1. 项目概述为什么我们需要一个AI原生的数据平面如果你最近在折腾AI应用尤其是多智能体Multi-Agent系统大概率会和我有一样的感受做个Demo很快乐但要把这玩意儿稳定、安全地推到生产环境那真是另一回事。你花在“核心逻辑”上的时间可能远不如花在那些“隐藏的中间件”上——比如用户说“帮我订机票并查天气”你的后台得先判断这句话该分给哪个Agent处理为了应对不同LLM供应商的API差异你得写一堆适配器为了监控和调试你得在代码里到处埋点打日志为了安全合规还得加上内容过滤和记忆管理……这些“脏活累活”不仅重复而且一旦分散在各个服务里维护起来就是个噩梦。这就是Plano要解决的问题。它不是一个框架而是一个AI原生的代理服务器和数据平面。你可以把它理解为你所有AI服务智能体、LLM调用前面的一个“智能交通枢纽”。所有进出流量都经过它由它来统一处理路由、编排、安全、可观测性这些基础设施层面的问题。这样一来你的业务代码只需要关心“我是谁”提供什么功能而不用管“我怎么被找到”、“我怎么跟别人协作”、“我怎么被监控”这些事。它的核心价值在于解耦与集中。把那些本不该在每个代码库里重复实现的、与业务逻辑无关的“管道工程”抽出来放到一个统一的、进程外的数据平面里。这带来的直接好处是你可以用任何语言、任何框架来写你的智能体只要它们遵循简单的HTTP协议你可以随时增删或修改智能体而无需触动上游的调用方或路由逻辑你可以获得开箱即用的、覆盖全链路的追踪和监控。1.1 核心定位不是框架是数据平面这一点至关重要也是Plano区别于LangChain、LlamaIndex这类AI应用框架的地方。框架通常提供一套SDK和抽象让你在它的范式内构建应用你或多或少会被“绑定”。而Plano是一个数据平面它运行在你的应用之外像一个透明的代理层。你的智能体服务是无状态的HTTP后端Plano是负责调度、管理和观察这些后端的“大脑”。这种架构选择带来了几个显著优势语言和框架无关性你的智能体可以用Python的FastAPI、Go的Gin、Node.js的Express甚至用Rust来写只要暴露一个兼容OpenAI Chat Completions的API端点就行。Plano不关心你的实现细节。独立部署与扩展Plano和你的智能体可以独立部署、独立扩缩容。智能体挂了Plano可以感知并做故障转移流量大了你可以单独给Plano或某个智能体增加实例。关注点分离开发者可以全心投入领域逻辑比如一个精准的航班查询算法而将非功能需求路由、熔断、限流、鉴权交给Plano。Plano的底层基于Envoy Proxy构建这是云原生领域久经考验的高性能数据平面。这意味着它天生就具备了现代代理所需的负载均衡、服务发现、健康检查、流量镜像等能力现在这些能力被专门用于AI工作负载。2. 核心能力深度解析Plano到底能做什么Plano的宣传语里提到了编排、模型敏捷性、智能体信号、审查钩子等。我们把这些略显抽象的概念拆开揉碎看看在实际项目中它们对应着什么。2.1 智能体编排从“if-else”到“描述即路由”在没有Plano的情况下实现一个多智能体系统的路由通常意味着你要写一个“总控”服务。这个服务接收用户请求然后通过一堆if-else、正则表达式或者训练一个意图分类模型来判断应该把请求转发给weather_agent还是flight_agent。每增加一个新智能体你就要修改这个总控服务的代码更新路由逻辑重新测试和部署。Plano的做法是声明式编排。你在一个YAML配置文件里用自然语言描述每个智能体是干什么的。agents: - id: weather_agent url: http://localhost:10510 - id: flight_agent url: http://localhost:10520 listeners: - type: agent name: travel_assistant port: 8001 router: plano_orchestrator_v1 agents: - id: weather_agent description: | Gets real-time weather and forecasts for any city worldwide. Handles: Whats the weather in Paris?, Will it rain in Tokyo? - id: flight_agent description: | Searches flights between airports with live status and schedules. Handles: Flights from NYC to LA, Show me flights to Seattle你不需要写任何路由逻辑。Plano内置的路由器这里用的是他们自研的4B参数小模型plano_orchestrator_v1会读取这些描述在运行时理解用户查询的意图并自动决定将请求路由到最合适的智能体甚至可以在一次对话中按顺序调用多个智能体来共同完成一个复杂任务。背后的原理与考量为什么用小模型而不是直接用GPT-4做路由核心是成本、延迟和可控性。GPT-4这类大模型API调用一次不便宜且有网络延迟。对于高频的路由决策用一个小而专的模型在本地运行能在亚毫秒级完成判断成本几乎可以忽略。同时小模型的行为更确定、更容易调试和优化。Plano团队将路由模型与代理服务分离让你可以根据场景选择或替换路由策略这是架构上的一个巧妙设计。2.2 模型敏捷性与统一网关告别供应商锁定LLM领域变化飞快今天你用GPT-4明天可能想试试Claude 3.5后天某个开源模型突然表现更优。在传统做法里这意味着你要在代码里硬编码不同供应商的API密钥、端点URL和调用方式切换起来非常麻烦。Plano内置了一个统一的LLM网关。你在配置文件中声明所有可用的模型及其供应商。model_providers: - model: openai/gpt-4o access_key: $OPENAI_API_KEY default: true - model: anthropic/claude-3-5-sonnet access_key: $ANTHROPIC_API_KEY - model: local/llama-3.1-8b base_url: http://localhost:8080 api_key: “none”然后在你的智能体代码里你不再直接调用OpenAI或Anthropic的SDK而是将所有LLM请求发送到Plano的网关地址例如http://localhost:12001/v1。你的代码只需要指定model: “gpt-4o”Plano会自动帮你找到正确的供应商、处理认证、重试、失败回退等。更高级的用法是语义化路由和偏好设置别名路由你可以给model: “best-for-analysis”设置一个别名背后可能映射到claude-3-5-sonnet。当有更好的模型出现时你只需在Plano配置中更新这个别名背后的真实模型所有智能体的代码都无需改动。智能偏好你可以设置规则例如“对于代码生成任务优先使用claude-3-5-sonnet如果它不可用则降级到gpt-4o”。Plano的路由器会根据任务类型和模型状态自动决策。这实现了真正的模型敏捷性。你的业务逻辑与具体的模型提供商解耦可以像更换汽车零件一样轻松地切换底层LLM。2.3 开箱即用的可观测性告别手动埋点可观测性是生产级AI应用的命脉。但给分布式、多步骤的AI工作流添加追踪Tracing和指标Metrics是痛苦的。你需要在每个函数、每次API调用前后手动插桩收集的日志还散落在各处。Plano利用其作为所有流量必经之路的位置优势提供了零代码的端到端可观测性。只需在配置中开启tracing: random_sampling: 100 # 100%采样率生产环境可调低 exporters: - type: jaeger endpoint: http://localhost:14268/api/traces从此每一个经过Plano的请求无论是用户查询到智能体还是智能体内部调用LLM网关都会自动生成完整的OpenTelemetry追踪链路。你可以在Jaeger、SigNoz等可视化工具中清晰地看到用户请求在哪个智能体处理耗时多少。智能体内部调用了哪个LLM模型该次LLM调用的耗时、token使用量、成本。如果请求被路由到多个智能体它们之间的先后关系和依赖一目了然。这不仅仅是方便调试。这些追踪数据是后续进行效果评估Evaluation、持续优化Continuous Improvement的黄金数据源。Plano更进一步提出了Agentic Signals™的概念旨在从这些原始的追踪数据中自动提取出对评估智能体性能更有意义的信号例如“用户是否得到了完整答案”、“回答中是否包含了要求的特定数据点”等为基于数据的迭代优化铺平了道路。2.4 过滤器链集中式的安全与合规护栏内容安全、审查、记忆管理是另一个容易分散在各处的关注点。你既要在用户输入时检查是否有不当内容又要在LLM输出时防止其“胡说八道”还要在多个回合的对话中维护上下文记忆。Plano通过过滤器链Filter Chain机制将这些能力集中化、模块化。过滤器是可以在请求/响应流水线上插入的处理器它们按顺序执行。你可以配置全局的过滤器链也可以为特定的路由或智能体配置。filter_chains: - name: safety_chain filters: - type: moderation # 配置敏感词过滤或调用外部审核API - type: jailbreak_detection # 检测并阻止越狱提示词 - type: memory # 为对话注入历史上下文这样做的最大好处是一致性和可维护性。你的安全策略在一个地方定义和更新立即对所有智能体生效。你不需要在每个智能体的代码里都写一遍内容审核的逻辑。记忆管理也由Plano统一负责智能体可以专注于处理当前回合的请求无需自己维护复杂的会话状态。3. 从零开始搭建一个多智能体旅行助手理论说了这么多我们动手搭一个。这个例子会涵盖Plano最核心的编排和LLM路由功能。目标是实现一个旅行助手用户一句话问天气和航班系统能自动调用对应的两个智能体并整合回复。3.1 环境准备与Plano安装首先确保你的机器上有Docker和Docker Compose这是运行Plano最简单的方式。Plano也提供了二进制包和从源码编译的选项但Docker方式最省心。克隆仓库并进入示例目录git clone https://github.com/katanemo/plano.git cd plano/demos/agent_orchestration/travel_agents/这个目录下已经准备好了两个智能体的示例代码Python FastAPI和一个Plano的Docker Compose配置文件。配置API密钥 在项目根目录创建一个.env文件填入你的OpenAI和Anthropic如果需要的API密钥。OPENAI_API_KEYsk-your-openai-key-here ANTHROPIC_API_KEYyour-anthropic-key-here注意Plano的LLM网关需要这些密钥来帮你转发请求。请妥善保管你的.env文件不要将其提交到版本控制系统。3.2 编写智能体服务保持简单智能体的职责非常单纯接收一个标准化格式的请求执行自己的领域逻辑比如调用第三方天气API然后利用LLM生成一段友好的回复。它通过Plano的LLM网关来调用大模型。我们来看一下weather_agent的核心代码已简化# weather_agent.py import os from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse from openai import AsyncOpenAI import httpx import asyncio app FastAPI() # 关键点客户端指向Plano的LLM网关而不是直接指向OpenAI LLM_GATEWAY_URL os.getenv(“LLM_GATEWAY_URL”, “http://localhost:12001/v1”) llm_client AsyncOpenAI(base_urlLLM_GATEWAY_URL, api_key“EMPTY”) # api_key由Plano网关处理 async def fetch_weather_from_api(city: str, days: int) - dict: 模拟或真实调用天气API。这里用模拟数据。 # 在实际项目中这里会调用如OpenWeatherMap的API await asyncio.sleep(0.1) # 模拟网络延迟 return { “city”: city, “forecast”: [{day: i1, “condition”: “Sunny”, “high_c”: 22i, “low_c”: 10i} for i in range(days)] } app.post(“/v1/chat/completions”) async def chat_completion(request: Request): # 1. 解析请求 body await request.json() messages body.get(“messages”, []) # 最后一条用户消息是我们要处理的 user_query messages[-1][“content”] if messages else “” # 2. 提取意图和参数这里简化实际可用更复杂的NLP # 假设我们能从查询中提取城市名和天数 city “Paris” # 简化实际应从query中解析 days 5 # 3. 执行核心业务逻辑获取数据 weather_data await fetch_weather_from_api(city, days) # 4. 构造系统提示词指导LLM如何格式化回复 system_prompt { “role”: “system”, “content”: f“””你是一个专业的天气助手。以下是{city}未来{days}天的天气预报数据 {weather_data} 请根据这些数据生成一段对用户友好、包含关键信息的天气总结。直接回复天气情况不要提及你收到了数据。“”” } # 5. 通过Plano网关流式调用LLM async def response_stream(): try: stream await llm_client.chat.completions.create( model“gpt-4o”, # 这里指定模型由Plano路由到具体供应商 messages[system_prompt] messages, streamTrue, temperature0.7, ) async for chunk in stream: if chunk.choices and chunk.choices[0].delta.content is not None: yield chunk.choices[0].delta.content except Exception as e: yield f“[Weather Agent Error: {str(e)}]” return StreamingResponse(response_stream(), media_type“text/plain”) # 简单返回文本流 if __name__ “__main__”: import uvicorn uvicorn.run(app, host“0.0.0.0”, port10510)代码解读与实操要点LLM网关地址LLM_GATEWAY_URL是核心配置。你的智能体不再直接面对五花八门的LLM API只面对Plano这一个统一的接口。这实现了模型调用的解耦。API兼容性智能体暴露的端点/v1/chat/completions与OpenAI的Chat Completions API格式兼容。这不仅是Plano与智能体通信的标准也意味着你的智能体本身就可以被当作一个简单的LLM服务来测试。业务逻辑隔离智能体的核心就是fetch_weather_from_api这个函数。它专注于获取结构化数据。将数据转化为自然语言回复的任务交给了通过网关调用的通用LLM。这符合“让专业的人做专业的事”的原则。流式响应使用StreamingResponse可以实现在数据生成的同时就返回给客户端用户体验更好。Plano本身也支持流式传输的代理。flight_agent的结构与此完全类似只是核心业务逻辑变成了查询模拟的航班信息。3.3 配置Plano声明式定义你的系统接下来是Plano的配置文件plano-config.yaml这是整个系统的“大脑”。version: “v0.3.0” # 第一部分定义后端智能体服务 agents: - id: weather_agent url: http://host.docker.internal:10510 # Docker Compose环境下访问宿主机服务 metadata: type: “weather_service” - id: flight_agent url: http://host.docker.internal:10520 metadata: type: “flight_service” # 第二部分定义可用的LLM模型提供商 model_providers: - model: openai/gpt-4o access_key: ${OPENAI_API_KEY} # 从环境变量读取 default: true # 默认提供商 - model: anthropic/claude-3-5-sonnet access_key: ${ANTHROPIC_API_KEY} # 你可以继续添加其他模型如Azure OpenAI, Google Gemini, 本地模型等 # 第三部分定义监听器对外提供的服务入口 listeners: - type: agent name: travel_assistant_listener port: 8001 # 用户将访问这个端口 router: plano_orchestrator_v1 # 使用的路由模型 agents: - id: weather_agent description: | 这是一个天气查询智能体。它能获取全球任何城市的实时天气和未来多日预报。 它能处理的问题包括“巴黎天气怎么样”、“东京下周会下雨吗”、“旧金山明天的气温”。 - id: flight_agent description: | 这是一个航班查询智能体。它能搜索机场间的航班信息包括实时状态和日程。 它能处理的问题包括“查一下从纽约到洛杉矶的航班”、“给我看看去西雅图的机票”、“找找下周北京飞伦敦的航班”。 # 可以在这里为这个监听器附加过滤器链例如安全过滤 # filter_chain_ref: safety_chain # 第四部分可观测性配置 tracing: enabled: true random_sampling: 20 # 20%的请求会被采样并生成追踪生产环境平衡性能与洞察 exporters: - type: console # 在控制台输出追踪用于调试 - type: otlp endpoint: http://jaeger:4317 # 发送到Jaeger进行可视化 # 第五部分可选定义过滤器链 filter_chains: - name: safety_chain filters: - type: keyword_block config: blocked_keywords: [“敏感词A”, “敏感词B”]配置深度解析agents这是你的“服务注册中心”。Plano通过这里定义的URL来发现和健康检查你的智能体。listeners这是对外暴露的API。router: plano_orchestrator_v1是关键它告诉Plano使用其内置的4B小模型来理解用户查询并根据description将查询路由到最匹配的智能体。描述写得越准确、涵盖的用例越典型路由效果就越好。host.docker.internal在Docker Compose设置中这是从容器内部访问宿主机服务的特殊域名。确保你的智能体服务在宿主机上运行在指定端口。采样率random_sampling: 20意味着只有20%的请求会产生详细的追踪数据。在高流量生产环境中100%采样会对性能造成压力20%通常是一个能有效发现问题又不过度消耗资源的平衡点。3.4 启动与测试见证自动编排启动智能体服务 在两个不同的终端窗口分别启动天气和航班智能体。# 终端1 cd demos/agent_orchestration/travel_agents/weather_agent LLM_GATEWAY_URLhttp://localhost:12001/v1 python weather_agent.py# 终端2 cd demos/agent_orchestration/travel_agents/flight_agent LLM_GATEWAY_URLhttp://localhost:12001/v1 python flight_agent.py启动Plano 在项目根目录plano/使用Docker Compose启动Plano。docker-compose -f docker-compose.demo.yaml up这个命令会启动Plano主服务、LLM网关以及可观测性套件如Jaeger。发送测试请求 一切就绪后向Plano的入口localhost:8001发送一个复合查询。curl -X POST http://localhost:8001/v1/chat/completions \ -H “Content-Type: application/json” \ -d ‘{ “model”: “gpt-4o”, “messages”: [ {“role”: “user”, “content”: “我下周想从纽约去巴黎旅行能告诉我巴黎那边的天气怎么样并且帮我找找航班吗”} ], “stream”: false }’神奇的事情发生了你只发送了一个请求到Plano。Plano的路由器会理解这个请求包含两个意图查天气、查航班。根据我们之前的配置它可能会首先将请求路由到weather_agent获取巴黎天气。然后将同一个对话上下文可能经过修饰路由到flight_agent获取纽约-巴黎航班。最后Plano可能会将两个结果整合或者按顺序流式返回给用户。 你会在控制台看到两个智能体被依次调用的日志。打开Jaeger的UI通常http://localhost:16686你能看到一个清晰的分布式追踪显示了请求在Plano内部以及两个智能体之间的完整生命周期。4. 生产级考量与避坑指南把Demo跑起来只是第一步要用于生产环境有几个关键点必须考虑。4.1 性能、扩展性与高可用Plano本身是无状态的它的配置来自文件。这意味着你可以轻松地水平扩展Plano的实例前面用一个负载均衡器如Nginx, AWS ALB分发流量。所有实例共享相同的配置。智能体的健康检查Plano会定期向agents中配置的URL发送健康检查请求。如果某个智能体宕机Plano会将其从可用节点池中移除避免将流量路由到故障实例。你需要确保智能体实现一个健康检查端点例如/health并在Plano配置中指定。LLM网关的容错与降级在model_providers配置中你可以为同一个语义模型如gpt-4配置多个后备提供商OpenAI官方、Azure OpenAI。Plano可以在主提供商失败时自动切换到备用。你还可以设置故障率、超时时间等熔断策略。资源限制为Plano容器分配合适的CPU和内存。路由模型如4B参数虽然小但仍需驻留内存。同时监控Plano的并发连接数和请求延迟。4.2 安全与网络API认证上述Demo为了简单没有设置认证。在生产中你必须为Plano的监听器listeners启用认证。Plano支持通过过滤器链集成JWT验证、API密钥验证等。例如可以添加一个auth过滤器要求所有请求必须在Header中携带有效的Bearer Token。网络隔离Plano网关、智能体、内部LLM服务如果自托管应该部署在同一个安全的私有网络内如Kubernetes集群内、同一个VPC。对外只暴露Plano的入口端口如8001并配置严格的安全组或防火墙规则。敏感信息管理API密钥OPENAI_API_KEY等绝对不要硬编码在配置文件中。使用环境变量或专业的密钥管理服务如HashiCorp Vault、AWS Secrets Manager。Plano的配置支持${ENV_VAR}语法来引用环境变量。4.3 监控、日志与调试利用好OpenTelemetry将追踪数据导出到Jaeger、SigNoz或云服务商的可观测性平台如AWS X-Ray。基于这些追踪你可以轻松定位是哪个智能体或哪次LLM调用导致了高延迟或错误。自定义指标除了自动收集的指标请求数、延迟、错误率你可以在智能体中通过OpenTelemetry SDK暴露自定义业务指标例如weather_api_call_duration、flight_search_results_count。这些指标同样可以被Plano收集和汇聚。结构化日志确保Plano和你的智能体都输出结构化的日志JSON格式。使用像Loki或Elasticsearch这样的日志聚合系统方便你通过关键词如agent_id,trace_id快速关联所有相关日志。调试路由决策如果发现路由不准确首先检查智能体的description是否足够清晰、有区分度。Plano可能提供了路由决策的日志或调试端点可以查看路由模型是如何理解用户查询和智能体描述的。4.4 常见问题排查实录在实际部署中你可能会遇到以下问题智能体健康检查失败现象Plano日志显示health check failed for agent X请求无法路由到该智能体。排查确认智能体服务是否真的在运行且端口正确curl http://localhost:10510/health。检查Plano配置中agents.url的地址。在容器化部署中注意使用服务名如http://weather-agent:10510而不是localhost。确认智能体的健康检查端点返回了正确的HTTP状态码如200 OK。LLM网关返回401 Unauthorized或Invalid API Key现象智能体日志显示调用Plano LLM网关失败。排查确认Plano容器的环境变量中正确设置了OPENAI_API_KEY等。确认model_providers配置中的access_key字段正确引用了环境变量${VAR}。检查API密钥是否有足够的额度或权限。路由不准确请求被发到错误的智能体现象用户问航班结果被路由到了天气智能体。排查优化描述这是最常见的原因。确保每个智能体的description字段用清晰、具体的自然语言描述其能力和处理范围。使用示例查询Handles: ...非常有效。检查路由模型确认router配置是你期望的模型。plano_orchestrator_v1是通用路由模型对于特定领域未来可能有更专业的模型或你可以微调自己的路由模型。查看路由日志如果Plano提供了调试模式开启它以查看路由模型对输入查询和智能体描述的匹配分数。流式响应中断或速度慢现象客户端收到不完整的流式响应或等待时间很长。排查网络超时检查Plano、智能体、客户端之间的网络连接和超时设置。确保代理和负载均衡器支持长连接和流式传输。智能体性能在智能体中模拟的fetch_weather_from_api或真实API调用可能是瓶颈。优化这些外部调用考虑加入缓存。LLM响应速度不同的LLM提供商和模型响应速度差异很大。在Plano的LLM网关配置中考虑设置请求超时并为关键任务指定低延迟的模型。将Plano引入你的技术栈初期会有一个学习和配置的成本但一旦跑通它为你带来的在编排、运维、观测上的清晰度和效率提升是巨大的。它迫使你遵循更清晰的架构边界而这正是构建可维护、可扩展的生产级AI应用所必需的。