📌 项目地址:[]() | ⭐ 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条原则能直接复用。没有框架反而是好事——你用任何编程语言、任何框架都能落地,只要遵守原则中的约束。

这篇文章对你有帮助吗?

发表回复