第二章 AI Agent 意图识别 & 槽位抽取(可落地 Python 实现|原理精讲 + 多套方案 + 完整代码)
目录
第二章 AI Agent 意图识别 & 槽位抽取(可落地 Python 实现|原理精讲 + 多套方案 + 完整代码)
方案一:LLM 结构化意图 + 槽位抽取(工程首选,带 Pydantic 强格式约束)
第三节 融合架构:LLM 主解析 + 规则兜底(生产最终封装类)
本章前置说明
1、模块定位
预处理层输出干净、规整文本 → 送入本章模块。 本章是 Agent 的决策大脑入口,解决两个核心问题: 1)意图识别:用户究竟要做什么事(分类问题);区分单意图、多意图、无效指令拒识 2)槽位抽取:完成这件事必须的关键参数(时间、文件、部门、渠道、格式、人员等) 搭配:歧义消解 + 非法指令拒识,保证 Agent 不乱执行、不瞎调用工具
2、两套落地方案(按需选用)
| 方案 | 技术 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 方案一:Prompt 大模型原生抽取(优先业务落地) | LLM API / 本地 LLM 结构化输出 | 90% 办公 Agent、快速开发迭代 | 不用标注数据、适配复杂口语、多意图天然支持 | 有少量调用成本,需要约束输出格式 |
| 方案二:轻量规则 + 正则 + 实体匹配(兜底低成本) | 正则字典 + 实体关键词库 | 固定办公指令、低并发、无模型依赖 | 零成本、响应毫秒级、稳定可控 | 复杂句式、模糊语句识别差 |
企业标准落地架构:LLM 方案做主逻辑,规则方案做兜底校验
3、依赖补充安装
# 若使用本地大模型调用
pip install openai python-dotenv pydantic
# 规则匹配辅助库(可选)
pip install pyparsing
4、承接上一章
导入第一章的预处理总类,代码文件建议把第一章代码放在preprocessor.py,本章新建intent_slot.py
第一节 基础概念通俗讲解
1)意图
办公常用意图集合(办公 Agent 通用枚举)
EXPORT_FILE(导出文件)、IMPORT_DATA(导入数据)
SEND_MESSAGE(消息推送)、QUERY_DATA(数据查询)
GENERATE_DOC(生成公文/报表)、FILE_UPLOAD(文件上传OA)
TABLE_CALC(表格计算)、NONE(无效/非法指令)
- 单意图:「导出 7 月营收报表」→ 只有 EXPORT_FILE
- 多意图:「导出 7 月营收报表并且发送到部门企微群」→ EXPORT_FILE + SEND_MESSAGE
2)槽位(实体参数)
执行意图必备的变量,没有槽位任务无法执行 通用办公槽位定义:
| 槽位 Key | 含义 | 示例值 |
|---|---|---|
| time_range | 时间范围 | 上个月、2026 年 7 月、Q2 季度 |
| file_name | 文件名称 / 类型 | 营收台账、月度报表、excel |
| target_dept | 目标部门 | 财务部、市场部 |
| send_target | 发送对象 | 企微群、OA 审批人、张三 |
| file_format | 文件格式 | xlsx、pdf、word |
| data_scope | 数据范围 | 全公司、驻马店片区 |
3)拒识判定
识别广告、乱码、无关闲聊、越权指令,直接拦截不进入后续任务规划
4)歧义消解
模糊语句补全:「整理一下表格」结合上下文自动补全 = 整理上月业务表格
方案一:LLM 结构化意图 + 槽位抽取(工程首选,带 Pydantic 强格式约束)
1、原理
使用Pydantic固定返回 JSON 结构体,强制 LLM 必须按照固定格式输出,杜绝自由文本乱输出; 同时在 Prompt 内置意图枚举、槽位规则、拒识规则、多意图拆分逻辑。 支持:本地 Qwen/Llama、OpenAI、DeepSeek、Ollama 全部大模型统一适配。
2、完整代码 + 逐段注释
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
import os
from dotenv import load_dotenv
# 加载环境变量,存放模型密钥、地址
load_dotenv()
# ========== 1、定义标准化输出结构体(强约束,LLM必须按这个格式返回) ==========
class SingleIntent(BaseModel):
intent_name: str = Field(description="意图标识,只能从枚举取值")
intent_desc: str = Field(description="该意图简单中文描述")
class SlotInfo(BaseModel):
slot_key: str = Field(description="槽位字段key")
slot_value: Optional[str] = Field(description="提取到的槽位值,无则为空字符串")
class IntentSlotResult(BaseModel):
intent_list: List[SingleIntent] = Field(description="解析出的意图列表,支持多个意图")
slot_list: List[SlotInfo] = Field(description="所有提取的槽位参数")
is_reject: bool = Field(description="是否需要拒识,true=拒绝执行指令")
reject_reason: str = Field(description="拒识原因,无需拒识填空")
resolve_ambiguity_text: str = Field(description="歧义消解后的标准化用户指令")
# ========== 2、配置大模型通用客户端(兼容Ollama本地模型) ==========
class LLMIntentExtractor:
def __init__(self):
self.client = OpenAI(
base_url=os.getenv("LLM_BASE_URL", "http://localhost:11434/v1"),
api_key=os.getenv("LLM_API_KEY", "dummy-key")
)
self.model_name = os.getenv("LLM_MODEL", "qwen3.5:9b")
# 固定意图枚举池,LLM只能选用这里面的值
self.intent_enum = [
"EXPORT_FILE", "IMPORT_DATA", "SEND_MESSAGE", "QUERY_DATA",
"GENERATE_DOC", "FILE_UPLOAD", "TABLE_CALC", "NONE"
]
# 固定槽位枚举池
self.slot_enum = [
"time_range", "file_name", "target_dept",
"send_target", "file_format", "data_scope"
]
def build_system_prompt(self) -> str:
"""构造系统提示词,写入全部业务规则"""
prompt = f"""
你是办公AI Agent的意图&槽位解析器,严格遵守下面所有规则:
## 固定意图列表(只允许输出这里面的值)
{self.intent_enum}
NONE代表:闲聊、无意义语句、违规指令、无法执行的请求,此时is_reject必须=true
## 固定槽位列表(只允许提取这些key)
{self.slot_enum}
1. time_range:所有时间相关描述
2. file_name:文件名称、报表、台账、文档类型
3. target_dept:部门名称
4. send_target:消息发送的群、人员、系统
5. file_format:导出文件格式xlsx/pdf/word
6. data_scope:数据所属范围片区、分公司
## 强制规则
1. 用户一句话包含多个动作,拆分出多个intent放入intent_list
2. 存在语句模糊、指代不明,在resolve_ambiguity_text做语句补全标准化
3. 提取不到对应槽位时slot_value填空字符串
4. 广告、聊天、骂人、系统控制类指令一律拒绝执行
5. 输出必须严格符合给定的JSON格式,禁止额外解释文字
"""
return prompt
def extract(self, clean_text: str) -> IntentSlotResult:
"""对外统一抽取入口"""
resp = self.client.beta.chat.completions.parse(
model=self.model_name,
messages=[
{"role": "system", "content": self.build_system_prompt()},
{"role": "user", "content": f"用户标准化指令:{clean_text}"}
],
response_format=IntentSlotResult
)
# 解析结构化对象
result = resp.choices[0].message.parsed
return result
# ========== 测试调用(对接第一章预处理结果) ==========
if __name__ == "__main__":
# 模拟上一章预处理输出的干净文本
test_text_1 = "导出2026年7月财务部营收台账,文件格式为xlsx,发送到财务部企业微信群"
test_text_2 = "今天晚饭吃什么,帮我推荐电影"
test_text_3 = "整理一下表格" # 歧义语句
extractor = LLMIntentExtractor()
print("=====测试用例1:正常多意图指令=====")
res1 = extractor.extract(test_text_1)
print(res1.model_dump_json(indent=2))
print("\n=====测试用例2:闲聊内容(拒识)=====")
res2 = extractor.extract(test_text_2)
print(res2.model_dump_json(indent=2))
print("\n=====测试用例3:模糊歧义语句=====")
res3 = extractor.extract(test_text_3)
print(res3.model_dump_json(indent=2))
3、运行结果说明
- 正常业务语句:输出 2 个意图(导出文件 + 发送消息),自动填充全部时间、文件、格式、发送对象槽位
- 闲聊语句:
is_reject=true,上层 Agent 直接终止流程,返回拒绝理由 - 模糊语句:
resolve_ambiguity_text自动补全语义,消除歧义
4、落地优势
- Pydantic + parse 模式,LLM 输出格式 100% 可控,不会出现 JSON 解析报错
- 更换本地 Ollama/Qwen、云端 DeepSeek/OpenAI 只需要改.env 配置,代码无需改动
- 新增意图、新增槽位只需要修改枚举列表,扩展性极强
.env 配置文件示例(同级目录新建.env)
# Ollama本地部署默认地址
LLM_BASE_URL=http://localhost:11434/v1
LLM_API_KEY=dummy-key
LLM_MODEL=qwen3.5:9b
方案二:规则化意图 + 槽位抽取(无模型、纯代码兜底方案)
1、适用场景
简单固定指令、对内标准化办公话术、边缘低配置服务器不想跑大模型;用作 LLM 解析失败时的降级兜底。 原理:关键词匹配判定意图 + 正则表达式捕获时间、文件、格式等实体槽位。
import re
from typing import Dict, List
class RuleIntentSlot:
def __init__(self):
# 意图关键词映射
self.intent_key_map = {
"EXPORT_FILE": ["导出", "下载", "另存为", "输出报表"],
"SEND_MESSAGE": ["发送", "推送", "发到", "转发"],
"GENERATE_DOC": ["生成", "制作", "整理文档", "编写台账"],
"QUERY_DATA": ["查询", "查看", "获取数据", "统计"],
"FILE_UPLOAD": ["上传", "提交OA"],
"TABLE_CALC": ["计算", "求和", "汇总表格"]
}
# 正则规则
self.patterns = {
"time_range": re.compile(r"(20\d{2}年[0-9]{1,2}月|上个月|上季度|Q[1-4]|本周|昨日)"),
"file_format": re.compile(r"xlsx|excel|pdf|word|docx"),
"dept": re.compile(r"(财务部|市场部|人事部|运维部)")
}
# 拒识关键词库
self.reject_words = ["吃饭", "电影", "游戏", "聊天", "解封", "破解"]
def match_intent(self, text: str) -> List[str]:
"""关键词匹配获取所有命中意图"""
hit_intents = []
for intent, keywords in self.intent_key_map.items():
for kw in keywords:
if kw in text:
hit_intents.append(intent)
break
return hit_intents if hit_intents else ["NONE"]
def extract_slot(self, text: str) -> List[Dict]:
slot_res = []
for slot_key, pat in self.patterns.items():
match = pat.search(text)
val = match.group() if match else ""
slot_res.append({"slot_key": slot_key, "slot_value": val})
return slot_res
def check_reject(self, text: str) -> (bool, str):
for word in self.reject_words:
if word in text:
return True, f"命中闲聊词汇:{word},拒绝执行"
return False, ""
def run_all(self, clean_text: str) -> Dict:
intents = self.match_intent(clean_text)
slots = self.extract_slot(clean_text)
is_rej, rej_reason = self.check_reject(clean_text)
return {
"intent_list": intents,
"slot_list": slots,
"is_reject": is_rej,
"reject_reason": rej_reason,
"resolve_ambiguity_text": clean_text
}
# 规则方案测试
if __name__ == "__main__":
rule_parser = RuleIntentSlot()
text = "导出7月财务部报表为pdf格式并发送到工作群"
res = rule_parser.run_all(text)
import json
print(json.dumps(res, ensure_ascii=False, indent=2))
规则方案优缺点总结
- 优点:零推理耗时、无模型依赖、服务稳定,做降级保护绝佳
- 缺点:复杂口语、倒装句识别很差,只能覆盖标准话术
第三节 融合架构:LLM 主解析 + 规则兜底(生产最终封装类)
生产环境不能只依赖 LLM(网络波动、模型离线会直接崩),做双层防护: 优先调用 LLM 抽取 → LLM 异常 / 超时自动切规则解析兜底
from preprocessor import AgentInputPreprocessor # 导入第一章预处理类
class UnifiedIntentSlotParser:
def __init__(self):
self.llm_parser = LLMIntentExtractor()
self.rule_parser = RuleIntentSlot()
self.preprocessor = AgentInputPreprocessor()
def parse_full(self, raw_user_input: str):
# 步骤1:先走第一章预处理清洗文本
pre_data = self.preprocessor.process_text_input(raw_user_input)
clean_txt = pre_data["norm_text"]
try:
# 优先LLM方案
llm_result = self.llm_parser.extract(clean_txt)
return {
"source": "llm",
"preprocess_data": pre_data,
"intent_slot_data": llm_result.model_dump()
}
except Exception as e:
# LLM报错降级到规则方案
print(f"LLM解析异常,切换规则兜底,异常信息:{str(e)}")
rule_result = self.rule_parser.run_all(clean_txt)
return {
"source": "rule_backup",
"preprocess_data": pre_data,
"intent_slot_data": rule_result
}
# 整条链路串联测试(原始输入 → 预处理 → 意图槽位解析)
if __name__ == "__main__":
parser = UnifiedIntentSlotParser()
user_input = "呃导出2026年7月财务部营收台账,输出为xlsx文件发送到财务部企微群"
final_out = parser.parse_full(user_input)
import json
print(json.dumps(final_out, ensure_ascii=False, indent=2))
第四节 模块输出格式 & 向下游交付标准
本模块输出结构体,固定传递给第三章【记忆模块】、第四章【任务规划】
{
"source": "llm/rule_backup",
"preprocess_data": {清洗文本、语种信息},
"intent_slot_data": {
"intent_list": ["EXPORT_FILE", "SEND_MESSAGE"],
"slot_list": [各个参数键值对],
"is_reject": false,
"reject_reason": "",
"resolve_ambiguity_text": "标准化后的用户指令"
}
}
上层拿到数据后:
- is_reject = True:直接回复用户拒绝提示,流程终止
- is_reject = False:把意图 + 槽位送入会话记忆、任务拆解模块
第五节 生产环境优化点
- 给 LLM 调用增加超时时间、重试次数,防止模型卡死
- 自建业务词库,定期扩充意图、槽位关键词
- 增加槽位校验:必要槽位缺失时,Agent 主动追问用户补充信息(对接自省模块)
- 记录每一次意图识别日志,统计识别准确率用于迭代 Prompt
本章整体数据流链路
用户原始输入 → 第一章预处理(清洗/ASR/文件解析)
↓
本章意图槽位解析(LLM主+规则兜底)
↓
校验是否拒识 → 拒识则结束;正常数据下发【记忆管理模块】更多推荐
所有评论(0)