第 2 课:LLM 作为决策引擎(Decision Engine)
你需要掌握:
- 1.1 如何让 LLM 做“推理 + 决策”
- 1.2 Prompt 架构(System Prompt、Role Prompt、Loop Prompt)
- 1.3 如何让 LLM 可控(避免幻觉)
- 1.4 LLM 的三种模式:
- 纯文本聊天模型(OpenAI GPT、Llama)
- 工具调用模型(function-calling 模式)
- 思维链模型(CoT)
- 1.5 决策类 Prompt 的结构化格式:
示例(工程化格式):
Thought:
Action:
Action Input:
- 1.6 如何让模型“只输出 JSON”
- 1.7 如何限制模型不跑偏(Guardrails + Validators)
- 1.8 推理深度控制(max_steps、deliberate_thinking)
(一)如何让 LLM 做“推理 + 决策”
在 Agent 系统里,LLM 不再是“回答问题的聊天机器人”,而是一个 Policy(策略函数):
输入:当前状态 state(含目标、历史、工具可用性、约束) 输出:下一步动作 action(包括 finish / tool_call / ask_user / replan)
后面所有模块(ReAct / Planner / State Machine)都建立在这个认知之上。
工程上,你必须把“决策”变成可解析的结构化输出,而不是自然语言随便说。
决策输出的两种主流形态
- 形态 A:Action JSON(推荐) 适合工程解析、状态机、工具路由
- 形态 B:Thought/Action/Obs(教学友好) 适合调试,但不如 JSON 稳
同时,你还要定义这个 agent 可以做的所有决策和动作,这称为 决策空间
必须把动作空间写进 system prompt, 是可控性的第一步。
典型 Agent 最小动作集合:
finish:输出最终回答tool_call:调用某个工具ask_user:信息不足,向用户提问replan:当前策略失败,重做计划
(二)Prompt 架构
工程上建议你把 prompt 分成三层(System / Role / Loop),并且代码里明确分离,便于维护、A/B test、版本化。
核心目的只有三点:
- 可控性:System 里放“硬规则”,避免模型跑偏。
- 可维护性:Role 变化频繁;Loop 每轮变化更频繁。分层能减少改动面。
- 可实验性:A/B test 只替换某一层(通常是 Role 或 System 的某个片段),其余不动,才能对比出“因果效果”。
1. System Prompt
System 是“宪法”,原则是:只放长期稳定且强约束的内容。
包含:
- 允许的动作集合(action space)
- 输出必须为 JSON(schema)
- 禁止输出推理过程(如果你要)
- 失败时的 fallback(例如:JSON 解析失败如何纠正)
- 工具使用原则(“没有 observation 不得引用工具结果”)
建议你把 System 写成“规格文档式”的 Prompt:
- 明确 schema
- 明确 allowed values
- 明确 error policy
示例(决策引擎 System):
You are an agent decision engine.
Output MUST be exactly one valid JSON object. No markdown, no extra text.
Allowed actions: ["finish","tool_call","ask_user","replan"].
Schema:
{
"action": "...",
"tool": "string|null",
"tool_input": "object|null",
"final": "string|null"
}
Rules:
- Never claim tool results without an Observation.
- If output is not valid JSON, self-correct and output valid JSON only.
- Be concise.
2. Role Prompt
Role 是“岗位说明书”(可变:任务角色与偏好),随场景变化。放:
- 任务类型(网页调研 / 代码生成 / 竞品分析 / 运营文案等)
- 风格偏好(简短、bullet、严格引用等)
- 质量标准(必须给出可执行步骤、必须给出风险提示等)
Role 的关键:同一系统规则下,切换岗位能力。
示例:
- “你是网页调研 agent,必须给出来源链接/引用”
- “你是代码审查 agent,优先指出安全风险”
- “你是数据分析 agent,输出要包含可复现步骤”
例如:
- “你是一个网页调研 agent”
- “你输出要简短”
- “你必须引用来源(如果有 web 工具)”
3. Loop Prompt(每轮动态:状态输入)
Loop 是“当下工单”,每轮都变。放:
- 当前目标(goal)
- 可用工具清单(tools + schema)
- 关键历史 / 摘要(memory summary)
- 预算(max_steps / token / cost)
- 上一次工具 observation / error(用于纠错与 replanning)
Loop 的关键:把 agent 的“状态”显式喂给模型,让它基于状态做决策,而不是凭空发挥。
Loop 内容建议统一用 JSON(对模型和你都更清晰,方便日志与回放):
{
"goal": "...",
"tools_available": [...],
"memory_summary": "...",
"budget": {"max_steps": 6, "max_tool_calls": 3},
"last_observation": "...",
"last_error": null
}
例子
import os, json, requests
from dataclasses import dataclass
API_KEY = os.getenv("COMET_API_KEY")
URL = "https://api.cometapi.com/v1/chat/completions"
if not API_KEY:
raise RuntimeError("COMET_API_KEY not set")
SYSTEM_PROMPT = """
You are an agent decision engine.
Output MUST be exactly one valid JSON object. No markdown, no extra text.
Allowed actions: ["finish","tool_call","ask_user","replan"].
Schema:
{
"action": "finish|tool_call|ask_user|replan",
"tool": "string|null",
"tool_input": "object|null",
"final": "string|null"
}
Rules:
- Never claim tool results without an Observation.
- If you need external info, use tool_call.
- If information is missing and no tool can help, use ask_user with a clear question in final.
- Keep final short.
""".strip()
role_prompt = """
You are a web research agent.
Prefer bullet points.
If you propose using a tool, specify the tool name and tool_input clearly.
""".strip()
loop_state = {
"goal": "Explain what an agent is with one concrete example.",
"tools_available": ["search", "http_get"],
"memory_summary": "User prefers concise bullet points.",
"budget": {"max_steps": 6, "max_tool_calls": 3},
"last_observation": None,
"last_error": None
}
@dataclass
class PromptPack:
system: str
role: str
loop: dict
def call_llm(prompt_pack: PromptPack, model="gpt-4o", max_tokens=220, temperature=0.2):
messages = [ # 解包传入的提示词结构
{"role": "system", "content": prompt_pack.system},
{"role": "system", "content": f"ROLE:\n{prompt_pack.role}".strip()},
{"role": "user", "content": json.dumps(prompt_pack.loop, ensure_ascii=False)}
]
print(type(json.dumps(prompt_pack.loop, ensure_ascii=False)))
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
payload = {"model": model, "messages": messages, "max_tokens": max_tokens, "temperature": temperature}
# 请求
r = requests.post(URL, headers=headers, json=payload, timeout=30)
r.raise_for_status()
print(type(r)) # <class 'requests.models.Response'>
text = r.json()["choices"][0]["message"]["content"] # 格式化
print(type(text)) # str
result = json.loads(text) # 这一步确保str 可以转化成 JSON 格式,否则会报错
print(type(result)) # dict
return result
if __name__ == "__main__":
pack = PromptPack(system=SYSTEM_PROMPT, role=role_prompt, loop=loop_state)
decision = call_llm(pack)
print_json(decision)
输出如下:
{
"action": "tool_call",
"tool": "search",
"tool_input": {
"query": "definition of an agent with example"
},
"final": null
}
思考两个问题
-
@dataclass PromptPack到底是什么?为什么不用原生dict?这段代码定义了一个 对象(class instance ),用内部的三个属性的值来代表具体的 prompt
相比于 dict,对象的 优点在于:
-
可以规定属性数据类型
-
更安全
-
更容易修改,包括属性名和内容
-
更容易拓展和追加新的字段
@dataclass class PromptPack: system: str role: str loop: dict version: str experiment: str | None = None -
“Prompt ≠ JSON”:
PromptPack是你本地的“工程对象”,而不是你发给 LLM 的 payload。你在call_llm()里才把它“展开”为 messages
-
-
为什么有的地方用了
.strip(),有的地方没有.strip()是为了消除去掉 开头和结尾 的 空格,\n,\t,这是因为三引号字符串天然会带一个“首尾空行”例如
"""
hello
"""实际内容是:
\nhello\n某些模型对 prompt 的第一行很敏感(尤其 system prompt)。去掉前导空白是个好习惯。
总之:对“展示给模型看的自然语言 Prompt” → 可以 strip,而对“结构化数据 / JSON / state” → 不要 strip
其他知识
A/B test 是什么
A/B 测试就是:在控制其他变量不变的前提下,只改变一个因素(例如 Role Prompt 的一段话),比较产出指标的差异,从而判断哪个更好。
你可以用同一批
loop_state,跑两套 role prompt:ROLE_A = "You are a web research agent. Be concise." ROLE_B = "You are a web research agent. Be concise. Never fabricate sources." # 对同一批 tasks 分别 call_llm,统计 JSON 合法率、action 分布、是否触发 ask_user 等
如何版本化 Prompt:
Prompt 版本化的目标:
- 任何线上结果可追溯到“当时用的 Prompt”
- 发生质量回退可快速回滚
- 支持 A/B test 与灰度发布
版本化的三种粒度(从轻到重)
- 字符串版本号(最简单)
SYSTEM_PROMPT_VERSION="sys_v1.3.0"- 文件版本化(Git)(推荐) prompt 存到 repo,以文件差异追踪
- Prompt Registry(大系统) 数据库/配置中心存 prompt,带发布流程
推荐的目录结构
prompts/
system/
decision_engine_v1.0.txt
decision_engine_v1.1.txt
roles/
web_research_v1.0.txt
coder_v1.0.txt
loop_templates/
state_schema_v1.0.json