拓冰建站拓冰建站
首页 / 资讯中心 / 正文

构建高可靠AI Agent:基于状态机与分层架构的工程实践

在实际企业级 AI 应用开发中直接让大语言模型LLM处理所有决策和流程常常会引入不可控的风险。模型幻觉、上下文长度限制、API 调用不稳定以及高昂的成本都可能让一个看似智能的 AI Agent 在生产环境中变得脆弱不堪。微软的工程师们在构建高可靠 AI 系统方面积累了丰富的实战经验其核心思想并非让 LLM 成为唯一的“大脑”而是将其作为强大但受控的“推理引擎”嵌入到一个由确定性规则、状态机和外部工具构成的稳健架构中。本文旨在拆解这种高可靠 AI Agent 的设计哲学与实现架构。我们将从为什么需要约束 LLM 开始逐步深入到架构的核心组件、工作流设计、关键代码模式并最终给出一个可运行的示例项目结构。无论你是正在构建客服助手、数据分析 Agent 还是自动化流程引擎理解这套架构都能帮助你设计出更稳定、更可预测、更易于维护的 AI 应用。1. 为什么不能让 LLM 掌控全局从理想 Agent 到现实挑战一个理想的 AI Agent 被设想为能够理解复杂目标、自主规划并执行任务的全能助手。然而当我们将这种理想模型直接落地时会立刻遇到一系列工程现实问题。1.1 LLM 的固有局限性大语言模型本质上是基于概率生成文本的模型这决定了它在可靠性上的天花板。幻觉与事实性错误LLM 会生成看似合理但完全错误的信息。在需要精确数据如金额、日期、代码的场景这是致命缺陷。非确定性输出相同的输入可能产生略有不同的输出这对于需要严格一致性的业务流程如订单状态变更是不可接受的。有限的上下文与计算能力LLM 的上下文窗口Context Window再大也有上限它无法记住超长对话或复杂文档的所有细节也不擅长进行精确的数学计算或逻辑推理。延迟与成本每次调用 LLM API 都涉及网络延迟和 Token 成本。让 LLM 反复思考或生成冗长内容会导致应用响应慢且费用高昂。1.2 失控的 Agent 工作流如果让 LLM 完全自主地决定“下一步做什么”工作流很容易失控。循环与死锁Agent 可能陷入“思考-行动-再思考”的无限循环无法达成终态。工具滥用LLM 可能错误地调用工具传入非法参数或者在不必要时频繁调用高成本工具如搜索引擎 API。状态管理困难纯靠自然语言在对话中维护业务状态如购物车、审批进度极其脆弱容易因用户表达的歧义而丢失状态。1.3 工程化与维护的噩梦从软件工程角度看一个由 LLM 黑盒驱动的工作流难以调试、测试和版本控制。调试困难当流程出错时你很难定位是提示词Prompt的问题、工具的问题还是模型本身的问题。日志可能只是一串难以解析的自然语言。测试脆弱基于概率输出的系统其自动化测试的断言Assertion很难编写测试结果可能不稳定。版本升级风险更换 LLM 的版本或供应商即使提示词不变也可能导致整个 Agent 行为发生不可预知的变化。因此高可靠架构的核心转变在于将 LLM 从“全局控制器”降级为“特定环节的推理器”而由我们编写的确定性代码来掌控工作流的骨架和状态流转。2. 高可靠 AI Agent 架构核心分层与确定性控制微软工程师推崇的架构模式通常体现为一种分层或管道Pipeline设计核心是分离“决策逻辑”与“执行逻辑”并用确定性状态机来驱动流程。2.1 架构分层视图一个典型的高可靠 Agent 架构包含以下层次接口层Interface Layer接收用户输入文本、语音、文件并进行初步的标准化和路由。例如判断用户意图是“查询天气”还是“创建工单”。编排层Orchestration Layer / Controller这是架构的大脑由确定性代码如状态机、规则引擎构成。它根据当前状态和输入决定调用哪个工具或哪个 LLM 功能并管理整个工作流的状态State。能力层Capability Layer确定性工具Deterministic Tools封装所有可重复、无歧义的操作。如数据库查询、API 调用、计算器、文件读写等。这些工具由传统代码编写输入输出明确。LLM 功能LLM Functions将 LLM 的能力包装成一个个具体的、功能单一的“子程序”。例如extract_entities(input_text): - JSON从文本中提取结构化信息。classify_intent(input_text): - “intent_label”对用户意图进行分类。generate_sql(natural_language, schema): - SQL根据自然语言和数据库模式生成 SQL。judge_sentiment(input_text): - “positive/negative/neutral”判断情感倾向。状态与记忆层State Memory Layer持久化存储工作流状态、会话历史、工具执行结果等。这通常使用数据库或缓存实现而非依赖 LLM 的上下文。用户输入 | v [接口层] - 标准化、路由 | v [编排层] - 状态机/规则引擎 (确定性代码) | | |--- 根据状态选择下一步 ---| | | v v [能力层-确定性工具] [能力层-LLM功能] (数据库、API、计算) (提取、分类、生成) | | |-------- 执行 ------------| | | v v [状态与记忆层] - 更新状态、存储结果 | v 输出给用户2.2 关键设计模式状态机State Machine状态机是编排层的理想实现方式。它将复杂的对话或业务流程分解为一系列明确的“状态”State和“转换”Transition。状态State代表 Agent 在流程中所处的某个特定阶段。例如IDLE空闲、AWAITING_CONFIRMATION等待确认、EXECUTING_QUERY执行查询、HANDLING_ERROR处理错误。转换Transition定义在某个状态下接收到特定输入或事件后应该执行什么动作并转移到哪个新状态。转换逻辑由确定性代码编写。示例机票预订简化状态机初始状态: IDLE 事件: 用户说“我想订票” 动作: 调用 LLM 功能 extract_travel_info 提取出发地、目的地、时间 下一状态: AWAITING_DATES_CONFIRMATION 状态: AWAITING_DATES_CONFIRMATION 事件: 用户确认日期 动作: 调用确定性工具 search_flights 查询航班 下一状态: DISPLAYING_FLIGHTS 状态: DISPLAYING_FLIGHTS 事件: 用户选择航班 动作: 调用确定性工具 reserve_seat 占座 下一状态: AWAITING_PAYMENT ...在这个模型中LLM 仅在IDLE状态时被用于信息提取extract_travel_info。之后的流程流转、工具调用、状态更新全部由状态机代码控制确保了流程的确定性和可追溯性。3. 从理论到实践构建一个高可靠查询 Agent让我们通过一个具体的例子来实践上述架构。我们将构建一个“智能数据查询 Agent”它允许用户用自然语言提问Agent 将其转换为 SQL 并执行最后将结果用自然语言总结。这个流程完全受控避免 LLM 直接操作数据库。3.1 环境准备与项目结构假设我们使用 Python 作为主要语言并选择 LangChain 作为 LLM 应用框架因其生态丰富但我们的架构思想是框架无关的。环境依赖 (requirements.txt):langchain0.1.0 langchain-openai0.0.5 # 用于连接 OpenAI API openai1.0.0 sqlalchemy2.0.0 # 用于数据库连接和操作 pydantic2.0.0 # 用于数据验证和设置管理 python-dotenv1.0.0 # 管理环境变量项目结构:high_reliable_ai_agent/ ├── config/ │ ├── __init__.py │ └── settings.py # 配置管理API密钥、数据库URL ├── core/ │ ├── __init__.py │ ├── state.py # 状态机定义 │ ├── orchestrator.py # 编排层核心逻辑 │ └── memory.py # 状态与记忆层 ├── capabilities/ │ ├── __init__.py │ ├── deterministic_tools/ │ │ ├── __init__.py │ │ ├── db_query.py # 确定性工具执行SQL查询 │ │ └── schema_fetcher.py # 确定性工具获取数据库表结构 │ └── llm_functions/ │ ├── __init__.py │ ├── intent_classifier.py # LLM功能意图分类 │ ├── sql_generator.py # LLM功能生成SQL │ └── result_summarizer.py # LLM功能总结查询结果 ├── agents/ │ ├── __init__.py │ └── data_query_agent.py # Agent主类整合各层 ├── models/ # 数据模型 │ ├── __init__.py │ └── state_models.py └── main.py # 应用入口3.2 实现编排层状态机与控制器首先我们定义 Agent 的状态。在core/state.py中from enum import Enum from pydantic import BaseModel from typing import Optional, Any, Dict class AgentState(str, Enum): Agent 的有限状态集合 IDLE idle # 空闲等待用户输入 CLASSIFYING_INTENT classifying_intent # 正在分类意图 GENERATING_SQL generating_sql # 正在生成SQL EXECUTING_QUERY executing_query # 正在执行查询 SUMMARIZING_RESULT summarizing_result # 正在总结结果 ERROR error # 出错状态 class AgentContext(BaseModel): 贯穿整个工作流的上下文数据 current_state: AgentState AgentState.IDLE user_input: Optional[str] None classified_intent: Optional[str] None # 例如data_query, chitchat generated_sql: Optional[str] None query_result: Optional[Any] None # 数据库查询结果 final_response: Optional[str] None error_message: Optional[str] None class Config: arbitrary_types_allowed True接下来在core/orchestrator.py中实现状态机的转换逻辑。这是确定性代码的核心。from .state import AgentState, AgentContext from capabilities.llm_functions import intent_classifier, sql_generator, result_summarizer from capabilities.deterministic_tools import db_query, schema_fetcher import logging logger logging.getLogger(__name__) class Orchestrator: 编排器根据当前状态和上下文决定下一步行动 def __init__(self, db_engine): self.db_engine db_engine self.schema_info None # 缓存数据库模式信息 async def transition(self, context: AgentContext) - AgentContext: 执行状态转换 logger.info(fState transition: {context.current_state}) if context.current_state AgentState.IDLE: # 从空闲状态开始接收到用户输入后转向意图分类 if context.user_input: context.current_state AgentState.CLASSIFYING_INTENT else: context.error_message No user input provided in IDLE state. context.current_state AgentState.ERROR return context elif context.current_state AgentState.CLASSIFYING_INTENT: # 调用 LLM 功能进行意图分类 try: intent await intent_classifier.classify(context.user_input) context.classified_intent intent if intent data_query: context.current_state AgentState.GENERATING_SQL else: # 非查询意图直接生成回复并回到空闲 context.final_response fI understand you want to talk about {intent}. For now, Im focused on data queries. context.current_state AgentState.IDLE except Exception as e: logger.error(fIntent classification failed: {e}) context.error_message fFailed to classify intent: {e} context.current_state AgentState.ERROR return context elif context.current_state AgentState.GENERATING_SQL: # 调用 LLM 功能生成 SQL需要数据库模式信息 try: if self.schema_info is None: self.schema_info schema_fetcher.get_schema(self.db_engine) sql await sql_generator.generate( questioncontext.user_input, schemaself.schema_info ) # 这里可以添加 SQL 安全校验如禁止 DROP, DELETE 等 if self._is_sql_safe(sql): context.generated_sql sql context.current_state AgentState.EXECUTING_QUERY else: context.error_message Generated SQL query is not allowed for security reasons. context.current_state AgentState.ERROR except Exception as e: logger.error(fSQL generation failed: {e}) context.error_message fFailed to generate SQL: {e} context.current_state AgentState.ERROR return context elif context.current_state AgentState.EXECUTING_QUERY: # 调用确定性工具执行 SQL try: result db_query.execute(context.generated_sql, self.db_engine) context.query_result result context.current_state AgentState.SUMMARIZING_RESULT except Exception as e: logger.error(fQuery execution failed: {e}) context.error_message fDatabase query failed: {e} context.current_state AgentState.ERROR return context elif context.current_state AgentState.SUMMARIZING_RESULT: # 调用 LLM 功能总结结果 try: summary await result_summarizer.summarize( questioncontext.user_input, datacontext.query_result ) context.final_response summary context.current_state AgentState.IDLE # 流程结束回到空闲 except Exception as e: logger.error(fResult summarization failed: {e}) context.error_message fFailed to summarize results: {e} context.current_state AgentState.ERROR return context elif context.current_state AgentState.ERROR: # 错误状态处理记录日志并准备错误响应 logger.error(fAgent entered ERROR state. Context: {context.dict()}) # 可以在这里实现重试逻辑或通知管理员 if not context.final_response: context.final_response fAn error occurred: {context.error_message}. Please try again or rephrase your question. context.current_state AgentState.IDLE # 清理错误回到空闲 return context else: logger.error(fUnknown state: {context.current_state}) context.current_state AgentState.ERROR return context def _is_sql_safe(self, sql: str) - bool: 简单的 SQL 安全校验示例 forbidden_keywords [DROP, DELETE, UPDATE, INSERT, ALTER, TRUNCATE] upper_sql sql.upper() # 这是一个非常基础的检查生产环境需要更严格的策略 return not any(keyword in upper_sql for keyword in forbidden_keywords)3.3 实现能力层LLM 功能与确定性工具LLM 功能被封装为独立的、功能单一的模块。以capabilities/llm_functions/sql_generator.py为例from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate from langchain.output_parsers import PydanticOutputParser from pydantic import BaseModel, Field import os from config.settings import settings # 定义期望的输出结构 class GeneratedSQL(BaseModel): sql_query: str Field(descriptionThe generated SQL query string) reasoning: str Field(descriptionBrief reasoning behind the generated SQL) class SQLGenerator: def __init__(self): self.llm ChatOpenAI( modelsettings.LLM_MODEL, temperature0.1, # 低温度确保生成稳定 api_keysettings.OPENAI_API_KEY ) self.parser PydanticOutputParser(pydantic_objectGeneratedSQL) # 定义提示词模板 self.prompt_template ChatPromptTemplate.from_messages([ (system, You are a senior SQL expert. Given a database schema and a users question in natural language, generate a correct and efficient SQL query. Database Schema: {schema} {format_instructions} ), (human, Question: {question}) ]) async def generate(self, question: str, schema: str) - str: 生成 SQL 查询语句 try: # 格式化提示词 prompt self.prompt_template.format_messages( schemaschema, format_instructionsself.parser.get_format_instructions(), questionquestion ) # 调用 LLM response await self.llm.ainvoke(prompt) # 解析输出 parsed_output: GeneratedSQL self.parser.parse(response.content) print(f[SQL Generator Reasoning]: {parsed_output.reasoning}) # 记录推理过程便于调试 return parsed_output.sql_query except Exception as e: # 在这里可以加入重试逻辑或降级策略 raise Exception(fSQL generation failed: {e}) # 创建单例实例 sql_generator SQLGenerator()确定性工具则完全由传统代码实现。以capabilities/deterministic_tools/db_query.py为例import pandas as pd from sqlalchemy import text from sqlalchemy.engine import Engine import logging logger logging.getLogger(__name__) def execute(sql_query: str, engine: Engine, limit: int 100): 执行 SQL 查询并返回结果。 这是一个确定性函数输入相同 SQL 和数据库状态输出必然相同。 logger.info(fExecuting SQL: {sql_query}) try: with engine.connect() as connection: # 使用 text() 包装 SQL 语句是 SQLAlchemy 的安全实践 result_proxy connection.execute(text(sql_query)) # 获取列名 columns result_proxy.keys() # 获取数据限制行数防止过量数据 data result_proxy.fetchmany(limit) # 转换为字典列表便于后续处理 result [dict(zip(columns, row)) for row in data] logger.info(fQuery executed successfully, returned {len(result)} rows.) return result except Exception as e: logger.error(fDatabase error during query execution: {e}) # 向上抛出异常由编排层处理 raise3.4 组装与运行 Agent最后在agents/data_query_agent.py中组装所有组件from core.orchestrator import Orchestrator from core.state import AgentContext, AgentState from sqlalchemy import create_engine from config.settings import settings import asyncio class DataQueryAgent: 高可靠数据查询 Agent 主类 def __init__(self, database_url: str None): db_url database_url or settings.DATABASE_URL self.engine create_engine(db_url) self.orchestrator Orchestrator(self.engine) self.context AgentContext() async def query(self, user_input: str) - str: 处理一次用户查询 # 1. 初始化上下文 self.context AgentContext(current_stateAgentState.IDLE, user_inputuser_input) # 2. 循环执行状态机直到回到 IDLE 或超过最大步数 max_steps 10 for step in range(max_steps): old_state self.context.current_state self.context await self.orchestrator.transition(self.context) # 如果状态变为 IDLE流程正常结束 if self.context.current_state AgentState.IDLE: if self.context.final_response: return self.context.final_response else: return Process completed without a response. # 如果状态未变化且不是 ERROR可能陷入死循环主动跳出 if self.context.current_state old_state and self.context.current_state ! AgentState.ERROR: logger.warning(fState did not change after transition: {self.context.current_state}) break # 3. 异常处理循环超时或未返回结果 if self.context.final_response: return self.context.final_response elif self.context.error_message: return fThe query process encountered an issue: {self.context.error_message} else: return The agent is unable to process your request at this time. Please try again. # 使用示例 async def main(): agent DataQueryAgent() # 示例查询 response await agent.query(我们部门上个月销售额最高的产品是什么) print(response) if __name__ __main__: asyncio.run(main())4. 关键配置、验证与排错4.1 核心配置说明 (config/settings.py)高可靠 Agent 的配置需要外置化管理。from pydantic_settings import BaseSettings from pydantic import Field class Settings(BaseSettings): # LLM 配置 OPENAI_API_KEY: str Field(..., envOPENAI_API_KEY) LLM_MODEL: str Field(defaultgpt-3.5-turbo, envLLM_MODEL) # 可切换为 gpt-4 等 # 数据库配置 DATABASE_URL: str Field(..., envDATABASE_URL) # Agent 行为配置 MAX_AGENT_STEPS: int Field(default10, envMAX_AGENT_STEPS) SQL_EXECUTION_ROW_LIMIT: int Field(default100, envSQL_EXECUTION_ROW_LIMIT) class Config: env_file .env # 从 .env 文件加载配置 settings Settings()对应的.env文件OPENAI_API_KEYyour_openai_api_key_here DATABASE_URLpostgresql://user:passwordlocalhost:5432/mydatabase LLM_MODELgpt-3.5-turbo4.2 运行验证与结果分析运行main.py后通过日志和输出验证 Agent 工作流。正常流程验证输入一个明确的查询如“列出所有在库的产品”。观察控制台日志应该能看到状态按IDLE - CLASSIFYING_INTENT - GENERATING_SQL - EXECUTING_QUERY - SUMMARIZING_RESULT - IDLE顺序流转。最终输出应为 LLM 总结的自然语言结果。意图分类验证输入“你好”日志应显示意图被分类为非data_query如chitchat然后直接返回相应回复并回到IDLE状态不会进入 SQL 生成和执行环节。错误处理验证输入一个会导致生成危险 SQL如包含 DROP的问题或模拟数据库连接失败。观察 Agent 是否进入ERROR状态并返回友好的错误信息而不是崩溃或执行危险操作。4.3 常见问题排查清单问题现象可能原因检查步骤解决方案Agent 无响应或卡住状态机循环未终止LLM API 调用超时。1. 检查日志看状态是否在非IDLE/ERROR状态间循环。2. 检查网络和 API 密钥。1. 在编排层transition方法中添加最大循环次数限制。2. 为 LLM 调用设置超时timeout和重试机制。SQL 生成错误或不符合预期提示词Prompt不清晰数据库模式Schema信息不完整或格式不对。1. 打印出传递给 LLM 的完整提示词。2. 检查schema_fetcher获取的模式信息是否准确。1. 优化提示词明确指定表名、列名和关系。2. 在模式信息中包含示例数据或数据类型。数据库查询结果为空生成的 SQL 逻辑错误数据库中没有匹配数据。1. 在sql_generator中打印 LLM 的推理过程reasoning。2. 手动在数据库客户端执行生成的 SQL。1. 在提示词中要求 LLM 先解释其推理便于调试。2. 考虑在 Agent 回复中增加“未找到相关数据”的明确提示。流程总是进入 ERROR 状态某个能力模块LLM 功能或工具抛出未处理异常。1. 查看ERROR状态前的日志定位是哪个状态转换失败。2. 检查该状态对应的能力模块的异常处理。1. 在每个能力模块内部进行更细致的异常捕获和日志记录。2. 在编排层为不同错误类型设计不同的恢复或降级路径。处理速度慢LLM API 调用是主要瓶颈数据库查询慢。1. 使用异步async/await并发调用可并行的步骤。2. 分析数据库查询性能。1. 对于不严格串行的步骤如获取 Schema 和分类意图可以考虑并行化。2. 对数据库查询添加索引或对结果进行缓存。5. 生产环境最佳实践与扩展方向将上述示例部署到生产环境还需要考虑更多维度。5.1 安全性加固SQL 注入防御示例中的_is_sql_safe方法非常基础。生产环境必须使用参数化查询SQLAlchemy 的text()结合绑定参数、严格的允许操作白名单如只允许 SELECT、或在沙箱环境中执行 SQL。LLM 输出净化对 LLM 生成的任何内容SQL、总结文本进行过滤防止其返回恶意代码或不当内容。权限控制Agent 使用的数据库账户应只有最小必要权限如只读权限。输入输出限流与审计记录所有用户输入、生成的 SQL、查询结果和最终输出用于审计和模型改进。5.2 可观测性与监控结构化日志记录每个状态转换、LLM 调用包括输入 Token、输出 Token、耗时、工具调用结果和错误信息。使用 JSON 格式便于收集到日志系统如 ELK。关键指标监控请求量、成功率、错误率。各状态平均处理时间尤其是 LLM 调用耗时。Token 消耗量成本监控。链路追踪Tracing为每个用户会话分配唯一 ID并在整个调用链中传递便于追踪一个请求在所有微服务和组件中的流转情况。5.3 性能与成本优化缓存策略LLM 结果缓存对相同的提示词输入进行缓存避免重复调用。数据库模式缓存模式信息通常不变可以长时间缓存。查询结果缓存对相同的 SQL 查询结果进行短期缓存。LLM 调用优化模型选型非核心推理任务使用小型/廉价模型。流式响应对于长文本生成使用流式接口改善用户体验。降级方案当主要 LLM 服务不可用时有备用的规则或本地模型可以接管部分功能。异步与并发利用 Python 的asyncio让 I/O 密集型操作如 LLM API 调用、数据库查询并发执行减少整体延迟。5.4 架构扩展方向多 Agent 协作本架构中的单个 Agent 可以作为一个“技能”。可以引入一个“主控 Agent”同样基于状态机来根据用户请求路由到不同的技能 Agent查询 Agent、文档分析 Agent、代码生成 Agent。动态工具注册能力层的工具可以设计为可插拔的系统在启动时自动发现并注册可用的工具使 Agent 能力能够动态扩展。人类在环Human-in-the-loop在状态机中引入AWAITING_HUMAN_APPROVAL状态。当 LLM 生成的内容置信度低或工具执行涉及关键操作时暂停流程并等待人工审核确认。强化学习微调收集状态、动作和最终用户满意度的数据用于微调决策模型编排层使其能学习到更优的状态转换策略。高可靠 AI Agent 架构的本质是软件工程原则在 AI 时代的应用通过分层、解耦、确定性的控制流来管理不确定性。将 LLM 视为一个强大的、但需要被“编程”和“约束”的组件而不是全能的魔法黑盒是构建能够真正服务于生产环境、承担关键业务责任的智能系统的必由之路。从定义一个清晰的状态机开始逐步封装 LLM 的能力并用坚实的代码搭建起工作流的骨架你的 AI Agent 将不再是一个脆弱的原型而是一个值得信赖的工程系统。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门