LLM / Agentic Systems
大语言模型 LLM 与 Agent:从下一个 token 到可验证行动闭环
LLM 本质上建模条件 token 分布;Agent 则把语言模型、检索、工具调用、状态更新和验证器组合成可审计的任务系统。
Mechanism Lab
动画:LLM 如何变成带检索、工具和验证的 Agent 闭环
动画从 prompt context 进入 token logits,展示模型先预测文本/动作;随后接入 RAG memory、tool schema、execution trace 和 verifier,把一次生成变成可复查的循环。
Step 1 / 5
Context
任务、系统约束、历史记录和检索材料共同进入 context window。
C=[task,system,history,docs]Animation Control
Reduced-motion users receive the same step states without continuous motion.
01 / 直觉
核心直觉
LLM 不是直接“理解世界”的程序,而是在上下文 C 下对下一个 token 建模:p(x_t | x_{<t}, C)。能力来自大规模预训练、指令微调、偏好优化和上下文学习。
Prompt、system message、历史记录、检索文档和工具返回值都会进入 context window,改变条件分布,而不是给模型增加外部事实保证。
RAG 把开放世界知识从参数记忆转移到可检查文档;工具调用把文本输出约束为结构化 action;验证器把“生成答案”改成“执行、观察、修正”。
一个可靠 Agent 不是更会聊天的 LLM,而是一个带状态机、权限边界、工具 schema、日志、回滚和证据检查的闭环系统。
02 / 数学
从语言模型目标到 Agent 状态转移
01 / 自回归分解
给定上下文 C,LLM 把序列概率拆成每一步的条件 token 概率。生成就是不断采样或选择下一个 token。
p(x_{1:T}|C)=prod_t p_theta(x_t | x_{<t}, C)02 / 预训练目标
标准 causal LM 用真实下一个 token 的负对数似然训练;梯度让模型提高正确 token 在 softmax 中的概率。
L(theta)=-sum_t log p_theta(x_t^* | x_{<t}, C)03 / 指令与偏好对齐
指令微调把任务格式、语气和约束写入监督样本;偏好优化让更被人类偏好的回答相对更可能。
maximize log pi_theta(y_good|x) - log pi_theta(y_bad|x)04 / RAG 边际化
检索器先给 query 找到候选文档 z,再让生成器在文档条件下回答。理想化写法是对文档证据求和。
p(y|q)=sum_z p_eta(z|q) p_theta(y|q,z)05 / 工具调用动作
Agent 把某些 token 序列约束为结构化 action,例如工具名和参数;环境执行 action 后返回 observation。
a_t={name,args}, o_t=Tool(a_t)06 / 状态与验证闭环
真正的 Agent 会把 observation、日志和验证结果写回状态;若验证失败,就重新计划或请求人工确认。
s_{t+1}=update(s_t,a_t,o_t,V(o_t))03 / 代码
Python 演示:最小 RAG + 工具调用 + 验证闭环
这个例子用一个可替换的 fake_llm 展示 Agent 结构。真实系统中,fake_llm 可以替换为任意 LLM API,但 schema、工具执行和验证边界应该保留。
import math
from collections import Counter
DOCUMENTS = [
{"id": "did", "text": "Difference-in-differences compares treated and control changes over time."},
{"id": "psm", "text": "Propensity score matching balances observed covariates before comparing outcomes."},
{"id": "uat", "text": "Universal approximation says a wide neural network can approximate continuous functions."},
]
def tokenize(text):
return [word.strip(".,:;!?").lower() for word in text.split()]
def vectorize(text):
return Counter(tokenize(text))
def cosine(a, b):
shared = set(a) & set(b)
dot = sum(a[key] * b[key] for key in shared)
norm_a = math.sqrt(sum(value * value for value in a.values()))
norm_b = math.sqrt(sum(value * value for value in b.values()))
return 0.0 if norm_a == 0 or norm_b == 0 else dot / (norm_a * norm_b)
def retrieve(query, k=2):
qv = vectorize(query)
ranked = sorted(
DOCUMENTS,
key=lambda doc: cosine(qv, vectorize(doc["text"])),
reverse=True,
)
return ranked[:k]
def validate_action(action):
allowed = {"search": {"query"}, "draft": {"claim", "evidence_ids"}}
if action.get("name") not in allowed:
raise ValueError("unknown tool")
missing = allowed[action["name"]] - set(action.get("arguments", {}))
if missing:
raise ValueError(f"missing arguments: {missing}")
return action
def run_tool(action):
args = action["arguments"]
if action["name"] == "search":
return retrieve(args["query"])
if action["name"] == "draft":
ids = ", ".join(args["evidence_ids"])
return f"{args['claim']} Evidence: {ids}."
raise ValueError("unreachable")
def fake_llm(state):
# Replace this with an LLM call. Keep the action schema and verifier outside the model.
if not state["docs"]:
return {"name": "search", "arguments": {"query": state["task"]}}
evidence_ids = [doc["id"] for doc in state["docs"]]
return {
"name": "draft",
"arguments": {
"claim": "Use RAG before answering empirical-method questions.",
"evidence_ids": evidence_ids,
},
}
def verify(answer, docs):
cited = {doc["id"] for doc in docs}
return all(doc_id in answer for doc_id in cited)
state = {"task": "Explain DID and PSM for a research assistant.", "docs": [], "trace": []}
for step in range(3):
action = validate_action(fake_llm(state))
observation = run_tool(action)
state["trace"].append({"action": action, "observation": observation})
if action["name"] == "search":
state["docs"] = observation
else:
if verify(observation, state["docs"]):
state["answer"] = observation
break
state["task"] += " Cite retrieved evidence explicitly."
print(state["answer"])
print("trace length:", len(state["trace"]))04 / 案例
案例:StatsPAI 研究助手如何从聊天变成可审计执行
- 用户提出:“帮我解释 DID 和 PSM,并给出 Stata/R 实操提醒。” 如果只是一次 LLM 生成,答案可能流畅但无法确认依据、版本和执行路径。
- Agent 版本会先把任务写入 state,检索课程知识页和方法文档,再生成结构化工具调用,例如 search(query)、open_file(path)、run_code(cmd) 或 render_table(model)。
- 每次工具调用都会产生 observation:检索到的文档、代码输出、表格路径或错误日志。Agent 把 observation 写回上下文,再决定继续检索、写草稿、运行验证还是请求人工确认。
- 最终答案必须带有可检查证据:引用了哪些知识页、运行了哪些命令、是否通过 route/build/test、哪些假设仍需人类判断。这就是 Agent 与普通聊天机器人的分界线。
05 / 风险
常见误区
参考资料
- Brown et al. (2020), Language Models are Few-Shot Learnershttps://arxiv.org/abs/2005.14165
- Lewis et al. (2020), Retrieval-Augmented Generation for Knowledge-Intensive NLP Taskshttps://arxiv.org/abs/2005.11401
- Yao et al. (2022), ReAct: Synergizing Reasoning and Acting in Language Modelshttps://arxiv.org/abs/2210.03629
- Schick et al. (2023), Toolformer: Language Models Can Teach Themselves to Use Toolshttps://arxiv.org/abs/2302.04761