📌 项目地址:[]() | ⭐ 0 颗星 | 📜 未标注
我第一个AI Agent从Notebook搬到Kubernetes,上线第一天挂了四次。出问题的原因很蠢:对话状态写在Python字典里,日志落到容器本地文件。扩容到两个Pod后,用户两次连续请求落到不同实例,上下文全乱。定位一次工具调用超时,得ssh进十几个Pod翻目录,花了一下午。
后来看到12-Factor Agents这个项目,当时0星,现在不清楚。它把Heroku的十二因子应用方法论映射到AI代理开发。项目不教提示词,不教LangChain用法,只有12条原则的Markdown文档。我对照自己的坑,改了对生产影响最大的四条,再也没出过类似问题。
对话状态交给外部存储,进程只做计算
原则第一条:“进程不能持有持久数据”。AI Agent的对话记录、工具调用中间结果、用户会话,如果存在进程内存里,重启、滚动更新、水平扩容,数据全部丢失。
我原来的代码:
# 错误:状态在内存
conversation_history = []
def handle_message(msg):
conversation_history.append(msg)
llm_response = chat_with_history(conversation_history)
return llm_response
改完之后:
# 正确:状态在外部存储
def handle_message(session_id, msg):
history = redis.lrange(f"session:{session_id}", 0, -1)
history.append(msg)
llm_response = chat_with_history(history)
redis.rpush(f"session:{session_id}", msg, llm_response)
return llm_response
工具调用中间结果存到对象存储(我用的MinIO),每次请求从外部加载。进程可以随时销毁重建。Pod从1个扩到10个,用户会话依然完整。同一套代码部署到开发、测试、生产,不再需要改内部状态。
日志只写stdout,Kubernetes自动收集
生产环境50个Pod,日志写到容器本地文件意味着你得ssh进每个容器翻目录。我之前找一次工具调用链断裂的原因,花了一下午手动查文件。
原则要求所有运行信息——思考链、工具调用、错误——统一打印到stdout。Kubernetes自动捕获并转发到ELK、Loki等集中日志系统。
我每条日志输出JSON格式:
import json, logging, time
logger = logging.getLogger("agent")
log_entry = {
"timestamp": time.time(),
"trace_id": trace_id,
"level": "INFO",
"message": f"Tool call {tool_name} started",
"tool_name": tool_name,
"input": input_params
}
logger.info(json.dumps(log_entry))
在日志看板一键搜索tool_name:search_web AND level:ERROR,立刻定位到具体trace。这个做法在项目README的“日志”原则里有提到,我照着做了。
配置全部走环境变量,密钥不进代码库
API Key、模型名、temperature、system prompt——如果硬写在Python文件里,换环境就得改代码,改完容易忘记提交。我见过一个公开仓库躺着config.py,OpenAI Key明文暴露,三天后被刷了几百美元。
原则要求所有配置通过环境变量传入,代码里只用os.getenv读取。
# config.py
import os
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4")
TEMPERATURE = float(os.getenv("TEMPERATURE", "0.7"))
SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", "You are a helpful assistant.")
项目建议提供一个.env.template列出所有变量名。CI/CD系统注入密钥,密钥不会出现在代码仓库历史里。我把敏感变量放Kubernetes Secret里,Deployment YAML里只写valueFrom引用,再也没出现配置泄漏。
快速启动 + 优雅关闭,让K8s管理更平滑
AI Agent实例最好几秒内启动完成。初始化阶段不要加载大模型或预建外部连接,等真正用到时再延迟加载。终止时要保证当前工具调用能正常完成,或保存断点。
我见过一个Agent启动时直接加载1GB模型权重,K8s扩容等好几分钟,流量一来完全撑不住。
项目建议提供/health接口让编排系统知道代理是否就绪。我还加了/ready做就绪检查:
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/ready")
async def ready():
# 检查外部依赖是否就绪
if not await redis_ping():
return {"status": "unhealthy"}, 503
return {"status": "ok"}
Deployment里配置:
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 3
periodSeconds: 5
startupProbe:
httpGet:
path: /health
port: 8000
failureThreshold: 10
periodSeconds: 2
实测从镜像拉取到Pod Ready,平均6秒。关闭时给10秒grace period,让正在执行的工具调用完成或落盘断点。
这个项目给什么
它不给代码框架,不给提示词模板,只给12条原则的Markdown文档。每条原则下面有简短说明和常见反例。我把每条原则对应的实践写到了团队内部文档里,已经避免了两次配置泄漏和一次状态丢失。
如果你正在把AI Agent从原型推向生产,或者带团队写Code Review规范,或者运维需要容器化部署和弹性伸缩,这12条原则能直接复用。没有框架反而是好事——你用任何编程语言、任何框架都能落地,只要遵守原则中的约束。