Self-Ask(自问自答)模式
概述
Self-Ask 模式让 Agent 在面对复杂问题时,先不直接回答,而是将问题拆解为一系列子问题,逐步自问自答,最终综合所有子答案形成最终回复。与 Chain of Thought 的单向推理不同,Self-Ask 明确地将问题分解为结构化的问答对。
原理
flowchart TD
用户问题["用户问题:2022年冬奥会在哪举办?<br/>举办国的人口是多少?"]
用户问题 --> 判断{是否需要拆解?}
判断 -->|"Yes"| Q1["Q1: 2022年冬奥会在哪里举办?"]
Q1 --> A1["A1: 中国北京"]
A1 --> Q2["Q2: 中国的人口是多少?"]
Q2 --> A2["A2: 约14亿"]
A2 --> 综合["Step 3: 综合回答<br/>2022年冬奥会在中国北京举办,<br/>中国人口约为14亿"]
核心机制:
- 判断是否拆解:Agent 判断问题是否需要分解
- 生成子问题:将原问题拆分为独立的、可逐个回答的子问题
- 逐一回答:对每个子问题进行回答(可能需要中间结果)
- 综合回答:将子答案合并为最终答案
使用场景
- 多跳问答:需要串联多个事实的问题
- 对比分析:"比较 A 和 B 在 X 方面的差异"
- 条件推理:需要分情况讨论的问题
- 数据聚合:需要多源数据综合的问题
- 逐步计算:多步数学或逻辑计算
示例代码
import json
import re
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass, field
@dataclass
class SubQuestion:
"""子问题"""
id: int
question: str
answer: Optional[str] = None
depends_on: List[int] = field(default_factory=list) # 依赖的子问题 ID
class SelfAskAgent:
"""Self-Ask 模式 Agent 实现"""
def __init__(self, llm, tools: Dict[str, callable] = None):
self.llm = llm
self.tools = tools or {}
def answer(self, question: str) -> str:
"""
使用 Self-Ask 策略回答问题
"""
# Step 1: 分析问题是否需要拆解
decomposition = self._should_decompose(question)
if not decomposition["should_decompose"]:
# 简单问题直接回答
return self._direct_answer(question)
# Step 2: 拆解为子问题
sub_questions = self._decompose(question, decomposition["complexity"])
print(f"拆解为 {len(sub_questions)} 个子问题:")
for sq in sub_questions:
deps = f" (依赖: {sq.depends_on})" if sq.depends_on else ""
print(f" Q{sq.id}: {sq.question}{deps}")
# Step 3: 逐步回答子问题(处理依赖关系)
answered = self._answer_sub_questions(sub_questions)
# Step 4: 综合最终答案
return self._synthesize(question, answered)
def _should_decompose(self, question: str) -> Dict:
"""判断是否需要拆解问题"""
prompt = f"""分析以下问题是否需要拆解为子问题来回答。
问题:{question}
判断标准:
- 需要查询多个独立事实:拆解
- 涉及比较/对比:拆解
- 需要多步计算:拆解
- 包含嵌套问题("A的B的C是什么"):拆解
- 简单的单一事实查询:不拆解
以 JSON 格式返回:
{{
"should_decompose": true/false,
"complexity": "simple|medium|complex",
"reason": "判断理由"
}}
"""
response = self.llm.generate(prompt)
return json.loads(response)
def _decompose(self, question: str, complexity: str) -> List[SubQuestion]:
"""将复杂问题拆解为子问题序列"""
prompt = f"""将以下问题拆解为可独立回答的子问题。
原始问题:{question}
复杂程度:{complexity}
要求:
1. 每个子问题应该是自包含的、可独立回答的
2. 标注子问题之间的依赖关系(哪些子问题需要先于其他回答)
3. 子问题按逻辑顺序排列
以 JSON 格式返回:
[
{{"id": 1, "question": "子问题?", "depends_on": []}},
{{"id": 2, "question": "子问题?", "depends_on": [1]}},
...
]
"""
response = self.llm.generate(prompt)
items = json.loads(response)
return [
SubQuestion(
id=item["id"],
question=item["question"],
depends_on=item.get("depends_on", []),
)
for item in items
]
def _answer_sub_questions(
self, sub_questions: List[SubQuestion]
) -> Dict[int, str]:
"""按依赖顺序回答子问题"""
answers: Dict[int, str] = {}
remaining = list(sub_questions)
while remaining:
# 找到所有依赖已满足的子问题
ready = [
sq for sq in remaining
if all(dep in answers for dep in sq.depends_on)
]
if not ready:
# 存在循环依赖,强制回答所有剩余问题
ready = remaining
for sq in ready:
# 构建上下文(包含依赖问题的答案)
context = ""
if sq.depends_on:
context_parts = []
for dep_id in sq.depends_on:
context_parts.append(
f"已知:{sub_questions[dep_id-1].question}\n"
f"答案:{answers[dep_id]}"
)
context = "\n\n".join(context_parts)
# 回答子问题
answer = self._answer_single(sq.question, context)
sq.answer = answer
answers[sq.id] = answer
print(f" A{sq.id}: {answer[:80]}...")
remaining.remove(sq)
return answers
def _answer_single(self, question: str, context: str = "") -> str:
"""回答单个问题(可使用工具)"""
prompt = f"""请回答以下子问题。
{f'背景知识:{context}' if context else ''}
子问题:{question}
请直接给出准确、简洁的答案。如果问题可以使用工具查询,请使用工具。
"""
return self.llm.generate(prompt)
def _direct_answer(self, question: str) -> str:
"""直接回答问题(不拆解)"""
return self.llm.generate(
f"请回答以下问题:{question}\n\n直接给出答案。"
)
def _synthesize(
self, original_question: str, answers: Dict[int, str]
) -> str:
"""综合子答案生成最终回答"""
qa_pairs = "\n".join([
f"Q: {sq.question}\nA: {answers.get(sq.id, 'N/A')}"
for sq in self._cached_sub_questions
if sq.id in answers
]) if hasattr(self, '_cached_sub_questions') else "\n".join(
f"{k}: {v}" for k, v in sorted(answers.items())
)
prompt = f"""基于以下子问题和答案,综合回答原始问题。
原始问题:{original_question}
子问题与答案:
{qa_pairs}
请生成一个连贯、准确的综合回答。
"""
return self.llm.generate(prompt)
# ========== 带 Follow-up 的 Self-Ask ==========
class FollowUpSelfAskAgent(SelfAskAgent):
"""支持追问澄清的 Self-Ask"""
def _decompose(self, question: str, complexity: str) -> List[SubQuestion]:
sub_questions = super()._decompose(question, complexity)
# 检查是否有需要澄清的子问题
clarified = []
for sq in sub_questions:
if self._needs_clarification(sq.question):
clarified_q = self._clarify(sq.question)
sq.question = clarified_q
clarified.append(sq)
self._cached_sub_questions = clarified
return clarified
def _needs_clarification(self, question: str) -> bool:
"""判断子问题是否需要澄清"""
prompt = f"""判断以下问题是否包含模糊术语需要澄清。
问题:{question}
以 JSON 返回:{{"needs_clarification": true/false}}
"""
response = self.llm.generate(prompt)
return json.loads(response).get("needs_clarification", False)
def _clarify(self, question: str) -> str:
"""澄清模糊问题"""
prompt = f"""以下问题包含模糊术语,请改写为精确的问题。
原问题:{question}
改写后的精确问题(只返回改写后的问题):
"""
return self.llm.generate(prompt)
# ========== 使用示例 ==========
agent = SelfAskAgent(llm=YourLLM())
# 多跳问题
result = agent.answer(
"特斯拉 CEO 的出生城市和国家的人口排名是多少?"
)
print(f"\n最终答案:{result}")
# 对比问题
result = agent.answer(
"Python 和 Rust 在内存管理、性能和生态方面有什么区别?"
)
print(f"\n最终答案:{result}")
拆解策略
1. 顺序拆解(Sequential)
每个子问题依赖前一个的答案:
flowchart TD
Q1s["Q1: X 是什么?"] --> A1s["A1: ..."]
A1s --> Q2s["Q2: X 的 Y 是什么?"]
Q2s --> A2s["A2: ..."]
A2s --> Q3s["Q3: Y 的 Z 是什么?"]
2. 并行拆解(Parallel)
子问题互不依赖,可并行回答:
flowchart TD
Q1p["Q1: A 的价格是多少?"]
Q2p["Q2: B 的性能如何?"]
Q3p["Q3: C 的评价怎样?"]
3. 混合拆解(Hybrid)
部分依赖,部分独立:
flowchart TD
Q1h["Q1: 目的地是哪里?"]
Q1h --> Q2h["Q2: 从当前位置到 Q1 的距离?"]
Q3h["Q3: 最佳交通工具?"]
Q2h --> 最终["结合 Q2 和 Q3 得出答案"]
Q3h --> 最终
优点与局限
| 优点 | 局限 |
|---|---|
| 问题结构化,回答更系统 | 拆解不当时可能引入错误 |
| 子问题可并行处理 | 依赖关系复杂时调度困难 |
| 中间结果可追溯、可验证 | 增加了 LLM 调用次数 |
| 适合处理多跳、对比类问题 | 简单问题使用会增加延迟 |
| 可结合工具调用强化子问题回答 | 子问题间信息传递可能失真 |
评论
评论加载中…