Dify AI工具接入指南.md 12 KB

Dify AI 工具接入完整部署记录(照着做就能成功)

实战日期:2026-08-15 | 目标:企微对话 → AI 调用 CordysCRM 售后 API 本文按「从头到尾」的顺序记录每一步操作、命令、踩坑和修复,下次接新工具照此文档复制执行。

〇、整体架构

企业微信消息
  → 回调服务(aliyun2:18790,wecom-callback)
  → Dify「企业微信」Chatflow 应用(Agent 节点,记忆窗口 10)
  → AI 判断意图 → 调用自定义工具(OpenAPI 定义)
  → 认证代理(aliyun2:8766,systemd cordys-api-proxy)
  → 目标 API(CordysCRM:8081)
  → 数据返回 → AI 格式化回答 → 推回企微

关键设计:AI 负责决策,代理负责认证安全,接口系统只做数据读写。


一、部署认证代理(10 分钟)

为什么需要代理:Dify 自定义工具只支持配「一个」认证 Header,但 CordysCRM 要两个(X-Access-Key + X-Secret-Key)。代理接收 Dify 的单 Token 请求,校验后注入双密钥转发。

1.1 代理脚本要点

/data/cordys_api_proxy.py(systemd cordys-api-proxy,端口 8766):

  • 校验 X-Proxy-Token(不对就 403)
  • 转发时注入 X-Access-Key / X-Secret-Key
  • 支持 chunked 传输(Dify 的 SSRF 代理用 chunked,不支持就读不到 body)
  • 参数修正钩子:可强制替换 AI 传错的参数(如 customFormId)
  • 日志全部 flush=True(否则 journald 里看不到)

    # systemd 服务
    cat > /etc/systemd/system/cordys-api-proxy.service << 'EOF'
    [Unit]
    Description=CordysCRM API Proxy for Dify
    After=network.target
    [Service]
    Type=simple
    ExecStart=/usr/bin/python3 /data/cordys_api_proxy.py
    Restart=always
    RestartSec=3
    [Install]
    WantedBy=multi-user.target
    EOF
    systemctl daemon-reload && systemctl enable --now cordys-api-proxy
    

1.2 安全要点

  • firewalld 不开 8766 → 外网访问不到,仅 Docker 内网可用
  • Dify api 容器访问宿主机的地址是 docker 网关:172.20.0.1:8766docker inspect dify_v1110_chns-api-1 看 Gateway)

1.3 验证

# 带 token:200
curl -s 'http://127.0.0.1:8766/custom-form/list' -H 'X-Proxy-Token: zilan-crm-proxy-2026'
# 无 token:403
curl -s -o /dev/null -w '%{http_code}' 'http://127.0.0.1:8766/custom-form/list'
# 从 Dify 容器内测试(模拟真实调用路径)
docker exec dify_v1110_chns-api-1 curl -s 'http://172.20.0.1:8766/custom-form/list' -H 'X-Proxy-Token: zilan-crm-proxy-2026'

二、编写 OpenAPI 工具定义(30 分钟)

2.1 先摸清接口(别猜)

# 用 api-docs 看接口全貌
curl -s 'http://127.0.0.1:8081/v3/api-docs' \
  -H 'X-Access-Key: <key>' -H 'X-Secret-Key: <secret>'
# 从中提取:路径、方法、参数 schema、必填字段

2.2 查业务字段(下拉选项的 ID 映射)

AI 填下拉框要填选项 ID 不是文字,所以 description 里必须写全映射:

公司=178642865321400000(子兰=...001/焕境=...002)
问题处理阶段=...(未处理=...001/安排中=...002/处理中=...003/已处理=.../推迟处理=...)

2.3 OpenAPI JSON 结构

{
  "openapi": "3.0.1",
  "info": {"title": "工具名", "version": "1.0.0"},
  "servers": [{"url": "http://172.20.0.1:8766"}],
  "security": [{"proxyTokenAuth": []}],
  "paths": {
    "/custom-form/data/page": {
      "post": {
        "operationId": "queryRecords",
        "summary": "查询售后记录",
        "description": "keyword 模糊匹配客户姓名。customFormId 固定 436092742804709376。",
        "requestBody": {"content": {"application/json": {"schema": {
          "type": "object",
          "properties": {
            "customFormId": {"type": "string", "default": "436092742804709376"},
            "keyword": {"type": "string", "description": "客户姓名"}
          }
        }}}}
      }
    }
  },
  "components": {"securitySchemes": {"proxyTokenAuth": {"type": "apiKey", "in": "header", "name": "X-Proxy-Token"}}}
}

⚠️ servers.url 用 docker 网关地址http://172.20.0.1:8766),因为请求是从 Dify api 容器发起的,容器里 127.0.0.1 是它自己。


三、创建 Dify 工具(10 分钟)

3.1 UI 方式(手动)

Dify 后台 → 工作室 → 企业微信 → 编辑 → 左下「工具」→ 自定义 → 创建 → OpenAPI schema 粘贴 → 认证选 API Key(Header X-Proxy-Token,值 zilan-crm-proxy-2026)。

3.2 控制台 API 方式(自动化,本战采用)

Step 1:建临时管理员账号(不改用户现有账号,用完即删)

-- Dify 密码哈希 = pbkdf2_hmac sha256 10000轮;password列 = base64(hexlify(dk));salt列 = base64(salt)
INSERT INTO accounts (id, name, email, password, password_salt, status, initialized_at)
VALUES (uuid_generate_v4(), 'temp-admin', 'temp-admin@zilan.local', '<hash>', '<salt>', 'active', CURRENT_TIMESTAMP);
INSERT INTO tenant_account_joins (id, tenant_id, account_id, role, current)
SELECT uuid_generate_v4(), '<tenant_id>', id, 'owner', true FROM accounts WHERE email='temp-admin@zilan.local';

⚠️ 哈希格式坑hash_password 返回 hexlify(dk)(hex 字节串),存入时还要 base64 一层。直接 base64(dk) 是错的,登录会「Invalid email or password」。

Step 2:登录password 字段必须先 base64 编码!)

PWD64=$(echo -n '密码' | base64)
curl -s -c /tmp/dify_cj.txt -X POST 'http://127.0.0.1:8088/console/api/login' \
  -H 'Content-Type: application/json' \
  -d "{\"email\":\"temp-admin@zilan.local\",\"password\":\"$PWD64\",\"language\":\"zh-Hans\"}"

⚠️ 登录接口的 password 加密就是 base64(libs/encryption.py 源码确认),不是 RSA。

Step 3:创建工具 provider

CSRF=$(grep csrf_token /tmp/dify_cj.txt | awk '{print $7}')
curl -s -b /tmp/dify_cj.txt -H "X-CSRF-Token: $CSRF" \
  -X POST 'http://127.0.0.1:8088/console/api/workspaces/current/tool-provider/api/add' \
  -H 'Content-Type: application/json' \
  -d '{
    "provider": "工具名",
    "icon": {"type": "emoji", "content": "🔧"},
    "credentials": {"auth_type": "api_key_header", "api_key_header": "X-Proxy-Token", "api_key_value": "zilan-crm-proxy-2026"},
    "schema_type": "openapi",
    "schema": "<openapi json 字符串>",
    "privacy_policy": "", "custom_disclaimer": "", "labels": []
  }'

⚠️ CSRF 坑:登录 cookie 里有 csrf_token,请求头必须带 X-CSRF-Token: <同值>,否则 401。 ⚠️ auth_type 值api_key_header(不是旧的 api_key)。

Step 4:拿到 provider id

SELECT id, name FROM tool_api_providers;  -- 记下 UUID,Agent 节点要用

四、改造 Chatflow:LLM → Agent 节点(40 分钟,本次踩坑重灾区)

4.1 准备工作

# 看当前图结构
SELECT id, type, version FROM workflows WHERE app_id='<app_id>' ORDER BY updated_at DESC;
# draft = 编辑版本;version 是时间戳的 = 已发布版本

4.2 Agent 节点完整 JSON(正确版)

{
  "type": "agent",
  "title": "AI助手",
  "desc": "",
  "version": "1",
  "model": {"provider": "langgenius/deepseek/deepseek", "model": "deepseek-v4-flash", "model_type": "llm", "mode": "chat", "completion_params": {"temperature": 0.7}},
  "agent_strategy_provider_name": "langgenius/agent/agent",
  "agent_strategy_name": "function_calling",
  "agent_strategy_label": "FunctionCalling",
  "memory": {"window": {"enabled": true, "size": 10}, "query_prompt_template": "{{#sys.query#}}", "role_prefix": {"user": "", "assistant": ""}},
  "tool_node_version": "2",
  "agent_parameters": {
    "model": {"value": {"provider": "langgenius/deepseek/deepseek", "model": "deepseek-v4-flash", "model_type": "llm", "mode": "chat", "completion_params": {"temperature": 0.7}}, "type": "mixed"},
    "tools": {"value": [{
      "provider_name": "<provider的UUID>",
      "provider_id": "<provider的UUID>",
      "type": "api",
      "tool_name": "queryRecords",
      "tool_label": "queryRecords",
      "tool_configurations": {},
      "enabled": true
    }], "type": "mixed"},
    "instruction": {"value": "<系统提示词>", "type": "constant"},
    "query": {"value": "{{#sys.query#}}", "type": "mixed"},
    "maximum_iterations": {"value": 3, "type": "constant"}
  }
}

4.3 三个致命坑(每个都实测踩过)

# 报错 根因 修法
1 '' is not a valid ModelType 模型参数缺 model_type 字段,且模型名要放 model 键(不是 name 按 4.2 补全
2 Invalid plugin id <工具名> 工具类型字段名是 type(值 api),我写成了 provider_type → 后端默认当 builtin "type": "api"
3 invalid input syntax for type uuid: "售后问题跟进表" 后端把工具 dict 的 provider_name 字段当 provider_id(UUID) provider_nameprovider_id 都填 UUID

⚠️ 核心教训:后端源码才是真相。三个坑都是读 agent_node.py 源码定位的:

  • tool.get("type") → 类型字段
  • tool.get("provider_name") → 被塞进 AgentToolEntity.provider_id
  • _fetch_modelvalue["provider"]/value["model"]/value["model_type"]

4.4 同步 draft + 发布(hash 机制)

# ① GET 拿当前 draft 的 hash
GET /console/api/apps/<app_id>/workflows/draft

# ② POST 提交(graph + features + hash + 环境变量)
POST /console/api/apps/<app_id>/workflows/draft
# payload: {"graph": {...}, "features": {...}, "hash": "<刚拿的hash>", "environment_variables": [], "conversation_variables": []}

# ③ 发布
POST /console/api/apps/<app_id>/workflows/publish
{"marked_name": "", "marked_comment": ""}

⚠️ hash 坑:每次 sync 后 hash 会变,下一次 sync 前必须重新 GET。否则报 draft_workflow_not_sync

4.5 提示词模板

你是XX助手(原有身份约束)

【工具使用规则】
1. 用户询问某客户的售后记录时,调用 queryRecords 工具(用客户姓名做 keyword)
2. 用户要求新增记录且信息齐全时,先复述确认,用户明确同意后再调用 addRecord
3. 用户要求修改记录时,先 queryRecords 查出记录 id,再调用 updateRecord
4. 查询不到就如实回答"未查到相关记录",禁止编造数据
5. 新增/修改成功后回复"已录入/已更新",并简述内容

五、测试(真实数据验证)

POST http://127.0.0.1:8088/v1/chat-messages
Header: Authorization: Bearer app-wecom-chat-20260815
Body: {"inputs": {}, "query": "帮我查一下刘丹的售后记录", "response_mode": "blocking", "user": "test_u"}

实测通过:AI 自动调 queryRecords,返回刘丹 3 条售后记录(地址/电话/问题类别/处理阶段/处理人/备注全部正确)。

测试期间的排障路径(以后遇到照这个顺序查):

  1. docker logs dify_v1110_chns-api-1 → 看 Agent/工具执行错误(最快定位)
  2. 日志里看到 8766 字样 → 请求已到代理,查 journalctl -u cordys-api-proxy
  3. 代理日志显示 → HTTP 500 → 目标 API 报错,看代理记录的 body,多半是 AI 传错参数
  4. AI 传错参数(如把表单名当 ID)→ 在代理加强制替换逻辑(比教 AI 靠谱)

六、收尾清单

  • 转移工具 ownerUPDATE tool_api_providers SET user_id='<真实账号id>' WHERE user_id='<临时账号id>'; ⚠️ 否则删临时账号后 Dify 界面报 user is None for api provider,工具全部不显示
  • 删除临时管理员账号(DELETE FROM accounts WHERE email='temp-admin@...'
  • 更新 CLAUDE.md(3.11 节:新 Dify Key、代理服务、工具清单)
  • 更新 Obsidian(本文件 + 企业微信集成方案.md)
  • 桌面回溯记录(含回滚方法)
  • 企微实测一轮真实对话

七、复用到其他系统(改哪里)

要改的 位置
目标 API 地址 OpenAPI 的 servers.url + 代理脚本 CRM_BASE
密钥 代理脚本 ACCESS_KEY/SECRET_KEY
接口定义 OpenAPI 的 paths + 字段映射
工具名 OpenAPI operationId + Agent 节点 tools 数组
提示词 instruction 的 value
参数修正逻辑 代理 _forward 里的钩子

全流程耗时参考:无坑首接约 1 小时;复用本文档约 30 分钟(坑都写在上面了)。