教程:用 Python + FastAPI + JSONL 为 AI 编程助手构建可追溯的决策日志,记录每次修改的意图与备选方案,解决 AI 代码助手的推理过程不可回溯问题。
An agent 改了一个函数,没人知道为什么。对话消失了,diff 很干净,唯独 reasoning 不见了。
Agent 产出 diff 很快,产出解释更快,但那些解释转眼就没了。Reasoning ledger 能解决这个问题。每一次决策都会产生一条只增不减的记录,包含时间戳、选项、理由和替代方案。Agent 先写再行动,人类事后阅读。
本教程从零开始构建这样一个 ledger。技术栈很小:一个 Python 脚本调用免费模型,一个 FastAPI 应用暴露 webhook,一个 JSONL 文件记住一切。构建运行在 MonkeyCode 的免费模型配额和免费服务器选项上。MonkeyCode 是开源项目。披露:本文是 MonkeyCode 产品推广的一部分。免费层目前包含 1000 万 token 和一个小应用的主机服务器。限额会变,依赖前请先阅读当前 dashboard。
代码与提供商无关,任何兼容 OpenAI 的端点都能用。MonkeyCode 是选项之一,不管用哪个,本教程都适用。
登录 MonkeyCode dashboard,创建一个 API token,复制到 shell 中。
export MONKEYCODE_API_KEY="your-token-here"
export MONKEYCODE_BASE_URL="https://api.monkeycode.example/v1"
export MONKEYCODE_MODEL="your-free-model-id"
Base URL 和 model id 来自 dashboard,不要猜测,不要硬编码。
用模型列表请求验证 token。
curl -s "$MONKEYCODE_BASE_URL/models" \
-H "Authorization: Bearer $MONKEYCODE_API_KEY"
预期返回一个 JSON 数组,包含免费模型的 id。保存这个 id。如果端点没有暴露 /models,那就从 dashboard 复制 model id。
创建一个目录,创建一个虚拟环境,安装三个包。
mkdir reasoning-agent
cd reasoning-agent
python -m venv .venv
source .venv/bin/activate
pip install openai fastapi uvicorn
三个包覆盖整个构建。openai 负责和模型通信,fastapi 托管 webhook,uvicorn 运行服务器。虚拟环境让依赖保持在本地,也让免费服务器部署保持整洁。
创建 ledger.py。Ledger 是一个 JSONL 文件,每行是一条决策记录。
import json
import time
from pathlib import Path
LEDGER_PATH = Path("ledger.jsonl")
def append_entry(entry: dict) -> None:
entry["timestamp"] = time.time()
with LEDGER_PATH.open("a") as fh:
fh.write(json.dumps(entry) + "\n")
def read_ledger() -> list[dict]:
if not LEDGER_PATH.exists():
return []
return [json.loads(line) for line in LEDGER_PATH.read_text().splitlines()]
把 ledger 想象成飞行记录仪。飞机飞行不需要它,但调查员需要。数据库引入了活动部件,文件不会。JSONL 对单个 agent 来说足够了。
用一行命令验证 ledger。
python -c "import ledger; ledger.append_entry({'decision': 'smoke'}); print(ledger.read_ledger())"
预期返回一条记录,时间戳自动生成。
Agent 只有一个任务:读取一个失败的测试,向模型请求一个 patch,把 patch 写入文件,记录计划。Agent 本身从不直接应用 patch,由人类稍后处理。
import os
import subprocess
from pathlib import Path
from openai import OpenAI
import ledger
client = OpenAI(
api_key=os.environ["MONKEYCODE_API_KEY"],
base_url=os.environ["MONKEYCODE_BASE_URL"],
)
MODEL = os.environ["MONKEYCODE_MODEL"]
def run(cmd: list[str]) -> str:
return subprocess.run(cmd, capture_output=True, text=True).stdout
def main() -> None:
test_out = run(["pytest", "-q"])
if "passed" in test_out:
print("Tests pass. No action.")
return
diff = run(["git", "diff"])
prompt = f"Failing output:\n{test_out}\nCurrent diff:\n{diff}\nWrite a minimal patch."
response = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
)
proposal = response.choices[0].message.content
Path("proposal.patch").write_text(proposal)
ledger.append_entry({
"stage": "proposal",
"decision": "write proposal.patch",
"rationale": proposal[:500],
"alternatives": ["revert last commit", "edit by hand"],
})
if __name__ == "__main__":
main()
在有 broken test 的仓库上运行它。
python agent.py
验证两件事:proposal.patch 存在,ledger.jsonl 包含一条 proposal 记录。自动应用的 patch 可能删除数据,写成文件的 patch 可以被审查——审查才是关键。
免费服务器运行小型应用,FastAPI 应用正合适。创建 server.py。
from fastapi import FastAPI, Request
import agent
import ledger
app = FastAPI()
@app.post("/webhook")
async def webhook(request: Request):
payload = await request.json()
ledger.append_entry({
"stage": "webhook",
"decision": "accept payload",
"rationale": payload.get("event", "unknown"),
"alternatives": [],
})
agent.main()
return {"ok": True}
先在本地运行服务器。
uvicorn server:app --port 8000
在另一个终端发送测试 payload。
curl -X POST http://localhost:8000/webhook \
-H "Content-Type: application/json" \
-d '{"event": "test"}'
tail -n 2 ledger.jsonl
预期看到两条新记录,一条来自 webhook,一条来自 proposal。把同样的应用部署到免费服务器,dashboard 有具体步骤说明,代码不需要改。Webhook 是一扇门,ledger 是一本日志,每个访客都会留下痕迹。
Ledger 只有被人读的时候才有价值。添加一个 report 命令。
def report() -> None:
for entry in read_ledger():
print(entry["timestamp"], entry["decision"])
每次 agent 运行后都执行它。Report 回答"为什么"这个老问题,现在答案在磁盘上。用 grep 按 stage 名搜索,按时间戳范围搜索。Ledger 是纯文本,所以所有 Unix 工具都能用。
免费层是一个评估层,没有 SLA。Token 配额慷慨但有限。Model id 会变,base URL 会变,什么都不要硬编码。
谁应该跳过这个方案?有合规要求的团队,含有 secrets 的仓库,高吞吐量的 CI。Ledger 有用,但不会让免费层变成生产级。
永远不要自动应用模型的 patch。本教程特意写成写 patch 文件的方式,由人类来应用——这才是 ledger 的全部意义。
Agent 仍然会让你意外,但现在你可以追溯意外的原因了。在一个真实失败的 test 上跑本教程,ledger 会告诉你模型在想什么。