Codex 教程
按顺序阅读,循序渐进掌握相关 AI 工具与实战方法。
模型看到的对话历史存在哪里?dsh 的答案很特别:不来自任何专门的地方,而是从一份会话日志派生。Session 是由类型化 SessionEvent 组成的仅追加日志,是完整交互历史的唯一真源;LLM 消息历史从日志派生、从不单独存储,回放就是从同一组事件重新派生。
每个事件携带单调递增的 seq(等于 log.length)与 epoch 毫秒的 time;事件基于 type 做可辨识联合,switch (event.type) 即可收窄 event.data。所有 event.data 必须能无损序列化为 JSON,Session.append 在源头强制这一点。核心不变量是“模型可见即已记录”:新增一项模型可见输入,就要新增一个会话事件(扩展 SessionEventMap 并从日志渲染);需要可回放 transcript 的 SDK 用户应消费 session/event 事件流。
Session.deriveMessages() 把事件日志投影成模型看到的 Message[]。它是缓存的(surface 节点首次出现时投影,重写时重建),返回冻结数组,从类型上杜绝了通过投影篡改历史。投影规则:
| 事件 | 投影结果 |
|---|---|
user/message |
一条 user 消息,携带确切 content |
assistant/message |
一条 assistant 消息,含提供方、模型与可选回放状态 |
assistant/chunk |
跳过(回放/UI 数据,组装后的消息才是权威) |
tool/result |
一条带 tool-result 块的 user 消息 |
turn/*、step/* |
跳过(结构信息,不投影为消息) |
细节:内容为空的 assistant/message 也会被跳过,但因 max-tokens 截断且无内容的步骤仍会记录一条 assistant/message 以保存用量,并通过 sourceEventSeqs 精确列出对应的 chunk 事件(包括显式空列表)。
事件分三域,选对是大多数改动的第一个决定:会话事件(turn/start、user/message、tool/result 等)追加进日志并广播,是持久事实;Agent 事件(agent/pre-step、agent/request 等)携带活跃 Agent,用于实时控制与观察;能力事件(tools/*、fs/*、llm/stream)给能力 seam 挂策略。其中 agent/pre-step、llm/stream 与三个 tools/* 事件是 waterfall,必须调 next() 委托;agent/turn-stopping 是 serial 事件,没有 next()。
一个步骤(step)是一次模型请求加上它调用的工具;一个轮次(turn)包含零个或多个步骤,在领取首条输入前打开、不再欠工作时关闭——它包围一次模型循环执行,而非整个会话日志。完整时序:turn/start → agent/pre-step → step/start → llm/stream → 工具 → step/end → turn/end;turn/end 的 reason 为 TurnEndReason(completed / aborted / blocked / error / max-tokens / interrupted)。首次领取被拒绝或改写为空时,仍会关闭一个不含步骤的持久轮次。
规范打包形态下每行是一个 SessionEvent,surface 事件(user/message、assistant/message、tool/result)带 surfaceOp 标记,边界事件不带:
{"type":"turn/start","seq":0,"time":1755000000000,"data":{"turn":1}}
{"type":"step/start","seq":1,"time":1755000000010,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":2,"data":{"role":"user","content":[{"type":"text","text":"Fix the typo in the runoob README."}]},"surfaceOp":"append","sourceEventSeqs":[0]}
{"type":"tool/call","seq":6,"data":{"turn":1,"step":1,"callId":"call_1","name":"bash","arguments":"{\"command\":\"grep runoob README.md\"}"}}
{"type":"turn/end","seq":9,"data":{"turn":1,"reason":{"kind":"completed"}}}
用 Python 重放这份日志(deriveMessages 的简化教学模型):
import json, sys
def derive_messages(events):
messages = []
for ev in events:
t, d = ev["type"], ev["data"]
if t == "user/message":
messages.append({"role": "user", "content": d["content"]})
elif t == "assistant/message":
messages.append({"role": "assistant", "content": d["message"]["content"]})
elif t == "tool/result":
messages.append({"role": "tool", "name": d["message"]["toolName"],
"content": d["message"]["content"]})
return messages
原始 assistant/chunk 在派生时被跳过,组装后的 assistant/message 才是权威——模型看到的每一条消息都能从这份日志原样重建。