1. dify 接 pg 数据库为什么绕不开 DBHub
dify 本身是个工作流编排平台,它擅长把 LLM、知识库、代码节点串起来,但它默认不会直接连你的 PostgreSQL。你当然可以在代码节点里塞一段 psycopg2,但那样每次改表结构都要改代码,Agent 也没法自己决定查哪张表。DBHub 解决的就是这个问题:它是一个 MCP 服务,把数据库包装成一组标准工具(list_tables、describe_table、execute_sql 等),dify 的 Agent 通过 Function Calling 调用这些工具,就能自己拼 SQL 去查 pg。
适合谁:本地或云端跑着 dify,手里有一个 PostgreSQL(不管是本机、内网还是云数据库),想让工作流里的 Agent 直接读业务数据,比如查学生成绩、查订单、查日志。这篇的目标很明确——给你一份能直接复制的配置链路:DBHub 连接串、dify 侧 MCP 端点、TaoToken 统一 Key 的 settings.json/config.toml 骨架,最后跑一次查询验证连通性。
我试过把 DBHub 和 dify 分开部署在两台机器上,踩过的坑主要集中在连接串转义和容器网络这两块,下面会逐个说清楚。
2. 前置准备:TaoToken 统一 Key 与 DBHub 镜像
在动 dify 之前,先把两样东西准备好:一个能用的模型 Key,和一个跑起来的 DBHub。
模型这块,dify 的 Agent 节点需要 Function Calling 能力,所以你得有一个支持工具调用的模型。TaoToken 的好处是统一 Key,一个 Key 走多个模型,省得在 dify 里配一堆供应商。先去控制台拿 Key:
- 模型对话入口:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
- API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
API 基址统一用https://taotoken.net/api,这个地址不加 UTM,直接填进 dify 的模型供应商配置里。
DBHub 用官方镜像bytebase/dbhub,Docker 一条命令就能起。注意 DBHub 默认监听 8080,我们映射到宿主机 8081,避免和别的服务撞端口。
docker run --init \ --name dbhub \ --publish 8081:8080 \ bytebase/dbhub \ --transport http \ --port 8080 \ --dsn "postgres://用户名:密码@数据库地址:5432/库名?sslmode=disable"这里有个高频坑:密码里如果带@,必须转义成%40。比如密码是zqyt@123,连接串里要写成zqyt%40123,否则 DBHub 会把@后面的部分当成主机名,直接连不上。
# 密码含 @ 的正确写法 --dsn "postgres://roadshow_owner:zqyt%40123@101.200.238.239:5432/roadshow?sslmode=disable"启动后访问http://localhost:8081,能看到 DBHub 自带的 Web UI workbench,说明服务活着。这个 workbench 后面还有用——dify 里 Agent 执行的 SQL 会在这里留痕,方便你排查它到底查了什么。
3. 可复制配置:DBHub 连接串与 dify MCP 端点
DBHub 起来之后,dify 侧要做两件事:装 MCP 插件、加 MCP 服务。
先装插件。在 dify 的插件市场搜 MCP,安装官方那个 MCP 插件(支持 HTTP 和 SSE 两种传输)。装完在「工具」里能看到 MCP 分类。
然后添加 MCP 服务,选 HTTP 传输,服务端点 URL 填:
http://host.docker.internal:8081/mcp这里的关键是host.docker.internal。如果你的 dify 是 Docker 部署的,容器内部访问宿主机要用这个域名;如果 dify 和 DBHub 在同一个 Docker 网络里,直接填 DBHub 的容器名加端口,比如http://dbhub:8080/mcp。填错了会一直连不上,绿点不亮。
保存后如果看到绿点,说明 dify 已经成功握手 DBHub,工具列表也拉到了。
接下来是模型配置。在 dify 的「模型供应商」里选 OpenAI 兼容类型,API Base 填https://taotoken.net/api,Key 填你从控制台拿的那个。如果你用配置文件方式管理,TaoToken 的 settings.json 骨架大概长这样:
{ "model_provider": "openai_compatible", "api_base": "https://taotoken.net/api", "api_key": "sk-你的TaoTokenKey", "default_model": "claude-sonnet-4-5", "function_calling": true }如果你更习惯 config.toml 风格(比如在自建网关或 CLI 工具里复用同一套 Key),可以这样写:
[provider] name = "taotoken" api_base = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" [model] default = "claude-sonnet-4-5" supports_function_calling = true注意:dify 的 Agent 节点必须选支持 Function Calling 的模型,否则工具调用不会触发,Agent 只会干聊不查库。
4. 验证请求:建表、插数据、跑一次查询
配置齐了,得用真实数据验证链路通不通。下面这套 pg 建表语句可以直接在 DBHub 的 workbench 里执行,也可以在 psql 里跑。注意这是 PostgreSQL 语法,和 MySQL 有区别:自增用SERIAL,枚举用INT加 CHECK 约束,YEAR类型换成SMALLINT。
-- 教师表 CREATE TABLE teachers ( teacher_id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, gender INT, contact_phone VARCHAR(20), email VARCHAR(100), hire_date DATE NOT NULL, CONSTRAINT check_teacher_gender CHECK (gender IS NULL OR gender IN (1,2,3)) ); COMMENT ON TABLE teachers IS '教师基本信息表'; -- 班级表 CREATE TABLE classes ( class_id SERIAL PRIMARY KEY, class_name VARCHAR(50) NOT NULL, head_teacher_id INT NOT NULL, create_year SMALLINT NOT NULL, CONSTRAINT fk_head_teacher FOREIGN KEY (head_teacher_id) REFERENCES teachers(teacher_id) ); -- 学生表 CREATE TABLE students ( student_id SERIAL PRIMARY KEY, student_number CHAR(12) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender INT, birth_date DATE, class_id INT NOT NULL, enrollment_date DATE NOT NULL, contact_phone VARCHAR(20), CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES classes(class_id), CONSTRAINT check_student_gender CHECK (gender IS NULL OR gender IN (1,2,3)) ); -- 课程表 CREATE TABLE courses ( course_id SERIAL PRIMARY KEY, course_code VARCHAR(20) NOT NULL UNIQUE, course_name VARCHAR(100) NOT NULL, credit NUMERIC(3,1) NOT NULL, teacher_id INT NOT NULL, course_hours SMALLINT, CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES teachers(teacher_id) ); -- 成绩表 CREATE TABLE exam_results ( result_id SERIAL PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, exam_date DATE NOT NULL, score NUMERIC(5,2), exam_type INT NOT NULL, recorder_id INT NOT NULL, record_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT unique_exam_record UNIQUE (student_id, course_id, exam_date, exam_type), CONSTRAINT fk_result_student FOREIGN KEY (student_id) REFERENCES students(student_id), CONSTRAINT fk_result_course FOREIGN KEY (course_id) REFERENCES courses(course_id), CONSTRAINT fk_result_recorder FOREIGN KEY (recorder_id) REFERENCES teachers(teacher_id), CONSTRAINT exam_results_score_check CHECK (score >= 0 AND score <= 100), CONSTRAINT exam_results_type_check CHECK (exam_type IN (1,2,3,4)) );插几条测试数据,让 Agent 有东西可查:
INSERT INTO teachers (name, gender, hire_date) VALUES ('张老师', 1, '2020-09-01'), ('李老师', 2, '2019-03-15'); INSERT INTO classes (class_name, head_teacher_id, create_year) VALUES ('2023级1班', 1, 2023); INSERT INTO students (student_number, name, gender, class_id, enrollment_date) VALUES ('202301010001', '王小明', 1, 1, '2023-09-01'), ('202301010002', '赵小红', 2, 1, '2023-09-01'); INSERT INTO courses (course_code, course_name, credit, teacher_id) VALUES ('MATH101', '高等数学', 4.0, 1); INSERT INTO exam_results (student_id, course_id, exam_date, score, exam_type, recorder_id) VALUES (1, 1, '2024-01-10', 88.5, 2, 1), (2, 1, '2024-01-10', 92.0, 2, 1);数据就位后,回到 dify 建一个对话流。Agent 节点的指令可以这样写,把表结构喂给它:
你是一个学校的工作人员,负责调用工具查询老师、学生、班级、课程和成绩信息。 数据库表结构如下: - teachers(teacher_id, name, gender, contact_phone, email, hire_date) - classes(class_id, class_name, head_teacher_id, create_year) - students(student_id, student_number, name, gender, birth_date, class_id, enrollment_date, contact_phone) - courses(course_id, course_code, course_name, credit, teacher_id, course_hours) - exam_results(result_id, student_id, course_id, exam_date, score, exam_type, recorder_id, record_time) gender 和 exam_type 用整数表示,gender: 1男 2女 3其他,exam_type: 1期中 2期末 3补考 4测验。 根据用户提问查询数据库并返回结果。Agent 策略选 Function Calling,模型选你配好的 TaoToken 模型。预览时问一句「2023级1班高等数学期末成绩最高的是谁」,Agent 会自己调 DBHub 的 execute_sql 工具,拼出类似这样的 SQL:
SELECT s.name, e.score FROM exam_results e JOIN students s ON e.student_id = s.student_id JOIN courses c ON e.course_id = c.course_id WHERE c.course_name = '高等数学' AND e.exam_type = 2 ORDER BY e.score DESC LIMIT 1;返回「赵小红 92.0」,链路就通了。同时去 DBHub 的 workbench(http://localhost:8081)能看到这条 SQL 的执行记录,确认 Agent 真的查了库而不是编答案。
5. 本篇常见错排查
绿点不亮,MCP 连不上。九成是端点 URL 写错。dify 在容器里,localhost指的是 dify 自己,不是宿主机。用host.docker.internal或者同网络的容器名。另外确认 DBHub 的--transport http参数带了,默认可能是 stdio。
连接串报错,提示 host 解析失败。检查密码里的特殊字符。@转义成%40,:转义成%3A,/转义成%2F。最稳妥的办法是给数据库单独建一个只读账号,密码用纯字母数字,省得转义。
Agent 不调工具,只回文字。模型不支持 Function Calling,或者 dify 里没开工具调用。换一个明确支持工具调用的模型,Agent 策略确认选的是 Function Calling 而不是 ReAct(部分版本 ReAct 对 MCP 工具支持不稳)。
SQL 执行报权限错误。DBHub 用的数据库账号没有目标表的 SELECT 权限。给它授只读权限就行:
GRANT SELECT ON ALL TABLES IN SCHEMA public TO 你的dbhub账号;查出来的字段是数字不是中文。因为 gender 和 exam_type 存的是 INT。要么在 Agent 指令里写清楚映射关系,要么在数据库里建视图把 INT 转成中文,让 Agent 查视图。
dify 版本差异导致 MCP 插件入口不同。老版本 dify 可能没有内置 MCP 插件市场,需要手动装插件包。建议升到较新版本,或者用 HTTP 请求节点直接调 DBHub 的 REST 接口作为兜底。
6. 长期跑 Agent 查库,Key 和模型怎么管
如果你只是偶尔查一次,上面这套配置够用了。但如果你要把这个对话流做成长期运行的 Agent,比如每天定时跑报表、或者接进客服系统实时查订单,那模型调用量会上来,Key 管理和成本控制就得提前想。
TaoToken 的统一 Key 在这里的价值是:你不用在 dify 里为每个模型单独配供应商,一个 Key 切换模型,Agent 节点换模型只改一个字段。对于需要长期编码或跑 Agent 的场景,Coding Plan 比按量付费更划算,适合高频调用的工作流:
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
- 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
另外提醒一句:DBHub 暴露的是数据库的完整查询能力,生产环境务必用只读账号,并且限制它能访问的 schema。别让 Agent 拿到 DROP 或 DELETE 的权限,这是接入生产库的底线。