📖 对话系统与Agent化NLP¶
⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。
学习时间:6-8小时 | 难度:⭐⭐⭐⭐ | 前置:10-预训练语言模型、11-大模型时代的NLP
💡 从规则对话到LLM Agent,NLP正在从"理解语言"转向"用语言做事"。
📖 1. 对话系统发展脉络¶
1.1 四代对话系统¶
规则/模板 → 检索式 → 生成式 → 大模型时代
(ELIZA) (IR-based) (Seq2Seq) (ChatGPT/Agent)
1960s 2010s 2016s 2023+
| 代际 | 代表系统 | 核心技术 | 优点 | 缺点 |
|---|---|---|---|---|
| 规则式 | ELIZA/Siri初版 | 模板匹配/有限状态机 | 可控、可预测 | 覆盖有限、不灵活 |
| 检索式 | AliMe/小冰 | TF-IDF/BM25 + 排序 | 回复质量稳定 | 无法生成新内容 |
| 生成式 | Seq2Seq/DialoGPT | RNN→Transformer | 回复多样 | 一致性差、幻觉 |
| 大模型 | ChatGPT/Gemini | RLHF + Tool Use | 通用能力强 | 成本高、可控性挑战 |
1.2 任务型 vs 开放域¶
对话系统
├── 任务型(Task-Oriented)
│ ├── 目标:完成特定任务(订机票/查天气)
│ ├── 架构:NLU → DST → Policy → NLG
│ └── 评估:任务完成率、对话轮次
│
└── 开放域(Open-Domain / Chitchat)
├── 目标:自然闲聊、情感陪伴
├── 架构:端到端生成
└── 评估:困惑度、人工评估、趣味性
📖 2. 任务型对话系统¶
2.1 Pipeline架构¶
用户: "帮我订明天从北京飞上海的机票"
│
┌──────▼──────┐
│ NLU模块 │ 意图:BookFlight
│ (自然语言理解)│ 槽位:出发=北京, 目的=上海, 日期=明天
└──────┬──────┘
│
┌──────▼──────┐
│ DST模块 │ 对话状态:{intent: BookFlight,
│ (对话状态追踪)│ slots: {from: 北京, to: 上海, date: 明天,
└──────┬──────┘ time: null, class: null}}
│
┌──────▼──────┐
│ Policy模块 │ 动作:request(time) - 询问时间偏好
│ (对话策略) │
└──────┬──────┘
│
┌──────▼──────┐
│ NLG模块 │ 生成:"请问您偏好上午还是下午的航班?"
│ (自然语言生成)│
└─────────────┘
2.2 意图识别与槽填充(NLU)¶
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer
class JointIntentSlotModel(nn.Module): # 继承nn.Module定义网络层
"""
联合意图识别 + 槽填充模型
意图识别: [CLS] → 多分类
槽填充: 每个token → BIO标注
"""
def __init__(self, bert_name='bert-base-chinese',
num_intents=10, num_slot_labels=30):
super().__init__() # super()调用父类方法
self.bert = BertModel.from_pretrained(bert_name)
hidden_size = self.bert.config.hidden_size
# 意图分类器(用[CLS]表示)
self.intent_classifier = nn.Sequential(
nn.Dropout(0.1),
nn.Linear(hidden_size, num_intents)
)
# 槽填充分类器(每个token)
self.slot_classifier = nn.Sequential(
nn.Dropout(0.1),
nn.Linear(hidden_size, num_slot_labels)
)
def forward(self, input_ids, attention_mask):
outputs = self.bert(input_ids, attention_mask=attention_mask)
# 意图: [CLS] token
cls_output = outputs.last_hidden_state[:, 0, :]
intent_logits = self.intent_classifier(cls_output)
# 槽位: 所有token
sequence_output = outputs.last_hidden_state
slot_logits = self.slot_classifier(sequence_output)
return intent_logits, slot_logits
# 使用示例
model = JointIntentSlotModel(num_intents=5, num_slot_labels=15)
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
text = "帮我订明天从北京飞上海的机票"
inputs = tokenizer(text, return_tensors='pt', padding=True)
intent_logits, slot_logits = model(inputs['input_ids'], inputs['attention_mask'])
intent_labels = ['BookFlight', 'BookHotel', 'Weather', 'Music', 'Other']
pred_intent = intent_labels[intent_logits.argmax(-1).item()] # 将单元素张量转为Python数值
print(f"意图: {pred_intent}")
print(f"槽位标签数: {slot_logits.shape}") # (1, seq_len, num_slot_labels)
2.3 对话状态追踪(DST)¶
class DialogueStateTracker:
"""
规则+模型混合的DST
维护对话状态 = {domain: {slot: value}}
"""
def __init__(self, domains):
self.state = {}
for domain in domains:
self.state[domain] = {}
self.history = []
def update(self, turn_nlu_result):
"""
根据NLU结果更新状态
Args:
turn_nlu_result: {
'intent': str,
'slots': {slot_name: slot_value},
'domain': str
}
"""
domain = turn_nlu_result['domain']
for slot, value in turn_nlu_result['slots'].items():
if value is not None:
self.state[domain][slot] = value
self.history.append({
'nlu': turn_nlu_result,
'state': self._copy_state()
})
def get_unfilled_slots(self, domain, required_slots):
"""检查必填槽位是否都已填充"""
filled = set(self.state.get(domain, {}).keys())
unfilled = [s for s in required_slots if s not in filled]
return unfilled
def _copy_state(self):
import copy
return copy.deepcopy(self.state)
def __repr__(self):
return f"DialogueState: {self.state}"
# 示例对话
tracker = DialogueStateTracker(domains=['flight', 'hotel'])
required_flight_slots = ['from_city', 'to_city', 'date', 'time']
# 第1轮
tracker.update({
'intent': 'BookFlight', 'domain': 'flight',
'slots': {'from_city': '北京', 'to_city': '上海', 'date': '明天'}
})
unfilled = tracker.get_unfilled_slots('flight', required_flight_slots)
print(f"轮次1状态: {tracker}")
print(f"未填槽位: {unfilled}") # ['time']
# 第2轮
tracker.update({
'intent': 'BookFlight', 'domain': 'flight',
'slots': {'time': '上午'}
})
unfilled = tracker.get_unfilled_slots('flight', required_flight_slots)
print(f"轮次2状态: {tracker}")
print(f"未填槽位: {unfilled}") # []
📖 3. 开放域对话¶
3.1 关键挑战¶
| 挑战 | 说明 | 解决方案 |
|---|---|---|
| 一致性 | 前后矛盾("我是学生"后说"我工作了10年") | PersonaChat人设对话 |
| 安全性 | 生成不当/有害内容 | RLHF + 安全过滤 |
| 知识性 | 事实错误/幻觉 | RAG增强 + Internet接入 |
| 共情 | 缺乏情感理解 | EmpatheticDialogues训练 |
| 长期记忆 | 遗忘之前的对话 | MemoryBank / 摘要压缩 |
3.2 PersonaChat与有人设对话¶
def build_persona_prompt(persona_facts, dialogue_history, user_input):
"""
构建带人设的对话prompt
persona_facts: 角色设定
dialogue_history: 历史对话
user_input: 用户最新输入
"""
prompt = "你是一个AI助手,以下是你的人设:\n"
for fact in persona_facts:
prompt += f"- {fact}\n"
prompt += "\n请始终保持人设一致。如果用户的问题超出你的人设,委婉地引导回相关话题。\n\n"
prompt += "对话历史:\n"
for role, text in dialogue_history:
prompt += f"{role}: {text}\n"
prompt += f"用户: {user_input}\n助手: "
return prompt
# 示例
persona = [
"我叫小明,25岁",
"我是一名AI算法工程师",
"我喜欢打篮球和看科幻电影",
"我养了一只叫Cookie的猫"
]
history = [
("用户", "你好啊,你是做什么工作的?"),
("助手", "你好!我是一名AI算法工程师,主要做NLP相关的工作。")
]
prompt = build_persona_prompt(persona, history, "你平时有什么爱好?")
print(prompt)
📖 4. 多轮对话管理¶
4.1 上下文追踪¶
class ConversationManager:
"""
多轮对话管理器
功能:
1. 上下文窗口管理(滑动窗口/摘要压缩)
2. 共指消解("它"→指代什么?)
3. 话题追踪与切换检测
"""
def __init__(self, max_turns=20, summary_threshold=10):
self.turns = []
self.max_turns = max_turns
self.summary_threshold = summary_threshold
self.summary = ""
self.current_topic = None
self.entities = {} # 实体追踪 {mention: entity}
def add_turn(self, role, content, entities=None):
"""添加一轮对话"""
self.turns.append({
'role': role,
'content': content,
'entities': entities or {}
})
# 更新实体追踪
if entities:
self.entities.update(entities)
# 如果超过阈值,压缩历史
if len(self.turns) > self.summary_threshold:
self._compress_history()
def resolve_coreference(self, text):
"""
简易共指消解
将代词替换为最近提到的实体
"""
pronouns = {'它': None, '他': None, '她': None, '这个': None, '那个': None}
# 查找最近的实体
for turn in reversed(self.turns):
for mention, entity in turn.get('entities', {}).items():
if pronouns.get('它') is None and entity.get('type') == 'thing':
pronouns['它'] = mention
pronouns['这个'] = mention
if pronouns.get('他') is None and entity.get('type') == 'person_male':
pronouns['他'] = mention
# 替换
resolved = text
for pronoun, replacement in pronouns.items():
if replacement and pronoun in resolved:
resolved = resolved.replace(pronoun, replacement)
return resolved
def detect_topic_switch(self, new_utterance):
"""检测话题切换(简化版:基于关键词重叠)"""
if not self.turns:
return True
last_content = self.turns[-1]['content'] # [-1]负索引取最后元素
last_words = set(last_content)
new_words = set(new_utterance)
overlap = len(last_words & new_words) / max(len(last_words | new_words), 1)
return overlap < 0.1 # 重叠极少 → 话题切换
def _compress_history(self):
"""将较早的对话压缩为摘要"""
# 保留最近的几轮,早期的压缩为摘要
keep_recent = 5
to_compress = self.turns[:-keep_recent]
# 简化版摘要(实际应用中用LLM生成摘要)
self.summary += "\n".join(
f"{t['role']}: {t['content'][:50]}..." for t in to_compress # 切片操作,取前n个元素
)
self.turns = self.turns[-keep_recent:]
def get_context(self):
"""获取当前上下文(摘要 + 近几轮)"""
context = ""
if self.summary:
context += f"[早期对话摘要]\n{self.summary}\n\n"
context += "[近期对话]\n"
for turn in self.turns:
context += f"{turn['role']}: {turn['content']}\n"
return context
# 使用示例
mgr = ConversationManager()
mgr.add_turn("用户", "我想了解一下BERT模型",
{"BERT": {"type": "thing"}})
mgr.add_turn("助手", "BERT是Google提出的预训练语言模型,使用MLM和NSP任务预训练。")
mgr.add_turn("用户", "它和GPT有什么区别?")
# 共指消解
resolved = mgr.resolve_coreference("它和GPT有什么区别?")
print(f"消解后: {resolved}") # "BERT和GPT有什么区别?"
print(f"\n当前上下文:\n{mgr.get_context()}")
4.2 消歧策略¶
def disambiguate(user_input, candidates, context):
"""
多轮对话中的消歧
场景:用户说"苹果",是水果还是公司?
"""
# 策略1:上下文推断
tech_keywords = ['手机', '电脑', 'Mac', 'iOS', '发布会', '股价']
fruit_keywords = ['吃', '水果', '甜', '红色', '超市', '好吃']
context_words = set(context.lower().split())
tech_score = len(context_words & set(tech_keywords))
fruit_score = len(context_words & set(fruit_keywords))
if tech_score > fruit_score:
return "Apple Inc. (苹果公司)"
elif fruit_score > tech_score:
return "苹果 (水果)"
else:
# 策略2:主动询问
return "ASK: 您说的苹果是指苹果公司还是水果呢?"
# 示例
result = disambiguate("苹果", [], "我想买个新手机")
print(f"消歧结果: {result}") # Apple Inc.
📖 5. Tool-Use范式¶
5.1 Why Tool Use?¶
LLM的固有局限: - 数学计算:\(23 \times 47\) 可能算错 - 实时信息:不知道今天的天气/股价 - 数据查询:无法访问私有数据库 - 精确操作:不能真正发邮件/预订
Tool Use = 让LLM决定何时调用什么工具,用结果增强回答
5.2 Tool Use架构¶
用户问题 → LLM思考 → 是否需要工具?
│
┌───────▼────────┐
│ 不需要 │ 需要
│ 直接回答 │ 生成工具调用
└────────────────┘
│
┌───────────▼───────────┐
│ 解析工具名+参数 │
│ tool: calculator │
│ args: {"expr": "23*47"}│
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ 执行工具,获取结果 │
│ result: 1081 │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ LLM整合结果,生成回答 │
│ "23×47 = 1081" │
└───────────────────────┘
5.3 Function Calling实现¶
import json
# 工具定义
TOOLS = [
{
"type": "function",
"function": {
"name": "search_web",
"description": "搜索互联网获取实时信息",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculator",
"description": "执行数学计算",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式"}
},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "query_database",
"description": "查询SQL数据库",
"parameters": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "SQL查询语句"},
"database": {"type": "string", "description": "数据库名"}
},
"required": ["sql", "database"]
}
}
}
]
# 工具执行器
class ToolExecutor:
def __init__(self):
self.tools = {
"search_web": self._search_web,
"calculator": self._calculator,
"query_database": self._query_database,
}
def execute(self, tool_name, arguments):
if tool_name not in self.tools:
return {"error": f"Unknown tool: {tool_name}"}
return self.tools[tool_name](**arguments)
def _search_web(self, query):
# 实际调用搜索API
return {"results": [f"搜索 '{query}' 的结果..."]}
def _calculator(self, expression):
try: # try/except捕获异常
# 安全的数学计算(实际应用使用更安全的eval)
result = eval(expression, {"__builtins__": {}}, {})
return {"result": result}
except Exception as e:
return {"error": str(e)}
def _query_database(self, sql, database):
return {"result": f"执行 {sql} on {database}"}
# 模拟完整的Tool Use循环
def tool_use_loop(user_query, tools_spec, executor, max_iterations=5):
"""
Tool Use主循环
1. LLM判断是否需要工具
2. 如需要,执行工具
3. 将结果反馈给LLM
4. LLM给出最终回答
"""
messages = [{"role": "user", "content": user_query}]
for i in range(max_iterations):
# 模拟LLM响应(实际调用API)
# response = llm.chat(messages, tools=tools_spec)
# 模拟:LLM决定调用calculator
if "计算" in user_query or any(c.isdigit() for c in user_query): # any()任一为True则返回True
tool_call = {
"name": "calculator",
"arguments": {"expression": "23 * 47"}
}
result = executor.execute(tool_call["name"], tool_call["arguments"])
messages.append({
"role": "tool",
"name": tool_call["name"],
"content": json.dumps(result) # json.dumps将Python对象序列化为JSON字符串
})
print(f"工具调用: {tool_call['name']}({tool_call['arguments']})")
print(f"工具结果: {result}")
# 最终回答
final_answer = f"23 × 47 = {result['result']}"
print(f"最终回答: {final_answer}")
return final_answer
else:
return "直接回答(不需要工具)"
executor = ToolExecutor()
tool_use_loop("23乘以47等于多少?", TOOLS, executor)
5.4 ReAct模式(Reasoning + Acting)¶
REACT_PROMPT = """你是一个能使用工具的AI助手。请按以下格式回答:
Thought: 分析用户问题,思考是否需要工具
Action: tool_name(args) 或 FINISH
Observation: 工具返回结果(由系统填入)
... (可以重复多次)
Thought: 根据所有信息,给出最终答案
Action: FINISH
Answer: 最终回答
可用工具:
- search_web(query): 搜索网页
- calculator(expression): 数学计算
- query_database(sql, database): 查询数据库
用户问题: {question}
"""
def simulate_react(question):
"""模拟ReAct推理过程"""
print(f"问题: {question}\n")
steps = [
("Thought", "用户想知道计算结果,我需要用计算器工具"),
("Action", "calculator(expression='(365 * 24 * 3600)')"),
("Observation", "{'result': 31536000}"),
("Thought", "计算完成,一年有31536000秒"),
("Action", "FINISH"),
("Answer", "一年有 31,536,000 秒(365天 × 24小时 × 3600秒)")
]
for step_type, content in steps:
print(f"{step_type}: {content}")
simulate_react("一年有多少秒?")
📖 6. 对话评估方法¶
6.1 自动评估指标¶
| 指标 | 适用场景 | 公式/说明 | 局限性 |
|---|---|---|---|
| Perplexity | 语言模型质量 | \(PPL = \exp(-\frac{1}{N}\sum \log p(w_i))\) | 低PPL不等于好对话 |
| BLEU | 参考答案匹配 | n-gram精确率 | 一对多问题严重 |
| ROUGE | 参考答案匹配 | n-gram召回率 | 同上 |
| BERTScore | 语义相似度 | BERT embedding余弦相似度 | 不评估对话质量 |
| Distinct-n | 多样性 | \(\frac{\lvert unique\ n\text{-}grams \rvert}{\lvert total\ n\text{-}grams \rvert}\) | 多样≠高质量 |
6.2 LLM-as-Judge¶
JUDGE_PROMPT = """请评估以下对话回复的质量,从1-5分打分。
评分维度:
1. 相关性(1-5):回复是否切题
2. 信息量(1-5):回复是否提供有用信息
3. 一致性(1-5):是否与对话历史一致
4. 安全性(1-5):是否有不当内容
5. 流畅度(1-5):语言是否自然流畅
对话上下文:
{context}
待评估回复:
{response}
请以JSON格式输出评分和理由:
{{
"relevance": {{"score": X, "reason": "..."}},
"informativeness": {{"score": X, "reason": "..."}},
"consistency": {{"score": X, "reason": "..."}},
"safety": {{"score": X, "reason": "..."}},
"fluency": {{"score": X, "reason": "..."}},
"overall": X
}}
"""
def evaluate_response(context, response):
"""使用GPT-4作为裁判评估对话质量"""
prompt = JUDGE_PROMPT.format(context=context, response=response)
# actual: response = openai.chat(prompt)
print(f"评估prompt长度: {len(prompt)} 字符")
print("实际应用中调用GPT-4 API进行评估")
return {"overall": 4.2} # 示例返回
evaluate_response(
"用户: BERT和GPT有什么区别?",
"BERT是双向编码器,GPT是单向解码器。BERT擅长理解任务,GPT擅长生成任务。"
)
6.3 人工评估框架¶
## 人工评估维度
### A/B测试(对比评估)
- 同一问题,展示两个模型的回复
- 评估员选择更好的一个(或平手)
- 需要100+样本才有统计显著性
### Likert量表(绝对评估)
- 1分:完全无用/错误
- 2分:部分有用但有明显问题
- 3分:基本可用
- 4分:好的回复,小瑕疵
- 5分:优秀,无可挑剔
### 评估注意事项
- 评估员间一致性(Cohen's Kappa > 0.6)
- 样本多样性(覆盖不同话题/难度)
- 双盲评估(不知道哪个是哪个模型)
📖 7. NLP Agent:让语言模型做事¶
7.1 NLP任务中的Agent应用¶
NLP_AGENT_TASKS = {
"自动摘要Agent": {
"描述": "自动提取长文档关键信息",
"工具": ["文本分段", "关键句提取", "摘要生成", "事实验证"],
"流程": "分段 → 每段提取关键信息 → 合并 → 验证事实 → 输出"
},
"信息抽取Agent": {
"描述": "从非结构化文本提取结构化信息",
"工具": ["NER识别", "关系抽取", "事件抽取", "知识图谱查询"],
"流程": "识别实体 → 判断关系 → 验证一致性 → 写入知识库"
},
"多语言翻译Agent": {
"描述": "处理复杂的多语言翻译任务",
"工具": ["语言检测", "翻译API", "术语库查询", "质量评估"],
"流程": "检测语言 → 分句 → 检查术语 → 翻译 → 质量评估 → 后编辑"
},
"文档QA Agent": {
"描述": "基于大量文档回答问题",
"工具": ["检索器", "阅读理解", "计算器", "表格解析"],
"流程": "理解问题 → 检索相关段落 → 阅读理解 → 验证答案 → 回答"
}
}
for name, info in NLP_AGENT_TASKS.items():
print(f"\n📋 {name}")
print(f" 描述: {info['描述']}")
print(f" 工具: {', '.join(info['工具'])}")
print(f" 流程: {info['流程']}")
7.2 HuggingGPT/TaskMatrix思路¶
用户请求: "帮我把这段英文翻译成中文,然后做一个情感分析"
LLM Controller (规划)
│
├─ Step 1: 调用翻译模型 (Helsinki-NLP/opus-mt-en-zh)
│ └─ 输入: "I love this product..." → 输出: "我很喜欢这个产品..."
│
├─ Step 2: 调用情感分析模型 (uer/roberta-base-finetuned-jd-binary-chinese)
│ └─ 输入: "我很喜欢这个产品..." → 输出: {positive: 0.95}
│
└─ Final: LLM整合两步结果,生成最终回答
7.3 MCP(Model Context Protocol)在NLP中的应用¶
# MCP工具定义示例
MCP_NLP_TOOLS = {
"text_analysis": {
"name": "text-analysis",
"description": "分析文本的情感、实体、关键词",
"inputSchema": {
"type": "object",
"properties": {
"text": {"type": "string"},
"tasks": {
"type": "array",
"items": {"enum": ["sentiment", "ner", "keywords", "summary"]},
"description": "要执行的分析任务"
}
},
"required": ["text", "tasks"]
}
},
"knowledge_base": {
"name": "knowledge-base-query",
"description": "查询知识库获取背景信息",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"top_k": {"type": "integer", "default": 5}
},
"required": ["query"]
}
}
}
print("MCP NLP工具定义:")
for tool_id, tool_def in MCP_NLP_TOOLS.items():
print(f" {tool_def['name']}: {tool_def['description']}")
📖 8. 从对话到多模态Agent¶
8.1 2024-2025趋势¶
## 对话系统未来方向
1. **多模态对话**
- 文本+图片+语音+视频的融合理解
- GPT-4o: 原生多模态,实时语音对话
- Gemini: 多模态长上下文理解
2. **长期记忆**
- MemGPT: 类操作系统的记忆管理
- 对话摘要 + 向量检索 + 结构化记忆
3. **个性化**
- 用户画像建模
- 偏好学习(对话风格/知识深度/语言习惯)
- Constitutional AI: 用户自定义AI行为准则
4. **可信对话**
- 引用来源(grounded generation)
- 不确定性表达("我不确定,但...")
- 拒绝回答的能力(知道自己不知道)
5. **主动对话**
- 不只是被动回答,主动提问/建议
- 预判用户需求
- 个性化推荐式对话
🎯 面试高频题¶
Q1: 任务型对话和开放域对话的主要区别?¶
A: 任务型对话有明确目标(订票/查询),通过NLU→DST→Policy→NLG的pipeline完成特定任务,评估指标是任务完成率。开放域对话没有特定目标,重点是自然流畅地聊天,评估依赖人工或LLM判断。现在大模型模糊了两者界限——ChatGPT既能闲聊也能做任务。
Q2: DST(对话状态追踪)是做什么的?为什么重要?¶
A: DST维护整个对话过程中的状态信息(已识别的意图和槽位值),类似"购物车"。每轮对话后更新状态,决定需要追问什么。它是任务型对话的核心,DST错误会级联传播——状态错了,后续策略和回复都会错。现在用BERT/LLM做end-to-end DST效果好很多。
Q3: Tool Use/Function Calling是怎么工作的?¶
A: LLM在生成回复时,可以选择调用预定义的外部工具(搜索/计算/API)。训练时让模型学会在合适时机输出特殊格式的工具调用指令,系统解析并执行后,将结果拼接到上下文中让模型继续生成。关键是工具描述要清晰,让模型知道何时该用什么工具。
Q4: ReAct和直接Tool Use有什么区别?¶
A: 直接Tool Use是one-shot调用一个工具。ReAct(Reasoning + Acting)是多步推理,每步先Thought(分析当前情况)→Action(调用工具或输出)→Observation(观察结果),可以链式调用多个工具来解决复杂问题。ReAct的优势是推理过程透明可解释。
Q5: 如何评估对话系统?自动指标够用吗?¶
A: 自动指标(BLEU/ROUGE/PPL)严重不足——BLEU和ROUGE假设有标准答案,但对话是一对多的;PPL只衡量流畅度不衡量质量。当前最佳实践:自动指标初筛 + LLM-as-Judge(如GPT-4评分) + 小规模人工评估。A/B测试是金标准但成本高。
Q6: NLP Agent和传统NLP pipeline有什么区别?¶
A: 传统NLP pipeline是固定流程(分词→NER→关系抽取),每步模型各管各。NLP Agent用LLM作为控制器,动态规划执行路径:根据任务自动选择要调用的NLP工具、决定执行顺序、处理中间错误。Agent更灵活,能处理传统pipeline无法覆盖的组合型任务。
✅ 学习检查清单¶
- 能画出任务型对话系统的pipeline架构
- 能实现联合意图识别+槽填充模型
- 理解DST的作用并能实现简易状态追踪
- 能说清楚Tool Use/Function Calling的工作原理
- 能用ReAct模式组织多步推理
- 知道对话评估的3个层次(自动/LLM-Judge/人工)
- 了解MCP和HuggingGPT等Agent化NLP方案
- 能对比任务型和开放域对话的优缺点
📌 下一步:学习 第14章 RAG系统设计 了解如何给对话系统接入知识库,或学习 LLM应用/07-Agent开发基础 深入Agent架构。