☰
从零搭建AI Agent:大模型+查询工具处理文字工单实战
2026/9/28 13:59:22 网站建设 项目流程

1. 从一条文字工单说起:为什么选大模型加查询工具这条路线

文字工单这东西,做过运维、客服、售后或者内部 IT 支持的人都不陌生。用户提交一段话,比如“我上周买的打印机今天开机一直闪红灯,订单号是 20250312-8871,麻烦帮我查下能不能换货”,这段文字里混着故障描述、订单信息、诉求意图,甚至还有情绪。传统做法是人工读一遍,判断类型,再去后台系统里翻订单、查物流、看售后政策,最后回复。一个人一天处理两三百条就到头了,而且状态好坏直接影响判断质量。

我这次要聊的,就是怎么用大模型配合一个查询工具,把这类文字工单的处理流程先跑起来。注意我的用词是“先跑起来”,不是“一步到位做成生产级系统”。很多人在搭建 AI Agent 的时候容易犯一个毛病:一上来就想把意图识别、多轮对话、知识库、工单流转、自动回复全部做完,结果卡在环境配置和接口调试上,两周过去连一个能演示的闭环都没有。我的建议一直是反过来的——先用最小可运行单元把主链路打通,再逐步加东西。

这条最小链路的核心就是两个东西:一个大模型负责“读懂人话并决定做什么”,一个查询工具负责“真的去数据里把结果捞出来”。大模型本身不知道你的订单表长什么样,也不知道库存系统里有没有货,它擅长的是理解自然语言、做推理、生成结构化调用参数。查询工具则是它的手和脚,负责执行具体的数据检索。两者通过 Tool Calling 机制连接起来,就形成了一个能处理文字工单的 AI Agent 雏形。

适合谁来参考这篇内容?如果你是会写一点 Python、懂基本的 API 调用、想从 0 到 1 搭建一个 AI Agent 练手项目的开发者,这篇就是写给你的。如果你是大模型学习路线上的新手,已经看过提示词工程和上下文工程的基础内容,但还没真正动手接过工具,那这篇也合适。甚至你只是想搞清楚“AI Agent 到底是怎么跑起来的”,跟着走一遍也能有直观感受。我不假设你有微调经验,也不要求你本地部署大模型,用免费的 API 加上一个 SQLite 查询工具就能起步。

2. 整体设计思路:为什么是“大模型 + 查询工具”而不是别的组合

2.1 文字工单处理的核心难点拆解

先把这个问题的难点说清楚,后面选型才有依据。文字工单处理看起来简单,实际上至少包含四层任务。第一层是意图识别,用户到底是要查订单、要退款、要报修,还是单纯发泄情绪。第二层是信息抽取,从一段自由文本里把订单号、商品名、时间、故障现象这些关键字段抠出来。第三层是数据查询,拿着抽出来的字段去对应的数据源里检索。第四层是结果组织,把查到的原始数据翻译成用户能看懂的话。

传统方案里,这四层要么用规则引擎硬编码,要么用专门的 NLP 模型分别训练。规则引擎的问题是写不完,用户换个说法就匹配不上;专门训练模型的问题是成本高,每个业务域都要标注数据、训练、调参。大模型出现之后,前两层和第四层它天然就能做,因为它就是在海量文本上训练出来的,理解意图和生成回复是它的强项。唯独第三层它做不了,因为数据在你的数据库里,不在它的参数里。

所以整个设计的核心判断就是:把大模型擅长的事交给大模型,把大模型做不了的事交给工具。这就是 Tool Calling 存在的意义。大模型不直接查数据库,它输出一个结构化的调用请求,比如“我要调用 query_order 这个工具,参数是 order_id=20250312-8871”,然后由外部程序真正执行查询,再把结果喂回给大模型,让它组织成最终回复。

2.2 为什么选查询工具作为第一个接入的能力

有人会问,为什么第一个接入的是查询工具,而不是写入工具、通知工具或者别的。我的考虑有三点。第一,查询是只读操作,风险最低。你让大模型去执行写操作,万一参数抽错了,可能把别人的订单改了,这种事故在练手阶段完全没必要冒。第二,查询的输入输出边界清晰,容易验证。给一个订单号,返回一条记录,对不对一眼就能看出来,调试成本低。第三,查询是工单处理里最高频的动作。大部分工单的本质就是“帮我查一下”,把查询跑通了,这个 Agent 就已经有实用价值了。

从 AI Agent 开发的角度看,查询工具也是理解 Tool Calling 最好的切入点。它足够简单,简单到你能把整个调用链路看得清清楚楚;又足够典型,典型到你换成别的工具时,套路完全一样。我见过不少人一上来就搞多工具编排、多智能体协作,结果连单个工具的调用参数怎么传都没搞明白。先把一个查询工具吃透,后面加十个工具都是复制粘贴的事。

2.3 大模型选型的实际考量

关于大模型选择,我不打算给一个绝对答案,因为这东西变化太快,而且每个人的约束条件不一样。但我可以给你一套判断逻辑。如果你只是想练手、验证流程,优先选有免费额度或者免费 API 的模型,别一上来就充钱。如果你对数据隐私敏感,考虑本地部署,但要有心理准备,本地跑 7B 级别的模型,工具调用的稳定性会比云端大模型差一些,需要更多提示词上的调教。

具体到工具调用能力,这是选型的硬指标。不是所有大模型都支持 Tool Calling,有些模型虽然能对话,但你让它输出结构化的函数调用格式,它就开始胡说。选之前一定要确认两件事:第一,官方文档里明确写了支持 function calling 或者 tool use;第二,社区里有实际跑通的案例。我个人的经验是,参数量在 7B 以上的指令微调模型,配合清晰的工具定义,基本都能跑通简单的单工具调用。如果你用的是更小的模型,或者没经过指令微调的基座模型,那就要做好反复调试的准备。

还有一个容易被忽略的点是上下文长度。文字工单本身不长,但如果你要把历史工单、知识库片段、工具返回结果都塞进上下文,长度就上去了。起步阶段不用太纠结,8K 上下文足够跑通流程,等真正要处理复杂工单时再考虑更长的上下文或者做上下文工程优化。

2.4 查询工具的技术选型

查询工具这块,我用的是 SQLite。原因很直接:零配置、单文件、Python 标准库自带。你不需要装 MySQL、不需要配用户权限、不需要起服务,一个 .db 文件就是整个数据库。对于练手项目来说,这是最低摩擦的选择。等你把流程跑通了,换成 MySQL 或者别的数据库,无非是改一下连接字符串和 SQL 方言,核心逻辑不变。

有人可能会问,热词里提到“mysql 查询工具 免费”,是不是应该用 MySQL。我的看法是,如果你本身就在用 MySQL,那直接用没问题。但如果你是从零开始搭练手项目,为了一个查询功能去装一整套数据库服务,性价比不高。SQLite 能让你把注意力放在 Agent 逻辑上,而不是环境配置上。等你需要多用户并发、需要远程访问的时候,再迁移也不迟。

工具的定义方式,我用的是最朴素的 Python 函数加 JSON Schema 描述。没有用 LangChain 之类的框架,也没有用 Spring AI 那套。不是说框架不好,而是起步阶段我希望每一行代码都是透明的,出了问题我能立刻定位。框架帮你省了样板代码,但也藏了细节,等你需要定制的时候反而更麻烦。先把裸的调用链路写一遍,之后再用框架就是降维打击。

3. 核心细节解析:Tool Calling 到底是怎么跑起来的

3.1 大模型眼里的“工具”是什么

很多人第一次接触 Tool Calling 会懵,觉得大模型怎么能“调用”外部函数,它又不是操作系统。这里要把概念掰清楚。大模型本身不执行任何代码,它做的只有一件事:根据你给的上下文,生成一段文本。所谓工具调用,本质上是你在提示词里告诉它“有这么几个工具可用,每个工具叫什么名字、干什么用、需要什么参数”,然后它生成的文本不是给用户看的回复,而是一段符合约定格式的调用请求。

这个约定格式,不同厂商的 API 略有差异,但核心结构是一样的:工具名加参数对象。比如用户问“帮我查下订单 20250312-8871”,大模型生成的可能是{"name": "query_order", "arguments": {"order_id": "20250312-8871"}}。你的程序拿到这个 JSON,去执行真正的查询,把结果再拼回对话历史,第二次请求大模型,它这次生成的就是给用户的自然语言回复了。

理解这一点很关键,因为它决定了你调试时的思路。当工具调用失败时,问题可能出在三个地方:大模型没理解该调用工具、大模型理解了但参数抽错了、参数对了但你的工具执行报错了。这三个问题的排查方法完全不同,后面我会细说。

3.2 工具描述怎么写才能让大模型用对

工具描述是 Tool Calling 里最容易被低估的环节。很多人随便写一句“查询订单”,然后抱怨大模型老是调不对。实际上,工具描述就是给大模型看的说明书,你写得越清楚,它用得越准。一份好的工具描述至少包含四部分:工具名、功能说明、参数定义、使用场景。

工具名要见名知意,用英文小写下划线风格,比如 query_order、search_logistics、check_refund_policy。功能说明用一句话讲清楚这个工具做什么,不要写“处理订单相关事务”这种模糊表述,要写“根据订单号查询订单的详细信息,包括商品、金额、状态、下单时间”。参数定义要说明每个参数的类型、含义、是否必填,最好给一个示例值。使用场景则是告诉大模型什么时候该用这个工具,比如“当用户提供了订单号并且想查询订单状态时使用”。

我踩过的一个坑是参数命名太随意。有一次我把参数写成id,结果大模型在用户说“查一下订单 123”的时候,把 123 当成了用户 ID 而不是订单 ID。后来改成order_id,并且在描述里明确写“订单号,通常是一串包含日期和序号的字符串”,准确率立刻上去了。参数名要自解释,别让大模型去猜。

3.3 查询工具的实现要点

查询工具本身就是一个普通的 Python 函数,接收参数,执行 SQL,返回结果。但有几个细节要注意。第一,返回值要是可序列化的结构,通常是字典或者列表,因为后面要转成 JSON 喂回给大模型。第二,要做好异常处理,查不到记录时不要抛异常,而是返回一个明确的“未找到”结果,让大模型知道该怎么回复用户。第三,返回的字段名要清晰,别用col1、col2这种,用order_status、product_name这种自解释的名字。

SQL 注入这个问题在练手阶段容易被忽略,但习惯要养好。永远不要用字符串拼接构造 SQL,用参数化查询。SQLite 的 Python 驱动支持?占位符,把参数作为元组传进去就行。虽然大模型生成的参数看起来人畜无害,但你不知道用户输入里会不会藏东西,参数化查询是零成本的防护。

还有一个实践细节是返回结果的裁剪。如果你的订单表有五十个字段,全返回给大模型既浪费 token 又干扰判断。只返回跟当前任务相关的字段,比如订单号、状态、金额、下单时间。这个裁剪逻辑放在工具函数里做,不要指望大模型自己去过滤。

3.4 对话循环的控制逻辑

Tool Calling 的完整流程是一个循环,不是一次请求就结束。第一轮,你把用户消息和工具定义发给大模型,它返回工具调用请求。你执行工具,把结果追加到对话历史。第二轮,你把更新后的对话历史再发给大模型,它这次返回自然语言回复。如果它又返回了工具调用请求,那就继续执行、继续追加,直到它返回纯文本回复为止。

这个循环要有终止条件,不能无限转下去。通常设置一个最大轮次,比如 5 轮,超过就强制结束并返回兜底回复。我见过因为工具返回格式不对,大模型反复调用同一个工具的情况,没有轮次限制就会死循环,烧 token 还出不来结果。

对话历史的管理也有讲究。工具调用的请求和结果都要按特定格式追加到消息列表里,不同 API 的格式要求不一样。有的要求工具结果用role: tool的消息,有的要求用role: user包一层。这个必须严格按文档来,格式错了大模型就理解不了上下文,会重复调用或者答非所问。

4. 实操过程:从零把这条链路跑通

4.1 环境准备与依赖安装

先把环境弄干净。我建议用虚拟环境,别把全局 Python 环境搞乱。Python 版本 3.9 以上都行,我用的是 3.11。创建虚拟环境、激活、装依赖,三步走。

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install openai

这里我装的是 openai 这个库,因为很多国内大模型的 API 都兼容 OpenAI 的接口格式,学会一套就能切换多家。如果你用的是特定厂商的 SDK,按官方文档装对应的包。SQLite 不用装,Python 标准库自带 sqlite3。

API Key 的管理要养成好习惯,别硬编码在代码里。用环境变量或者 .env 文件,代码里通过 os.environ 读取。练手项目也建议这么做,因为一旦养成硬编码的习惯,后面写生产代码很容易出事。

4.2 造一批测试工单数据

没有数据就没法验证,所以先造一个 SQLite 数据库,建一张订单表,塞几条测试数据。表结构不用复杂,够用就行。

import sqlite3 conn = sqlite3.connect('tickets.db') cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS orders ( order_id TEXT PRIMARY KEY, product_name TEXT, amount REAL, status TEXT, created_at TEXT, customer_name TEXT ) ''') test_orders = [ ('20250312-8871', '激光打印机 X200', 1299.00, '已发货', '2025-03-12', '张先生'), ('20250310-5523', '无线键盘 K380', 199.00, '已完成', '2025-03-10', '李女士'), ('20250315-9902', '显示器 27寸 4K', 2199.00, '待发货', '2025-03-15', '王先生'), ] cursor.executemany('INSERT OR REPLACE INTO orders VALUES (?,?,?,?,?,?)', test_orders) conn.commit() conn.close()

数据造好之后,手动查一下确认没问题。这一步别省,我见过数据库文件建错路径、表名拼错、字段类型不对的各种低级问题,提前查一下能省后面半小时的排查时间。

4.3 实现查询工具函数

工具函数要做得健壮一点。接收 order_id,查数据库,返回字典。查不到就返回一个带明确标识的结果,别抛异常。

import sqlite3 def query_order(order_id: str) -> dict: conn = sqlite3.connect('tickets.db') cursor = conn.cursor() cursor.execute( 'SELECT order_id, product_name, amount, status, created_at, customer_name FROM orders WHERE order_id = ?', (order_id,) ) row = cursor.fetchone() conn.close() if row is None: return {"found": False, "message": f"未找到订单号为 {order_id} 的订单"} return { "found": True, "order_id": row[0], "product_name": row[1], "amount": row[2], "status": row[3], "created_at": row[4], "customer_name": row[5] }

注意这里用了参数化查询,?占位符加元组传参。返回结构里加了found字段,这是给大模型看的信号,让它知道查询是成功还是没找到。字段名全部自解释,大模型拿到之后能直接理解每个值的含义。

4.4 定义工具 Schema 并接入大模型

工具 Schema 是给大模型看的说明书,用 JSON 格式描述。不同 API 的字段名略有差异,但结构大同小异。

tools = [ { "type": "function", "function": { "name": "query_order", "description": "根据订单号查询订单的详细信息,包括商品名称、金额、订单状态、下单时间和客户姓名。当用户提供了订单号并想查询订单相关问题时使用此工具。", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,格式通常为日期加序号,例如 20250312-8871" } }, "required": ["order_id"] } } } ]

description 里我特意写了“当用户提供了订单号并想查询订单相关问题时使用”,这是使用场景提示。parameters 里给了示例格式,帮助大模型识别订单号。这些细节看起来啰嗦,但实测下来对准确率提升很明显。

4.5 编写完整的对话循环

把上面的东西串起来,就是一个完整的处理流程。核心是一个 while 循环,不断请求大模型、执行工具、追加结果,直到大模型返回纯文本。

import json from openai import OpenAI client = OpenAI(api_key="你的API_KEY", base_url="你的API地址") def handle_ticket(user_message: str) -> str: messages = [ {"role": "system", "content": "你是一个工单处理助手,负责理解用户的问题并调用工具查询信息,然后用友好的语气回复用户。"}, {"role": "user", "content": user_message} ] max_rounds = 5 for _ in range(max_rounds): response = client.chat.completions.create( model="你的模型名", messages=messages, tools=tools, tool_choice="auto" ) msg = response.choices[0].message if msg.tool_calls: messages.append(msg) for tool_call in msg.tool_calls: if tool_call.function.name == "query_order": args = json.loads(tool_call.function.arguments) result = query_order(args["order_id"]) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) else: return msg.content return "抱歉,处理这个问题时遇到了困难,请稍后再试或联系人工客服。" # 测试 print(handle_ticket("我上周买的打印机订单 20250312-8871 现在什么状态了?"))

这段代码跑通,整个链路就活了。用户发一句话,大模型识别出要查订单,抽出订单号,调用 query_order,拿到结果,生成回复。你不需要写任何意图识别的规则,也不需要写任何字段抽取的正则,这些全由大模型完成。

4.6 实测效果与参数观察

我用上面三条测试数据跑了几轮,输入不同的问法,观察大模型的调用行为。问“订单 20250312-8871 到哪了”,它正确调用工具并返回“已发货”。问“我买键盘那个订单怎么样了”,它没有订单号,会先追问订单号,这是合理的。问“帮我查下 20250310-5523”,它直接调用工具返回“已完成”。

有一个值得注意的现象是,当用户消息里同时包含多个订单号时,大模型会发起多次工具调用。比如“帮我查下 20250312-8871 和 20250310-5523 这两个订单”,它会生成两个 tool_calls,我的循环里用 for 遍历处理,两个结果都追加回去,最后它生成一条合并的回复。这个行为是自动的,不需要额外配置。

参数方面,temperature 建议设低一点,0 到 0.3 之间。工具调用需要的是稳定和准确,不需要创造性。我试过 temperature 设 0.8,同样的输入偶尔会抽错订单号,调低之后就稳定了。max_tokens 不用设太大,工单回复通常不长,512 到 1024 足够。

5. 常见问题与排查技巧实录

5.1 大模型不调用工具,直接瞎编答案

这是最常见的问题。用户问订单状态,大模型不调工具,直接回复“您的订单正在处理中”。原因通常是工具描述不够清晰,或者系统提示词没有强调要用工具。解决办法有两个:一是在系统提示词里明确写“涉及订单查询必须调用 query_order 工具,不要凭猜测回答”;二是把工具描述写得更具体,把使用场景写进去。我实测下来,系统提示词里加一句“如果用户问题涉及具体订单信息,必须先调用工具查询”能解决大部分情况。

5.2 工具调用参数抽取错误

用户说“查一下 8871 那个订单”,大模型可能把 8871 当成完整订单号传进去,而实际订单号是 20250312-8871。这种部分匹配的问题,靠工具函数本身解决不了,因为工具只认完整订单号。我的处理方式是在工具返回“未找到”之后,让大模型根据上下文再追问用户完整订单号。或者在工具描述里强调“订单号是完整字符串,不要截取部分数字”。如果业务上确实需要支持模糊查询,那就在工具函数里加 LIKE 查询,但要注意返回多条时的处理逻辑。

5.3 工具返回结果格式导致大模型理解错误

有一次我把查询结果直接返回成字符串,大模型把整个 JSON 字符串当成了订单内容复述给用户,回复里全是花括号和引号。后来改成返回结构化的字典,并且在系统提示词里说明“工具返回的是结构化数据,请提取有用信息用自然语言回复”。格式问题在 Tool Calling 里很关键,返回给大模型的内容要干净、结构化、字段名清晰。

5.4 对话循环不终止

前面提过,工具返回格式不对或者大模型反复调用同一个工具,会导致循环不终止。除了设置最大轮次,还要在追加工具结果时确保格式正确。不同 API 对 tool 消息的格式要求不同,有的要求tool_call_id,有的要求name字段,必须严格按文档来。我建议在循环里加日志,打印每一轮大模型返回的内容,出问题时一眼就能看出卡在哪。

5.5 常见问题速查表

问题现象可能原因排查方向解决方式
不调用工具直接回答工具描述模糊、系统提示词未强调检查 description 和使用场景补充系统提示词,明确必须调用工具
参数抽取错误参数名不清晰、缺少示例检查参数定义改参数名,加示例值,加格式说明
返回结果被复述返回格式不结构化检查工具返回值返回字典,系统提示词说明提取信息
循环不终止工具结果格式错误打印每轮返回内容按 API 文档修正 tool 消息格式,加最大轮次
查不到订单订单号不完整或不存在检查传入参数工具返回未找到,让大模型追问完整订单号

5.6 几个踩坑之后的经验

第一个经验是,先把工具函数单独测通,再接大模型。我一开始图快,直接端到端跑,结果工具函数里一个 SQL 字段名拼错,大模型那边表现是“查询失败”,排查了半天才发现是数据库层的问题。后来我养成习惯,工具函数写完先手动调用几次,确认返回正确,再接进 Agent。

第二个经验是,日志要打全。每一轮发给大模型的消息、大模型返回的内容、工具执行的参数和结果,全部打出来。Tool Calling 的调试本质上是看数据流,没有日志就是盲调。我用的就是最简单的 print,够用了。

第三个经验是,别在起步阶段追求多工具。我见过有人第一个项目就定义五六个工具,结果大模型选择困难,该调 A 的时候调了 B。先把一个工具调到 95% 准确率,再加第二个。工具数量增加带来的复杂度不是线性的,是组合爆炸的。

第四个经验是,系统提示词值得反复打磨。同样一套工具定义,系统提示词写得好不好,准确率能差出两成。我的系统提示词模板是:角色定义 + 能力说明 + 工具使用规则 + 回复风格要求。这四块写清楚,大模型的表现会稳定很多。

6. 这条链路后续可以怎么扩展

把单工具查询跑通之后,扩展方向其实很自然。最直接的是加工具,比如加一个查物流的工具、加一个查退款政策的工具。工具多了之后,大模型会根据用户问题自动选择合适的工具,这就是多工具编排的雏形。但记住我前面说的,一个一个加,加一个调一个。

再往上是加多轮对话的记忆。现在的实现是无状态的,每次请求都是独立的。如果要处理“刚才那个订单帮我退了吧”这种依赖上下文的工单,就需要把历史对话维护起来。这个在消息列表里追加就行,但要注意上下文长度控制,太长了要做摘要或者裁剪。

还有一个方向是接入真实的数据源。SQLite 换成 MySQL 或者公司的订单系统 API,工具函数的实现变一下,上层的 Agent 逻辑完全不用动。这就是把工具抽象出来的好处,数据源换了,大模型那边的体验是一致的。

如果要做成真正能用的工单系统,还需要考虑并发、限流、错误重试、人工兜底这些工程问题。但这些都属于“跑起来之后”的事,起步阶段不用想太多。先把这条最小链路跑通,你会对 AI Agent 的工作方式有一个完全不同于看文章的理解。我自己最大的体会是,看再多 Tool Calling 的教程,都不如自己亲手把一个查询工具接上去跑一遍来得实在。那些参数格式、消息结构、循环控制的细节,只有真正跑过一遍才会变成你自己的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询