公司动态
基于LLM的智能体设计:从多轮对话到架构图生成的工程实践
1. 项目概述从“一问一答”到“持续规划”的跨越最近在做一个智能客服系统的升级项目客户提了个挺有意思的需求他们希望系统不仅能回答单次问题还能像经验丰富的技术支持专家一样在复杂的故障排查、方案咨询等场景下与用户进行多轮、有逻辑的对话并最终能生成一个清晰的架构图或流程图把讨论的方案可视化出来。这让我意识到传统的“输入-输出”式对话模型已经不够用了我们需要的是一个具备“思考-规划-执行-反思”闭环能力的智能体也就是现在常说的Agent。这个“多轮对话生成架构图 Agent”的核心价值在于它模拟了人类专家在解决复杂问题时的思维过程。想象一下当你向一位架构师咨询系统设计时他不会立刻给你一张图。他会先问你业务规模、技术栈偏好、预算约束然后基于你的回答在脑中构思几个候选方案接着他会和你讨论每个方案的优劣可能还会追问一些细节最后才把达成共识的方案画出来。我们要做的就是用代码和算法把这个过程自动化。这不仅仅是接个 ChatGPT API 那么简单它涉及到对话状态管理、任务分解、工具调用、以及最终的图形化渲染等多个环节的协同。无论是做智能客服、设计助手还是教育领域的解题引导这个模式都有很大的用武之地。2. 核心设计思路构建一个会“思考”和“使用工具”的智能体设计这样一个 Agent关键在于让它摆脱“鹦鹉学舌”式的回复具备自主的任务理解和分步执行能力。我的设计思路围绕“规划-执行-反思”这个核心循环展开并为其配备必要的“工具箱”。2.1 智能体的“大脑”基于大语言模型的规划与决策Agent 的“大脑”通常由一个大型语言模型LLM担任例如 GPT-4、Claude 3 或开源的 Llama 3 等。它的核心职责不是直接生成最终答案而是进行任务规划和工具调度。理解与规划当用户提出一个初始请求比如“帮我设计一个能承受百万日活的电商系统”LLM 首先需要理解这个模糊的请求背后隐含的多个子任务。它会规划出一个大致的步骤序列例如步骤一澄清需求询问具体的业务模块、数据量级、一致性要求等。步骤二选择合适的技术组件如负载均衡器、数据库选型、缓存策略。步骤三确定组件间的交互关系和数据流向。步骤四调用绘图工具生成架构图。步骤五解释生成架构图的关键设计点。工具调用LLM 知道自己不擅长画图、精确计算或查询实时信息。因此在规划中它会识别出需要外部工具协助的步骤。例如“确定数据库选型”可能需要调用一个知识库查询工具来获取最新的数据库性能对比数据“生成架构图”则明确需要调用绘图工具。LLM 会生成结构化的工具调用请求包含工具名称和输入参数。注意这里的一个常见误区是让 LLM 直接生成 PlantUML 或 Mermaid 代码。对于复杂架构LLM 很容易生成语法错误或逻辑矛盾的代码。更好的做法是让 LLM 输出一个结构化的中间表示如 JSON描述节点和边再由一个专用的、稳定的转换器去生成图形代码。这解耦了逻辑和渲染提升了系统的鲁棒性。2.2 智能体的“工具箱”为对话与绘图赋能一个强大的 Agent 离不开一套好用的工具。我为这个项目设计了几类核心工具对话状态管理工具这是实现多轮对话的基石。它需要维护一个“对话上下文”不仅包括历史消息更重要的是维护当前任务的状态机。例如状态可能是“需求收集进行中”、“技术方案讨论中”、“等待用户确认细节”、“绘图生成中”。这个工具帮助 Agent 记住之前聊过什么当前处在哪个阶段下一步该做什么。我通常会用 Redis 或数据库来持久化这些会话状态键值包括session_id,current_stage,collected_parameters(收集到的用户需求)conversation_history等。需求澄清与追问工具这是一个封装好的提示词Prompt模板。当 LLM 判断当前信息不足以进行下一步时例如用户只说“要一个高可用系统”该工具会触发一组预设的、针对性的问题。例如“请问您对‘高可用’的具体要求是几个9的可用性如 99.9% 或 99.99%”、“故障恢复时间目标RTO和恢复点目标RPO分别是多少”。架构知识检索工具为了让推荐的技术栈更专业、更与时俱进Agent 需要访问一个架构知识库。这个知识库可以是你维护的 Markdown 文档、向量数据库如 Chroma, Weaviate中存储的最佳实践案例甚至是联网搜索的权限。当 LLM 需要决定是使用 Kafka 还是 RabbitMQ 时它可以调用此工具查询两者的适用场景、吞吐量对比和运维复杂度。架构图生成工具这是最终的输出环节。输入是经过多轮对话后形成的、结构化的系统描述JSON 格式。这个工具负责将其转换为图形定义语言。方案A推荐结构化JSON - 模板引擎 - Mermaid/PlantUML 代码 - 渲染服务。这样 LLM 只需输出规整的 JSON降低了出错率。方案BLLM - Mermaid/PlantUML 代码 - 渲染服务。更直接但对 LLM 的代码生成能力要求高需要后置严格的语法检查和修正。 渲染服务可以使用mermaid.ink这样的在线 API或者自部署mermaid-cli、PlantUML server来生成 PNG/SVG 图片。2.3 工作流与状态机设计整个 Agent 的工作流是一个动态循环我将其设计为一个可扩展的状态机。初始状态等待用户输入 | v 用户输入 - LLM 分析意图并规划下一步 | v 判断信息是否充足 | |-- 否 - 调用【追问工具】进入“澄清中”状态等待用户回复。 | |-- 是 - 判断下一步是否需要调用工具 | |-- 是 - 执行工具调用如检索知识库更新对话状态和上下文。 | | | v | LLM 分析工具返回结果并规划下一步。 | |-- 否 - LLM 直接生成回复给用户。 | v 判断是否满足生成架构图的条件如需求已确认组件已选定 | |-- 是 - 调用【绘图工具】生成架构图并解释。 | | | v | 任务完成状态重置或进入总结阶段。 | |-- 否 - 返回继续下一轮对话。这个状态机的核心是“LLM 作为调度中心”。每一步之后LLM 都会根据当前完整的上下文历史对话工具执行结果当前状态重新评估决定下一步行动。这赋予了 Agent 处理意外中断、用户修改需求等复杂情况的能力。3. 关键技术实现细节与踩坑实录有了设计思路接下来就是具体的实现。这部分我会分享几个关键模块的代码级细节和实际开发中遇到的“坑”。3.1 与大语言模型的高效交互超越简单的 Chat Completion很多人调用 LLM API 就是简单的user: ...,assistant: ...对话。但对于 Agent我们需要更精细的控制。我采用ReAct (Reasoning Acting)范式来构建提示词。系统提示词System Prompt是 Agent 的“角色设定”和“行为准则”必须写得非常明确你是一个资深的系统架构师助手。你的目标是通过多轮对话帮助用户厘清需求并最终生成一个清晰的技术架构图。 ## 能力 1. 你可以通过调用工具来获取信息或执行任务。 2. 你会主动、有条理地询问不清楚的需求细节。 3. 你最终会生成一个结构化的架构描述用于绘图。 ## 行为规则 - 每次回复你必须首先思考当前对话阶段和用户目标。 - 如果你需要更多信息请直接提出具体问题。 - 如果你有足够信息进行下一步或调用工具请按以下格式输出 【思考】你的推理过程 【行动】工具名称 【输入】传递给工具的JSON参数 - 如果你可以直接回答用户问题请正常回复。 - 架构图生成条件是核心业务模块、数据存储、外部依赖三者都已明确。用户提示词User Prompt则包含了当前的对话历史、工具返回结果和当前状态。这里的关键是上下文管理。我们不能无限制地把所有历史对话都塞给 LLM因为存在令牌Token限制。我的策略是维护一个固定长度的“短期记忆”如最近10轮对话。维护一个“摘要式长期记忆”每经过一定轮次或用 LLM 对之前的对话进行关键信息摘要并将摘要放入后续对话的上下文。这能有效节省 Token并聚焦关键信息。一个踩过的坑早期我把所有工具的描述都放在系统提示词里导致提示词过长且混乱。后来我改成了“动态工具描述”只在 LLM 可能需要调用工具时才在当轮的用户提示词中附加相关工具的功能和参数说明。这大大提高了提示词的效率和 LLM 对工具理解的准确度。3.2 工具调用模块的实现稳定与灵活并重工具调用模块是 Agent 的“手和脚”。我将其设计为一个可插拔的注册中心。class ToolRegistry: def __init__(self): self._tools {} def register(self, name: str, func: callable, description: str, schema: dict): 注册一个工具 self._tools[name] { function: func, description: description, schema: schema # JSON Schema描述输入参数 } async def execute(self, tool_name: str, arguments: dict) - str: 执行指定工具 if tool_name not in self._tools: return fError: Tool {tool_name} not found. tool self._tools[tool_name] try: # 验证参数是否符合 schema此处省略验证代码 result await tool[function](**arguments) return str(result) except Exception as e: return fError executing tool {tool_name}: {str(e)} # 示例注册一个架构知识查询工具 def query_arch_knowledge(keyword: str, aspect: str comparison) - str: # 模拟从向量数据库或知识库中查询 knowledge_base { (kafka, comparison): Kafka 适用于高吞吐、持久化、流式数据场景支持多订阅者..., (rabbitmq, comparison): RabbitMQ 适用于复杂的路由、消息确认、低延迟场景... } return knowledge_base.get((keyword.lower(), aspect), No relevant knowledge found.) registry ToolRegistry() registry.register( namequery_arch_knowledge, funcquery_arch_knowledge, description查询关于特定技术组件如数据库、中间件的架构知识、选型对比或最佳实践。, schema{ type: object, properties: { keyword: {type: string, description: 技术组件名称如 kafka, redis}, aspect: {type: string, enum: [comparison, best_practice, scaling], default: comparison} }, required: [keyword] } )当 LLM 输出【行动】query_arch_knowledge和【输入】{keyword: Kafka}时Agent 的核心循环就会截取这些信息调用ToolRegistry.execute()方法并将工具返回的结果如“Kafka 适用于高吞吐...”插入下一轮对话的上下文供 LLM 参考。实操心得工具函数的返回值最好是结构化的字符串或简单 JSON。避免返回过于复杂或冗长的 HTML/Markdown因为 LLM 需要“阅读”这个结果。另外每个工具都应该有完善的错误处理和超时机制防止单个工具失败导致整个 Agent 卡死。3.3 从结构化描述到架构图可靠的内容生成这是用户体验的临门一脚。如前所述我不建议让 LLM 直接生成图形代码。我的流水线是这样的结构化输出约束在最终生成图的阶段我会在给 LLM 的提示词中严格要求其输出格式。请根据以上对话总结出最终的架构设计并严格按照以下 JSON 格式输出不要有任何额外解释 { system_name: 电商系统, components: [ {id: client, type: external, label: Web/App 客户端}, {id: nginx, type: gateway, label: Nginx 负载均衡}, {id: app_server, type: service, label: 应用服务器集群}, {id: redis, type: cache, label: Redis 缓存}, {id: mysql_master, type: database, label: MySQL 主库}, {id: mysql_slave, type: database, label: MySQL 从库} ], connections: [ {from: client, to: nginx, label: HTTPS 请求}, {from: nginx, to: app_server, label: 负载均衡}, {from: app_server, to: redis, label: 缓存读写}, {from: app_server, to: mysql_master, label: 写操作}, {from: app_server, to: mysql_slave, label: 读操作} ] }使用 JSON 模式极大提高了 LLM 输出的规整度。模板化渲染收到 JSON 后用一个预定义的 Jinja2 模板将其转换为 Mermaid 代码。{# mermaid_template.j2 #} graph TD {% for comp in components %} {{ comp.id }}[“{{ comp.label }}”] {% endfor %} {% for conn in connections %} {{ conn.from }} --|“{{ conn.label }}”| {{ conn.to }} {% endfor %}渲染后得到graph TD client[“Web/App 客户端”] nginx[“Nginx 负载均衡”] app_server[“应用服务器集群”] redis[“Redis 缓存”] mysql_master[“MySQL 主库”] mysql_slave[“MySQL 从库”] client --|“HTTPS 请求”| nginx nginx --|“负载均衡”| app_server app_server --|“缓存读写”| redis app_server --|“写操作”| mysql_master app_server --|“读操作”| mysql_slave后端渲染与返回将生成的 Mermaid 代码发送给后端渲染服务如调用mermaid.ink或自建服务得到图片 URL最终连同架构描述一起返回给前端展示。这样做的好处即使未来想更换绘图引擎比如从 Mermaid 切换到 Draw.io 或 Excalidraw也只需要修改模板和渲染步骤核心的 Agent 逻辑和 LLM 交互完全不用动。4. 工程化部署与性能优化考量当原型验证通过后要投入实际使用就必须考虑工程化的问题。这里面的水一点也不浅。4.1 会话、状态与上下文的持久化Agent 是有状态的而且对话可能很长。你不能把所有状态都放在内存里。我的方案是会话存储使用 Redis 或 PostgreSQL。每个活跃会话一个 Key存储序列化后的会话对象。Redis 更快适合高频访问PostgreSQL 更可靠便于复杂查询和分析。状态管理会话对象中不仅包含原始对话记录更重要的是一个state字典。这个字典记录了当前任务进展到了哪一步、收集到了哪些关键参数如{“availability”: “99.99%”, “peak_qps”: “10000”}。这比让 LLM 每次都从历史对话中自行推断要可靠和高效得多。上下文窗口优化这是成本Token 费用和效果记忆力的平衡。我采用分层记忆策略工作记忆最近 3-5 轮对话的原始记录保证对最新话题的连贯理解。摘要记忆每隔一定轮次或当对话历史过长时用一个小模型如 GPT-3.5-turbo或专门的摘要链将之前的对话浓缩成一段“背景摘要”。关键事实记忆将用户明确确认的需求参数如“我们需要 MySQL 数据库”、“必须使用微服务”提取出来作为独立的事实点存储。 在每次构造 LLM 提示词时将“背景摘要”和“关键事实”放在系统提示词或上下文头部后面跟上“工作记忆”。这样可以用最少的 Token 携带最核心的信息。4.2 降低延迟与提升响应速度的技巧Agent 需要多次调用 LLM 和工具容易导致响应慢。用户体验是“这 AI 怎么老在思考”。流式输出Streaming在 Agent “思考”和调用工具时可以先给用户返回一个“正在思考中...”或“正在为您查询...”的占位信息。对于 LLM 生成文本的部分务必开启流式输出让用户看到文字逐个出现而不是长时间白屏。这能极大提升感知速度。并行与异步仔细分析你的工作流。有些步骤可以并行吗例如在“需求澄清”阶段如果需要查询多个不相关的技术组件的知识可以并行发起多个工具调用。使用asyncio.gather能显著缩短整体耗时。LLM 调用优化温度Temperature设置在需要创造性、发散性思维的“头脑风暴”阶段可以设高一点如 0.8。在需要稳定、确定性输出的“工具调用决策”或“结构化输出”阶段一定要调低如 0.1 或 0避免它“胡言乱语”导致流程出错。模型选型不是所有步骤都需要最强大、最贵的模型。对于“对话摘要”、“意图分类”这类相对简单的任务可以使用更小、更快的模型如 Claude Haiku, GPT-3.5-turbo把 GPT-4 这样的“重型武器”用在最核心的规划和复杂推理上。这能有效降低成本并提升速度。4.3 可观测性与调试给 Agent 装上“黑匣子”Agent 内部决策过程不透明出了问题很难排查。必须建立完善的可观测性体系。全链路日志记录下每一轮的关键信息。我建议的结构化日志字段包括session_idturn_number(第几轮对话)user_inputllm_prompt_snapshot(发送给 LLM 的完整提示词脱敏后)llm_full_response(LLM 的完整回复)parsed_action(解析出的行动如query_arch_knowledge)tool_input_output(工具的输入和输出)current_state(执行后的状态)final_response_to_user(最终给用户的回复) 将这些日志输出到 ELKElasticsearch, Logstash, Kibana或 Grafana Loki便于搜索和聚合分析。构建调试界面开发一个内部管理界面可以输入任意session_id回放整个对话流程查看每一轮 LLM 的“思考过程”、工具调用的详情和状态变迁。这是定位“为什么 Agent 在这里卡住了”或“为什么它做出了这个奇怪决定”的终极武器。关键指标监控业务指标会话成功率、平均完成轮次、用户主动中断率、架构图生成率。性能指标每轮平均响应时间、LLM API 调用耗时分布、工具调用耗时。成本指标各模型 Token 消耗量、每日/每月成本趋势。 设置告警当会话成功率骤降或平均响应时间异常拉长时能第一时间收到通知。5. 避坑指南与常见问题排查在实际开发和上线后我遇到了不少典型问题。这里列几个最有代表性的希望能帮你绕过这些坑。5.1 Agent 陷入循环或偏离主题现象Agent 反复问同一个问题或者在某个细节上钻牛角尖无法推进到下一阶段。根因分析状态机设计有漏洞某个状态缺少出口条件或者状态转移逻辑有误。LLM 的提示词引导力不足系统提示词中没有强调“推进任务”的目标或者对“完成条件”描述模糊。工具返回结果质量差工具返回的信息无法帮助 LLM 做出有效决策导致它“不知所措”只能原地打转。解决方案强化状态机为每个状态设置超时或最大重试次数。例如在“需求澄清”状态如果连续3轮对话都在问类似问题而没有收集到新信息则强制跳转到“方案建议”状态并基于已有可能不完整的信息进行。优化提示词在系统提示词中明确加入“任务推进指令”例如“你的首要目标是高效地收集齐所有必要信息并引导对话走向生成架构图。避免在无关紧要的细节上过度纠缠。”改进工具确保知识检索工具返回的信息是简洁、相关、权威的。如果工具频繁返回“未找到”考虑扩充知识库或改进检索算法。5.2 生成的架构图逻辑混乱或不符合常识现象最终生成的图里组件关系奇怪比如“数据库直接调用前端”。根因分析LLM 的领域知识不足或幻觉LLM 可能对某些专业的架构原则理解有偏差。结构化输出约束不够强虽然要求输出 JSON但 JSON 中的connections字段可能包含了不合逻辑的连接。缺乏事后验证生成 JSON 后没有经过任何规则校验就直接绘图。解决方案知识增强在知识库中不仅存放组件介绍还要存放架构约束规则。例如可以有一个规则库“external类型的组件不能直接连接database类型的组件”。在 LLM 规划或最终输出前将这些规则作为上下文喂给它。输出后校验在将 JSON 送入渲染引擎前增加一个轻量级规则校验层。用一组简单的 if-else 规则或一个小的分类模型检查connections的合理性过滤掉明显违背常识的连接。提供修正机会将生成的架构图描述文本形式先展示给用户确认问一句“这是您想要的架构吗有没有需要调整的地方”让用户参与最终把关这比直接给一张可能有错的图要好。5.3 处理用户模糊、多变或错误的需求现象用户一开始说“要微服务”后来又说“好像单体也行”或者用户提出的技术组合本身存在冲突。根因分析Agent 被设计成“顺从”和“帮助”用户但当用户需求本身矛盾或低效时它可能无法做出专业反驳。解决方案构建“挑战”能力在工具集中增加一个challenge_assumption工具。当 LLM 检测到用户需求可能存在矛盾如“既要强一致性又要无限水平扩展”或明显反模式如“所有服务都直连数据库主库”时可以调用此工具。该工具会基于知识库生成一段友好的、专业的提醒文本例如“注意到您提到了‘强一致性’和‘无限扩展’。根据常见的分布式系统理论CAP 定理指出三者难以兼得。我们通常需要在一致性和扩展性之间做出权衡。您更看重哪一方面”记录需求变更历史在会话状态中不仅记录当前确认的需求也记录需求变更的轨迹。当用户频繁变更核心需求时Agent 可以主动总结“我看到您对架构风格的想法从微服务调整到了单体。为了确保我们方向一致我是否可以确认我们最终确定采用单体架构作为讨论基础” 这能帮助锁定范围避免无效讨论。5.4 成本失控与响应延迟现象随着用户增多API 调用费用暴涨且响应速度越来越慢。根因分析上下文无限增长没有做摘要和压缩每次对话都携带全部历史Token 消耗量呈线性甚至指数增长。工具调用冗余相同的问题被反复查询没有缓存。模型滥用所有步骤都用最贵的模型。解决方案结合4.2节的优化实施严格的上下文窗口管理如前所述采用分层记忆策略这是控制成本最有效的手段。为工具调用添加缓存特别是对于知识查询类工具。为查询结果建立缓存如使用 Redis设置合理的 TTL。相同的查询命中缓存后可以立即返回速度极快且成本为零。实施速率限制和负载均衡在 Agent 服务层对每个用户或每个 API Key 实施速率限制如每分钟最多发起 10 轮对话。同时确保你的后端渲染服务如 PlantUML 服务器有足够的实例来处理并发绘图请求避免这里成为瓶颈。设计并实现一个能进行多轮对话并生成架构图的 Agent是一个典型的系统工程它考验的不仅仅是对大语言模型 API 的调用更是对复杂工作流、状态管理和人机交互设计的综合能力。从简单的提示词工程到复杂的工具编排和状态维护每一步都需要在灵活性和可控性之间找到平衡。我个人的体会是启动一个可用的原型很快但打磨一个稳定、可靠、用户体验良好的生产级系统需要持续地迭代、观察和优化。最宝贵的经验往往来自于真实的用户对话日志多看看用户在哪里困惑、在哪里放弃那些地方就是你下一个需要重点改进的“价值洼地”。