
本文介绍如何在 langchain 构建的邮件 rag 系统中,突破语义检索局限,通过 openai 函数调用(function calling)机制,让大模型自动触发 python 原生逻辑(如 imap 邮件计数),从而准确回答“发送了多少封邮件”等聚合类问题。
本文介绍如何在 langchain 构建的邮件 rag 系统中,突破语义检索局限,通过 openai 函数调用(function calling)机制,让大模型自动触发 python 原生逻辑(如 imap 邮件计数),从而准确回答“发送了多少封邮件”等聚合类问题。
在基于 LangChain 的邮件智能问答系统中,RAG(检索增强生成)是核心范式:它先用向量数据库(如 FAISS + OpenAIEmbeddings)做语义相似度检索,再将匹配的邮件内容拼入 prompt 交由 LLM(如 GPT-3.5 Turbo)生成答案。这种方式对事实抽取类问题(如“最后一封发给谁的邮件时间?”)效果出色;但面对聚合统计类问题(如“共发送多少封邮件?”、“2024 年收到多少封含附件的邮件?”),单纯依赖 top-k 检索会失效——因为:
- 向量检索返回的是语义最相关的 样本(默认仅 3 条),而非全量数据;
- 统计结果需遍历/聚合原始数据源(IMAP 服务器),无法从片段文本中可靠推断;
- 将全部 150 封邮件塞入 prompt 既不现实(超上下文窗口),也违背 RAG 设计初衷。
✅ 正确解法:引入 LLM 工具调用(Tool Calling)机制,让大模型具备“主动决策+执行能力”。
OpenAI 的 chat.completions.create API 支持 tools 参数,允许你注册任意 Python 函数,并以自然语言描述其用途和参数。当用户提问涉及结构化操作时,模型会自动输出 JSON 格式的调用指令(含函数名与参数),而非直接作答。你的代码只需解析该指令、执行对应函数、并将结果反馈给模型(可选)或直接返回。
以下是一个最小可行示例,展示如何为“统计发件人邮件数量”功能集成工具调用:
import json
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY") # 替换为真实密钥
# ✅ 定义工具:描述给 LLM 的函数接口
tools = [
{
"type": "function",
"function": {
"name": "count_emails_by_sender",
"description": "统计指定发件人发送的邮件总数(需连接 IMAP 执行真实查询)",
"parameters": {
"type": "object",
"properties": {
"sender_email": {
"type": "string",
"description": "发件人的完整邮箱地址,例如 'alice@example.com'"
}
},
"required": ["sender_email"]
}
}
}
]
# ✅ 实现工具逻辑(此处为示意,需替换为真实 IMAP 查询)
def count_emails_by_sender(sender_email: str) -> int:
# 示例:实际应使用 imaplib 登录、搜索 'FROM "xxx"' 并获取 COUNT
print(f"[DEBUG] Querying IMAP for emails FROM: {sender_email}")
# ↓ 替换为真实 IMAP 逻辑 ↓
# import imaplib
# mail = imaplib.IMAP4_SSL("imap.gmail.com")
# mail.login("your@email.com", "app_password")
# mail.select("inbox")
# status, messages = mail.search(None, f'FROM "{sender_email}"')
# return len(messages[0].split()) if messages[0] else 0
return 42 # 占位返回值,实际请对接邮箱服务
# ✅ 主查询流程
def ask_email_question(query: str):
response = client.chat.completions.create(
model="gpt-3.5-turbo-1106", # 推荐使用支持 tool calling 的新版模型
messages=[{"role": "user", "content": query}],
tools=tools,
tool_choice="auto" # 让模型自主判断是否调用工具
)
message = response.choices[0].message
# 若模型决定调用工具
if message.tool_calls:
for tool_call in message.tool_calls:
if tool_call.function.name == "count_emails_by_sender":
args = json.loads(tool_call.function.arguments)
result = count_emails_by_sender(args["sender_email"])
print(f"✅ 工具执行成功:{args['sender_email']} 共发送 {result} 封邮件")
return result
else:
# 模型未调用工具 → 视为普通 RAG 问答(走原有流程)
print("ℹ️ 未触发工具调用,建议改用 RAG 流程处理该问题")
return None
# 测试调用
ask_email_question("How many mails have been sent by alice@example.com?")
? 关键注意事项:
- 模型版本要求:必须使用 gpt-3.5-turbo-1106 或 gpt-4-1106-preview 等支持函数调用的模型;旧版 gpt-3.5-turbo 不兼容。
- 工具注册即契约:tools 列表中的 description 和 parameters 是 LLM 理解能力的唯一依据,务必准确、无歧义。
- 安全边界:工具函数内应严格校验输入(如邮箱格式)、设置超时与重试,并避免暴露敏感凭证(推荐使用环境变量或密钥管理服务)。
- LangChain 集成提示:若你已在用 LangChain 的 ChatOpenAI,可通过 bind_tools() 方法绑定工具,再配合 agent_executor 构建更健壮的 Agent 工作流(进阶推荐)。
? 总结:RAG 解决“找相关内容”,工具调用解决“执行确定操作”。二者不是替代关系,而是互补组合。当你发现 LLM 总在“猜答案”而非“查数据”时,就是引入工具调用的最佳时机——让 AI 做推理者,让代码做执行者。











