前面的文章分别讨论了标签、语料、模型、状态、任务依赖和服务。现在把这些组件放到一起,实现一个可以连续对话的酒店助手:它能回答政策问题、查询模拟订单、补齐预订参数、处理修改与条件取消,并在确认后写入模拟结果。
本文介绍各模块的分工,以及安装、运行、切换识别器和测试的方法。下载完整工程 v3.0。
一、运行对话示例
解压后进入 intent-recognition-lab 目录。默认演示只使用 Python 标准库:
python3 batch3/assistant.py
它会运行预订、政策、条件取消、选择订单、修改和范围外请求等场景,输出每轮输入、路由、状态与结果。预订场景的主要变化如下:
| 用户输入 | 当前状态 | 返回结果 |
|---|---|---|
| 我要订房 | 预订目标已建立,缺城市和日期 | clarify:补充参数 |
| 北京 2026-10-01 2026-10-03 | 参数补齐,生成待确认方案 | confirm:展示方案 |
| 确认 | 模拟订单写入,会话任务结束 | mock_write:返回订单号 |
这里“补齐参数”和“完成预订”是两步。用户提供日期时,程序只形成方案;明确确认之后,才执行模拟操作。
订单数据全部位于本地 SQLite。DEMO-A 的演示取消费为 0,DEMO-B 为 50,DEMO-X 属于另一个演示用户。它们用于观察条件与权限分支,没有连接真实酒店或支付系统。
二、模块分工与请求流程
以“取消订单 DEMO-A”为例,HTTP 或 CLI 入口先提供会话、请求 ID、状态版本和文本。服务读取当前状态,检查是否为已完成请求的重试,再将新请求交给对话处理。
识别器给出取消目标,槽位提取器找到订单号。状态模块把目标与对象绑定,路由层查询订单归属、状态、费用和版本,形成待确认方案。此时返回 confirm,数据库中的订单仍保持 booked。
下一轮“确认”由待确认状态解释,而不是重新当作一个没有上下文的分类问题。执行前再次核实订单版本与条件;成功后,在同一个本地事务中更新模拟订单、会话状态和去重结果。
可以把组件职责对应到文件:
| 文件 | 职责 |
|---|---|
| core.py | 第一批输出契约与单任务状态更新 |
| llm.py | 可选的结构化 LLM 识别接口 |
| batch2/baselines.py | 规则、TF-IDF 和句向量候选分数 |
| batch2/policy.py | 分数与分差的接收、拒识和澄清决策 |
| batch2/task_graph.py | 多任务结构与依赖校验 |
| batch3/assistant.py | 上下文衔接、业务路由、模拟工具和持久化 |
| batch3/planner.py | 根据模拟查询事实推进任务图 |
| batch3/service.py | 本地 HTTP 请求契约 |
| batch3/evaluate.py | 读取预测结果并计算分类与拒识指标 |
第一批的识别输出契约与第二批任务图仍然分别保留。单任务状态与多任务计划承担不同职责,通过明确入口连接,比把所有字段塞进一个不断膨胀的 JSON 更容易理解。
三、短回复与上下文处理
用户说“取消订单”但没给订单号时,演示系统列出两个选项:“1 DEMO-A;2 DEMO-B。”下一轮的“1”先在当前有效选项里解释,得到 DEMO-A,再继续取消流程。
它不会进入一条全局的“数字 1 代表取消订单”规则。选项记录属于当前会话和当前任务;另一个会话中的“1”没有同样的含义。
日期与房型补充也类似。预订任务进行中,“北京 2026-10-01 2026-10-03”可以补入当前参数。用户明确提出新的预订目标时,则应切换任务,不能将新需求误当成旧任务的参数补充。
因此,程序会先处理确认回复、选项选择和参数补充,再调用通用意图识别器。利用已有的上下文信息,可以减少不必要的推断。
四、跨轮保留条件约束
运行“如果免费就取消订单 DEMO-B”,系统查询到演示费用为 50,返回 condition_false,没有进入确认,也不会取消订单。
如果用户最初只说“如果免费就取消订单”,条件需要跟随当前任务保存。下一轮选择 DEMO-B 后,仍然必须检查“免费”约束,不能因为这一轮只有数字,就把条件丢失。
即使条件在提出方案时成立,确认前也可能变化。附件保存方案使用的订单版本,并在提交前重新读取费用和状态。版本不一致或条件变化时返回冲突,交由上层重新展示方案。
更复杂的多任务请求,可以运行:
python3 batch3/planner.py
该命令接收系列第六篇定义的任务图。真实运行结果中,订单查询和查房节点完成,取消节点停在 requires_business_validation。它展示了依赖如何推进,也保留了写操作的确认入口。自由表达转成任意任务图,仍是识别与规划模块需要完成的工作;本命令从明确的结构化计划开始。
五、切换意图识别器
默认规则适合直接阅读和运行,覆盖教程使用的表达。需要句向量候选时,安装模型实验依赖并启动:
python -m pip install -r batch2/requirements.txt
python batch3/service.py --recognizer vector --db /tmp/intent-vector.sqlite3
句向量分支沿用前面固定的模型与验证参数,可能将支持不足的输入送去澄清。更换候选方法,不会自动放宽订单核验、参数检查或确认流程。
选择 --recognizer llm 时,适配器读取 OPENAI_API_KEY 与 OPENAI_MODEL,调用已有结构化接口。模板在 batch3/.env.example,服务不会自动加载它,需要由运行环境注入变量。LLM 返回已知的单一目标后,仍交给同一套状态与业务约束。
当前演示将候选识别与简化槽位提取分开,日期使用明确的 ISO 格式。扩展自然日期、复杂指代或完整 LLM 槽位结果时,应在接口适配层加入规范化和字段校验,而不是绕过状态模块直接执行工具。规则、句向量和可选 LLM 也不应被假定具有相同的识别覆盖。
本次验证包含本地规则链路与句向量接入;LLM 入口保留可运行配置,未消耗在线 API。银行八类学生模型用于展示训练流程,其标签与酒店不同,没有被直接接入酒店路由。
六、通过 HTTP 接口完成对话
python3 batch3/service.py --db /tmp/intent-demo.sqlite3 --port 8765
第一轮提出取消请求:
curl http://127.0.0.1:8765/turn \
-H 'Content-Type: application/json' \
-d '{"session_id":"cancel-demo","request_id":"r1","expected_version":0,"text":"取消订单 DEMO-A"}'
收到版本 1 的待确认方案后,再提交:
curl http://127.0.0.1:8765/turn \
-H 'Content-Type: application/json' \
-d '{"session_id":"cancel-demo","request_id":"r2","expected_version":1,"text":"确认"}'
若响应丢失,原样重发第二条请求即可读取保存的结果。不要只为了重试就生成新请求 ID。同一个 ID 换成其他文本,会返回幂等冲突;新请求携带旧状态版本,会返回状态冲突。
再次运行示例时,可以换一个会话 ID;模拟订单是数据库中的持久对象,已经取消的订单不会因新会话自动恢复。需要完整重置时,改用一个新的数据库文件路径。
七、运行测试与模型实验
从工程根目录执行:
python3 -m unittest discover -s tests -v
python3 batch2/tests.py
python3 batch3/tests.py
python3 batch3/http_smoke.py
前三项检查契约、状态、任务图和完整工作流;最后一项会启动真实的回环 HTTP 服务,发送请求并核对响应,然后结束服务。
重点用例包括:补槽后预订、咨询不触发操作、条件跨轮保留、确认前订单变化、费用变化、重复请求不重复写入、不同会话隔离、服务超时回滚、进程重启后继续任务。
模型与指标实验则另外运行:
python batch3/evaluate.py
python batch3/train_small.py
python batch3/infer_small.py "My card was charged twice"
功能测试检查程序的行为,分类实验检查模型对数据的判断。两类结果都保存在工程中,阅读时可以沿某条失败记录追到对应组件。
八、接入自己的业务
先替换能力目录和标签定义,再适配自己的槽位与业务对象。将模拟订单工具替换成真实服务时,要明确认证身份、工具幂等键、超时后的结果查询、费用变化与操作确认。
知识分支当前使用少量带来源的政策条目,目的是看清路由与证据返回。接入文档检索后,可以继续增加切块、混合检索、重排和答案组织,但对当前用户订单的读取仍应留在受权限约束的业务工具中。
并发服务应将耗时识别移到短事务之外,再使用版本检查提交;远程操作通过自身幂等与对账机制恢复。模型、Prompt、标签、阈值和路由版本应一起保存,才能复现一次线上判断。
接入新业务时,需要保留用户目标、上下文和条件约束,并根据这些信息选择下一步。操作是否完成,应以业务系统保存的结果为准。
九、系列阅读导航
| 想解决的问题 | 对应文章 |
|---|---|
| 系统里意图识别负责什么 | 总览 |
| 标签怎样划分 | 标签设计 |
| 复杂对话怎样标注 | 语料与下载 |
| 规则、向量、小模型怎样选 | 基础方法 |
| LLM 怎样返回稳定接口 | 结构化识别 |
| 短回复与改口怎样理解 | 多轮状态 |
| 多目标和条件怎样表示 | 任务依赖 |
| 何时拒识与追问 | 未知意图与澄清 |
| 改进是否有效 | 评测方法 |
| 怎样训练更轻量的识别器 | 分类头与蒸馏 |
| 识别后走哪条处理路径 | RAG 与 Agent 路由 |
| 服务怎样可靠运行 | 服务与错误闭环 |
读完并运行项目后,再回看每篇文章里的独立概念,会更容易理解它们为什么需要分开,以及它们如何共同影响一段对话的最终结果。