大模型实战:把 DeepSeek RAG 接入企业 IM(钉钉 / 企业微信)
本文介绍了如何将基于DeepSeek和本地向量检索的企业知识库问答系统接入钉钉和企业微信平台。文章首先提出整体架构思路:用户在IM平台@机器人提问,消息转发到服务端接口,调用RAG引擎处理后返回答案。具体实现分为三步:1)将RAG封装为HTTP服务;2)通过钉钉Webhook实现群聊问答;3)通过企业微信回调URL实现类似功能。文中提供了核心代码示例,并给出多知识库路由、权限控制、限流等实战建议。
在上一篇里,我们已经实现了一个基于 DeepSeek + 本地向量检索 的企业知识库问答 Demo。
但如果只能在命令行或 Web 页面里用,还离“真正落地”有点远——大部分同事日常待的地方,其实是:
- 钉钉群聊 / 个人聊天
- 企业微信群聊 / 个人聊天
这一篇,我们就做一件事:
把第一篇做好的 DeepSeek RAG 知识库问答服务,接入到 钉钉 / 企业微信 里,让同事在群里 @ 机器人就能问公司内部知识。
一、整体思路 & 架构
无论是钉钉还是企业微信,本质都是:
- 用户在群里 @ 机器人 / 发消息
- IM 平台把这条消息转发到你配置的服务端接口(HTTP 或 WebSocket)
- 你的服务解析消息 → 调用 RAG(向量检索 + DeepSeek)→ 生成答案
- 把答案按平台要求的格式返回给 IM 平台
- 平台把这条消息以“机器人发言”的形式展示给用户
可以抽象成:
钉钉 / 企业微信
↓ (消息推送)
你的后端服务(RAG 网关)
↓ (本地向量检索 + DeepSeek API)
DeepSeek RAG 问答引擎(上一篇已经实现)
所以这篇的核心不是再造 RAG,而是实现一个“IM 消息 ↔ RAG 引擎”的中间层。
二、准备工作(承接第 1 篇)
默认你已经有了下面这些:
- 上一篇文章中的代码文件
rag_deepseek_demo.py,里面有:load_knowledge_baseVectorDBRAGWithDeepSeek
- 一个能跑通的 RAG 问答脚本(本地测试没有问题)
- 已申请好 DeepSeek 的 API Key,并在环境变量中配置:
export DEEPSEEK_API_KEY="your_real_key" - 你有一台可以部署服务的机器(公司内网、云服务器都可以)
本篇我们只在这基础上“加一层 IM 接入网关”。
三、把 RAG 封装成一个 HTTP 服务
不论是钉钉还是企业微信,最终都会调用一个 HTTP / HTTPS 接口,所以先把 RAG 封装成一个简单的 HTTP 服务。
这里用 FastAPI 做例子(Flask / Django 都可以自行替换):
pip install fastapi uvicorn
创建 rag_service.py:
# -*- coding: utf-8 -*-
"""
RAG HTTP 服务:对外暴露一个 /ask 接口
"""
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from rag_deepseek_demo import load_knowledge_base, VectorDB, RAGWithDeepSeek
# ====== 初始化 RAG 引擎(与上一节保持一致) ======
KB_PATH = "data/knowledge_base.json"
docs = load_knowledge_base(KB_PATH)
vdb = VectorDB(docs)
vdb.build(n_neighbors=4)
rag = RAGWithDeepSeek(vdb)
class AskRequest(BaseModel):
user_id: str
question: str
class AskResponse(BaseModel):
answer: str
refs: list
app = FastAPI()
@app.post("/ask", response_model=AskResponse)
def ask(req: AskRequest):
"""
统一问答接口:
- user_id:来自钉钉 / 企业微信的用户 ID(方便做限流、权限)
- question:用户问题
"""
result = rag.answer(req.question, top_k=3)
return AskResponse(
answer=result["answer"],
refs=[d["title"] for d in result["retrieved_docs"]]
)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=9000)
启动:
python rag_service.py
现在你已经有一个统一的 HTTP RAG 问答服务:
POST http://your_host:9000/ask
{
"user_id": "some_id",
"question": "DeepSeek-Reasoner 适合做什么?"
}
四、接入钉钉:企业内部机器人 + Webhook
钉钉有很多接入方式,这里选 企业内部机器人 + Webhook,优点是实现简单、文档多。
4.1 在钉钉后台创建机器人
-
「应用开发 → 企业内部开发 → 机器人 → 创建应用」
-
填写名称、图标,创建后进入“开发管理”
-
找到「机器人配置」或「消息推送」,添加一个 自定义机器人 / 自定义关键词机器人
-
获取一个类似这样的 Webhook 地址:
https://oapi.dingtalk.com/robot/send?access_token=xxxxxx -
在“安全设置”中,建议打开 加签,会生成一个
secret
记住这两个值:
WEBHOOK_URLSECRET(加签密钥)
4.2 编写钉钉机器人转发服务
这里我们做的是“被动通知”的模式:
钉钉群里 @ 机器人 → 你收到消息 → 调用 RAG → 再用 Webhook 把答案发回群里。
安装依赖:
pip install requests
新建 dingtalk_bot.py:
# -*- coding: utf-8 -*-
"""
钉钉群机器人:接收群消息 → 调用 RAG 服务 → 通过 Webhook 回答
"""
import os
import time
import hmac
import hashlib
import base64
import urllib.parse
import requests
from fastapi import FastAPI, Request
# 这些在钉钉机器人后台获取
WEBHOOK_URL = os.getenv("DINGTALK_WEBHOOK", "https://oapi.dingtalk.com/robot/send?access_token=xxxx")
SECRET = os.getenv("DINGTALK_SECRET", "your_secret")
RAG_URL = os.getenv("RAG_URL", "http://127.0.0.1:9000/ask")
app = FastAPI()
def sign():
"""
生成当前时间戳对应的加签字符串
"""
timestamp = str(round(time.time() * 1000))
secret_enc = SECRET.encode('utf-8')
string_to_sign = '{}\n{}'.format(timestamp, SECRET).encode('utf-8')
hmac_code = hmac.new(secret_enc, string_to_sign, digestmod=hashlib.sha256).digest()
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
return timestamp, sign
def send_dingtalk_markdown(title: str, text: str):
"""
通过自定义机器人 Webhook 发送 markdown 消息
"""
timestamp, sign_str = sign()
url = f"{WEBHOOK_URL}×tamp={timestamp}&sign={sign_str}"
headers = {"Content-Type": "application/json;charset=utf-8"}
data = {
"msgtype": "markdown",
"markdown": {
"title": title,
"text": text
}
}
resp = requests.post(url, json=data, headers=headers, timeout=10)
return resp.json()
@app.post("/dingtalk/callback")
async def dingtalk_callback(req: Request):
"""
钉钉群里 @机器人 后,配置的“自定义回调地址”会收到消息
注意:这里假设你已经在钉钉后台配置了这个 URL
"""
body = await req.json()
# body 结构参考钉钉文档,这里只处理文本消息
msg_type = body.get("msgtype")
if msg_type != "text":
return {"msg": "ignore non-text"}
content = body["text"]["content"].strip()
sender_id = body.get("senderStaffId", "unknown")
# 把 @机器人 的前缀去掉,只保留真正的问题
# 例如 "@知识库机器人 DeepSeek-Reasoner 适合做什么?"
if content.startswith("@"):
content = content.split(" ", 1)[-1].strip()
if not content:
send_dingtalk_markdown("提示", "请直接输入你的问题,例如:\n\n> DeepSeek-Reasoner 适合做什么?")
return {"msg": "empty"}
# 调用本地 RAG 服务
try:
resp = requests.post(
RAG_URL,
json={"user_id": sender_id, "question": content},
timeout=30
)
resp.raise_for_status()
data = resp.json()
answer = data["answer"]
refs = data.get("refs", [])
except Exception as e:
send_dingtalk_markdown("系统错误", f"处理你的问题时出错:{e}")
return {"msg": "error"}
# 组织 Markdown 答案
ref_text = ""
if refs:
ref_text = "\n\n---\n**引用文档**:\n" + "\n".join([f"- {r}" for r in refs])
md = f"""**你的问题:**
> {content}
**答案:**
{answer}{ref_text}
"""
send_dingtalk_markdown("知识库问答", md)
return {"msg": "ok"}
启动:
uvicorn dingtalk_bot:app --host 0.0.0.0 --port 9100
然后在钉钉机器人配置里把「自定义回调地址」配置为:
https://你的域名或IP:9100/dingtalk/callback
(内网环境可配反向代理 / 内网穿透,这里不展开)
此时,在群里输入:
@知识库机器人 DeepSeek API 怎么调用?
就会触发我们上面的逻辑 → 调用 RAG → 机器人以 Markdown 形式把答案发回群里。
五、接入企业微信:自建应用 + 回调 URL
企业微信这边稍微复杂一点,需要处理 消息签名 & 解密。
为了不让文章被“加解密细节”淹没,这里给一个 简化版思路 + 伪代码结构,你落地时可以直接换成官方 SDK 或社区封装。
5.1 在企业微信后台创建应用
- 登录 https://work.weixin.qq.com/
- 「应用管理 → 自建应用 → 创建应用」
- 记录以下信息:
- 企业 ID(CorpID)
- 应用 ID(AgentId)
- 应用 Secret
- 在该应用的“接收消息”中:
- 填写服务器 URL(例如
https://your-domain.com/wecom/callback) - 设置 Token(自定义)
- 设置 EncodingAESKey(自动生成)
- 填写服务器 URL(例如
- 保存后,企业微信会向你的 URL 发送一个验证请求,你需要按文档要求原样返回
echostr
5.2 企业微信 ↔ RAG 中转服务
依赖:
pip install fastapi
新建 wecom_bot.py(把签名/解密部分用占位函数说明,实际用官方 SDK 替换):
# -*- coding: utf-8 -*-
"""
企业微信自建应用:回调消息 → 调用 RAG → 主动回复
"""
import os
import time
import requests
from fastapi import FastAPI, Request, Response
# 这些从企业微信后台获取
CORP_ID = os.getenv("WECOM_CORP_ID", "your_corp_id")
AGENT_ID = os.getenv("WECOM_AGENT_ID", "1000001")
SECRET = os.getenv("WECOM_SECRET", "your_secret")
TOKEN = os.getenv("WECOM_TOKEN", "your_token")
ENCODING_AES_KEY = os.getenv("WECOM_AES_KEY", "your_encoding_aes_key")
RAG_URL = os.getenv("RAG_URL", "http://127.0.0.1:9000/ask")
app = FastAPI()
# ====== 工具函数(简化版,占位)======
def verify_signature(signature: str, timestamp: str, nonce: str, echostr: str) -> bool:
"""
校验 URL 签名(简化版思路:按 Token + timestamp + nonce 排序后 SHA1)
实际使用请参考企业微信官方文档 / SDK
"""
import hashlib
tmp_list = [TOKEN, timestamp, nonce]
tmp_list.sort()
tmp_str = "".join(tmp_list)
hashcode = hashlib.sha1(tmp_str.encode("utf-8")).hexdigest()
return hashcode == signature
def decrypt_message(xml: str) -> dict:
"""
解密并解析企业微信消息(简化,实际请用官方 SDK)
返回一个 dict,至少包含:
- FromUserName(发送人)
- MsgType(消息类型)
- Content(文本内容)
"""
# TODO: 替换成真实解密逻辑
# 这里可以先用“明文模式”调通流程,再逐步换成加密模式
import xmltodict
data = xmltodict.parse(xml)["xml"]
return data
def build_text_reply(to_user: str, from_user: str, content: str) -> str:
"""
构造一个最简单的文本回复 XML
"""
now = int(time.time())
reply = f"""
<xml>
<ToUserName><![CDATA[{to_user}]]></ToUserName>
<FromUserName><![CDATA[{from_user}]]></FromUserName>
<CreateTime>{now}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{content}]]></Content>
</xml>
""".strip()
return reply
# ====== 回调入口 ======
@app.get("/wecom/callback")
async def wecom_verify(signature: str, timestamp: str, nonce: str, echostr: str):
"""
企业微信验证 URL 有效性时会发 GET 请求到这里
"""
if verify_signature(signature, timestamp, nonce, echostr):
# 正常实现里,echostr 需要用 AES 解密后再返回,这里简化
return Response(echostr)
else:
return Response("invalid signature", status_code=401)
@app.post("/wecom/callback")
async def wecom_callback(request: Request):
"""
接收用户消息
"""
xml = await request.body()
msg = decrypt_message(xml.decode("utf-8"))
msg_type = msg.get("MsgType")
if msg_type != "text":
return Response("success") # 非文本消息先忽略
user_id = msg.get("FromUserName")
content = msg.get("Content", "").strip()
# 可选:去掉“@机器人”的前缀
if content.startswith("@"):
content = content.split(" ", 1)[-1].strip()
if not content:
return Response("success")
# 调用 RAG 服务
try:
resp = requests.post(
RAG_URL,
json={"user_id": user_id, "question": content},
timeout=30
)
resp.raise_for_status()
data = resp.json()
answer = data["answer"]
refs = data.get("refs", [])
except Exception as e:
answer = f"处理你的问题时出错:{e}"
refs = []
ref_text = ""
if refs:
ref_text = "\n\n---\n引用文档:\n" + "\n".join([f"- {r}" for r in refs])
full_answer = f"你的问题:{content}\n\n{answer}{ref_text}"
reply_xml = build_text_reply(
to_user=msg["FromUserName"],
from_user=msg["ToUserName"],
content=full_answer
)
# 实际加密模式下,需要对 reply_xml 再包一层加密,这里先返回明文流程
return Response(reply_xml, media_type="application/xml")
启动:
pip install xmltodict uvicorn wecom_bot:app --host 0.0.0.0 --port 9200
然后在企业微信后台,把回调 URL 配置为:
https://你的域名或IP:9200/wecom/callback
先用“明文模式”跑通(安全模式 / 兼容模式的加解密,用官方 SDK 即可对照替换)。
六、几个实战细节建议
1. 问题前缀规范
为了方便统计和后期扩展,你可以约定:
- “
#产品开头的问题走产品知识库” - “
#技术开头的问题走技术知识库” - “
#人事开头的问题走 HR 知识库”
在 RAG 侧可以维护多套 VectorDB,根据前缀路由:
if content.startswith("#产品"):
engine = product_rag
question = content.replace("#产品", "").strip()
elif content.startswith("#技术"):
engine = tech_rag
question = content.replace("#技术", "").strip()
else:
engine = default_rag
question = content
2. 权限与敏感信息控制
- 通过
user_id/senderStaffId查询用户部门(调用钉钉 / 企业微信通讯录接口) - 为不同部门加载不同的知识库索引
- 对包含关键词(如“薪资”“财务报表”“密码”)的问题直接拦截或转人工
3. 限流与稳定性
- 钉钉自定义机器人有发送频率限制:每分钟最多 20 条,企业微信也有 QPS 限制
- 可以在 IM 网关层做:
- 单用户 QPS 限流(例如每分钟最多 10 次)
- RAG 调用失败时,返回“系统繁忙,请稍后再试”而不是把异常抛给用户
4. 日志与埋点
建议至少记录:
- platform(dingtalk / wecom)
- user_id
- question
- answer 片段(可脱敏)
- 命中的文档 ID / 标题
- DeepSeek 调用耗时、Token 用量(用于成本核算)
后续可以:
- 统计 FAQ
- 优化知识库内容和向量模型
- 针对常见问题提前缓存答案,减轻模型压力
七、从 Demo 到企业应用:一个可行的路线图
-
第 0 步:本地 RAG Demo 跑通
完成第一篇中的向量检索 + DeepSeek 问答脚本。 -
第 1 步:抽象出统一的 /ask HTTP 接口
用 FastAPI / Flask 封一层,让任何客户端都能通过 HTTP 调用 RAG。 -
第 2 步:先接入一个平台(建议钉钉 Webhook)
- 用上面
dingtalk_bot.py的逻辑 - 在一个测试群做试用,看问题形式、效果、延时
- 用上面
-
第 3 步:再接入企业微信
- 先用明文模式(方便调试)
- 基本流程稳定后,再切到安全模式 + SDK 加解密
-
第 4 步:做企业级增强
- 多知识库路由、权限控制、日志分析、缓存、监控
- 往“智能客服 / 智能助理”方向演进
-
第 5 步:产品化 / 平台化
- 管理后台:配置知识库、查看问答日志、手动标注问题
- 多租户:支持多个 BU / 子公司共用一套系统
八、总结
这一篇,我们把上一集的 DeepSeek RAG 知识库 Demo,真正“搬进了工作 IM”:
- 用一个统一的
/askHTTP 接口,把 本地向量检索 + DeepSeek 封装成服务 - 通过 钉钉自定义机器人 + Webhook 实现“群里 @机器人 → 知识库问答”
- 通过 企业微信自建应用 + 回调 URL 实现类似能力
- 给出了可直接改造的核心代码结构,重点放在“消息转发 → 调 RAG → 格式化回复”的完整链路
你可以直接按文中的结构把代码拆成三层:
- RAG 内核层:向量检索 + DeepSeek 调用(上一篇已有)
- RAG HTTP 服务层:统一的
/ask接口 - IM 适配层:钉钉 / 企业微信 / 甚至飞书、Slack 的适配器
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)