链式调用原理
单次 LLM 调用的能力是有限的。链式调用 (Prompt Chaining) 将复杂任务分解为多个步骤,每步的输出作为下一步的输入,形成流水线:
[用户输入] → [Step 1: 理解意图] → [Step 2: 检索知识] → [Step 3: 生成答案] → [最终输出]
核心假设:分解后的子任务更简单、更可靠,错误隔离更容易。
链式架构模式
1. 顺序链 (Sequential Chain)
最简单的模式——线性传递:
from typing import Dict, Any
import openai
def llm_call(prompt: str, model: str = "gpt-4") -> str:
resp = openai.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
)
return resp.choices[0].message.content
class SequentialChain:
def __init__(self):
self.steps = []
def add_step(self, name: str, prompt_template: str):
"""prompt_template 中用 {prev_output} 引用上一步输出"""
self.steps.append({"name": name, "template": prompt_template})
return self
def run(self, initial_input: str) -> Dict[str, str]:
results = {}
current_output = initial_input
for step in self.steps:
prompt = step["template"].format(prev_output=current_output)
current_output = llm_call(prompt)
results[step["name"]] = current_output
print(f"[{step['name']}] 完成")
results["final"] = current_output
return results
# 示例:技术文档翻译流水线
chain = SequentialChain()
chain.add_step("extract_keypoints",
"分析以下技术文档,提取关键技术概念和术语(JSON数组格式):\n{prev_output}")
chain.add_step("translate",
"将以下内容翻译为中文,保持技术术语准确:\n{prev_output}")
chain.add_step("review",
"审校以下翻译,修正不准确之处,输出最终版本:\n{prev_output}")
2. 并行链 (Parallel Chain)
多个独立子任务并行执行后汇总:
import asyncio
import aiohttp
async def async_llm_call(prompt: str, model: str = "gpt-4") -> str:
async with aiohttp.ClientSession() as session:
# 实际使用 openai async client
resp = await openai.AsyncOpenAI().chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
class ParallelChain:
def __init__(self):
self.branches = []
def add_branch(self, name: str, prompt_template: str):
self.branches.append({"name": name, "template": prompt_template})
return self
async def run(self, input_text: str) -> Dict[str, str]:
tasks = []
for branch in self.branches:
prompt = branch["template"].format(input=input_text)
tasks.append(self._run_branch(branch["name"], prompt))
results = await asyncio.gather(*tasks)
return dict(results)
async def _run_branch(self, name, prompt):
output = await async_llm_call(prompt)
return (name, output)
# 示例:多角度分析
async def analyze_article(article: str):
chain = ParallelChain()
chain.add_branch("tech", "从技术角度分析:{input}")
chain.add_branch("business", "从商业角度分析:{input}")
chain.add_branch("risk", "从风险角度分析:{input}")
results = await chain.run(article)
# 汇总
summary = llm_call(
f"综合以下三个角度的分析,给出总结:\n"
f"技术:{results['tech']}\n"
f"商业:{results['business']}\n"
f"风险:{results['risk']}"
)
return summary
3. 条件链 (Conditional Chain)
根据中间结果动态选择下一步:
class ConditionalChain:
def __init__(self):
self.routes = {}
def add_route(self, condition_name: str,
condition_fn, prompt_template: str):
self.routes[condition_name] = {
"fn": condition_fn,
"template": prompt_template,
}
def run(self, input_text: str) -> str:
# Step 1: 分类
classification = llm_call(
f"将以下问题分类为 'code', 'math', 'general' 之一,只输出类别名:\n{input_text}"
).strip()
# Step 2: 根据类别路由
if classification == "code":
return llm_call(f"作为编程专家回答:\n{input_text}")
elif classification == "math":
return llm_call(f"逐步推理以下数学问题:\n{input_text}")
else:
return llm_call(f"回答以下问题:\n{input_text}")
4. Map-Reduce 模式
处理长文档或批量数据的标准模式:
class MapReduceChain:
def __init__(self, chunk_size: int = 3000):
self.chunk_size = chunk_size
def _chunk_text(self, text: str) -> list[str]:
words = text.split()
chunks = []
current = []
current_len = 0
for w in words:
if current_len + len(w) > self.chunk_size:
chunks.append(" ".join(current))
current = [w]
current_len = len(w)
else:
current.append(w)
current_len += len(w)
if current:
chunks.append(" ".join(current))
return chunks
def run(self, text: str, map_prompt: str, reduce_prompt: str) -> str:
chunks = self._chunk_text(text)
print(f"分割为 {len(chunks)} 个块")
# Map: 并行处理每个块
map_results = []
for i, chunk in enumerate(chunks):
result = llm_call(map_prompt.format(chunk=chunk))
map_results.append(result)
print(f" [Map {i+1}/{len(chunks)}] 完成")
# Reduce: 合并结果
combined = "\n---\n".join(map_results)
final = llm_call(reduce_prompt.format(intermediate=combined))
return final
# 示例:长文档摘要
mrc = MapReduceChain(chunk_size=3000)
summary = mrc.run(
long_document,
map_prompt="用 200 字摘要以下文本的核心内容:\n{chunk}",
reduce_prompt="将以下各段摘要整合为一份连贯的总体摘要(500字以内):\n{intermediate}"
)
LangChain Pipeline 实现
from langchain.chains import LLMChain, SequentialChain
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
llm = ChatOpenAI(model="gpt-4", temperature=0.3)
# 定义各步骤
extract_prompt = PromptTemplate(
input_variables=["document"],
template="从以下文档中提取所有实体和关系(JSON格式):\n{document}"
)
extract_chain = LLMChain(llm=llm, prompt=extract_prompt, output_key="entities")
verify_prompt = PromptTemplate(
input_variables=["entities"],
template="验证以下提取的实体是否准确,修正错误:\n{entities}"
)
verify_chain = LLMChain(llm=llm, prompt=verify_prompt, output_key="verified")
format_prompt = PromptTemplate(
input_variables=["verified"],
template="将以下实体信息格式化为 Markdown 表格:\n{verified}"
)
format_chain = LLMChain(llm=llm, prompt=format_prompt, output_key="formatted")
# 组合流水线
pipeline = SequentialChain(
chains=[extract_chain, verify_chain, format_chain],
input_variables=["document"],
output_variables=["formatted"],
verbose=True,
)
result = pipeline({"document": raw_text})
错误处理与断点续传
生产环境必须处理 LLM 调用失败的情况:
import json
import os
import time
from dataclasses import dataclass, asdict
@dataclass
class Checkpoint:
step_name: str
step_index: int
input_data: str
output_data: str
timestamp: str
class ResilientChain:
def __init__(self, checkpoint_dir: str = ".checkpoints"):
self.checkpoint_dir = checkpoint_dir
os.makedirs(checkpoint_dir, exist_ok=True)
self.steps = []
def add_step(self, name, prompt_template, max_retries=3):
self.steps.append({
"name": name,
"template": prompt_template,
"max_retries": max_retries,
})
return self
def _save_checkpoint(self, run_id: str, step_idx: int,
step_name: str, output: str):
cp = Checkpoint(
step_name=step_name,
step_index=step_idx,
input_data="",
output_data=output,
timestamp=time.strftime("%Y-%m-%d %H:%M:%S"),
)
path = os.path.join(self.checkpoint_dir, f"{run_id}_{step_idx}.json")
with open(path, "w", encoding="utf-8") as f:
json.dump(asdict(cp), f, ensure_ascii=False, indent=2)
def _load_checkpoint(self, run_id: str) -> int:
"""返回上次完成的步骤索引,无则返回 -1"""
for i in range(len(self.steps) - 1, -1, -1):
path = os.path.join(self.checkpoint_dir, f"{run_id}_{i}.json")
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return i, data["output_data"]
return -1, None
def run(self, initial_input: str, run_id: str = None) -> str:
if run_id is None:
run_id = str(int(time.time()))
# 尝试从断点恢复
last_step, saved_output = self._load_checkpoint(run_id)
if last_step >= 0:
print(f"从步骤 {last_step + 1} 恢复 (run_id={run_id})")
current_output = saved_output
start_step = last_step + 1
else:
current_output = initial_input
start_step = 0
# 执行剩余步骤
for i in range(start_step, len(self.steps)):
step = self.steps[i]
print(f"[{i+1}/{len(self.steps)}] 执行: {step['name']}")
for attempt in range(step["max_retries"]):
try:
prompt = step["template"].format(prev_output=current_output)
current_output = llm_call(prompt)
self._save_checkpoint(run_id, i, step["name"], current_output)
break
except Exception as e:
print(f" 尝试 {attempt+1} 失败: {e}")
if attempt == step["max_retries"] - 1:
raise
time.sleep(2 ** attempt) # 指数退避
return current_output
模式选择决策矩阵
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| 线性处理流程 | 顺序链 | 每步依赖前步,简单可靠 |
| 多角度分析 | 并行链 | 子任务独立,可并行加速 |
| 多类型问题 | 条件链 | 不同类型需不同处理策略 |
| 长文档处理 | Map-Reduce | 超出上下文窗口必须分块 |
| 多步推理 | 顺序链 + SC | 每步用 Self-Consistency 提升可靠性 |
| 实时对话 | 短链/单步 | 延迟敏感,减少链长度 |
实战建议
- 每步设定明确输出格式:JSON 或结构化文本,避免下游解析失败
- 监控每步延迟:链总延迟 = Σ 各步延迟,及时优化瓶颈
- 设置全局超时:避免单步卡死导致整个链超时
- 可观测性:记录每步输入输出,便于调试和优化
- 版本化 prompt 模板:链中任一 prompt 变更都可能影响全局效果
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
