AB
AiBoss
チュートリアル

为企业系统设计 AI 智能体:上下文、授权、日志与失败处理

チュートリアル

为企业系统设计 AI 智能体:上下文、授权、日志与失败处理

面向把 AI 智能体接入业务系统的开发者与架构师,讲清 LLM 之外的四个关键设计面:上下文与元数据管理、工具执行的授权校验、可追溯的结构化执行日志,以及以失败为前提的重试、限流与人工审批。含 Python 授权示例、日志字段示例与上线前测试清单。

把大模型接进业务系统,最常见的起点是「用户提问 → 调模型 → 返回答案」。这条链路能解决问答,却解决不了另一类需求:查内部数据、调外部 API、更新业务系统里的记录。一旦让 AI 智能体去执行这些动作,问题就从「回答得准不准」变成「它能碰什么、碰过什么、出错怎么办」。这篇教程面向要把智能体接入企业系统的开发者与架构师,按上下文管理、授权控制、执行日志、失败处理四条主线,给出可落地的结构、代码骨架与上线前的验证清单。文中涉及的具体产品能力、配额与价格,请以各厂商官网当前信息为准。

准备工作

在动手写代码之前,先把几件前置事项定下来。这些不是可选项,缺一项后面的设计都会返工。

  • 明确智能体要执行的动作清单。把「只读」和「写入」分开列。读取客户资料、查询报表属于只读;修改订单状态、删除记录、变更配置属于写入。写入类动作在后面的授权与审批环节要走更严格的路径。
  • 准备一个策略引擎或等价的授权判断模块。它需要能回答一个问题:某个主体(用户或智能体)对某个工具、某组参数,是否被允许。可以自研,也可以接入现有的策略组件,但接口要稳定。
  • 准备工具注册表。每个可被调用的工具都要有名字、参数结构、执行函数,以及它对应的资源类型。智能体只能从注册表里选工具,不能凭空构造调用。
  • 准备结构化日志的落点。日志要能按 trace_id 串起一次完整会话,并且支持按用户、工具、资源维度检索。
  • 准备模型调用凭据与业务系统凭据,并分开管理。模型侧凭据不应具备直接操作业务数据的能力,业务操作统一走应用层的工具执行通道。
  • 确认各依赖服务的超时与限流参数。这些数值直接决定后面重试策略怎么写,不要凭感觉设。

还有一项容易被忽略的准备:确定哪些操作必须人工确认。把这份清单在开发前就写出来,比上线后补审批流程成本低得多。

操作步骤

第一步:把智能体的整体结构画清楚

普通 LLM 应用的结构是线性的:输入进模型,答案出来。智能体的结构是多分支的:模型在中间做决策,但真正的数据访问和业务操作由应用侧执行。一个可用的分层大致是这样:

用户
  |
  v
Agent API
  |
  v
认证 / 权限确认
  |
  v
Agent Orchestrator
  +----> Context Retrieval(上下文检索)
  +----> LLM(决策与生成)
  +----> Tool Execution(工具执行)
           |
           +--> CRM API
           +--> Database
           +--> Reporting API
  |
  v
结果校验
  |
  v
返回用户

这个结构里最关键的一点:模型只是系统的一个部件,不是系统的决策终点。模型可以建议调用哪个工具、传什么参数,但「是否允许执行」由应用侧的授权层判断,「执行结果是否可信」由结果校验层判断。把模型的输出直接当作可执行指令,是这类系统最常见的设计错误。

第二步:设计上下文与元数据管理

智能体要完成业务动作,光有用户那句话不够。以生成一份销售报告为例,除了销售数据本身,还需要知道统计区间、所属组织、客户分类等条件。这些背景信息如何组织、如何检索,是上下文层的职责。

如果采用检索增强的方式提供上下文,要特别注意文档质量。典型问题是新旧版本的制度文件同时存在于索引中,模型可能引用已经失效的那一份。解决办法不是靠提示词提醒模型「注意版本」,而是在数据层面把元数据管起来。每份文档至少记录这些字段:

{
  "document_id": "policy-001",
  "title": "内部安全规程",
  "department": "information-security",
  "version": "3.0",
  "updated_at": "2026-10-01",
  "access_groups": ["security-team", "management"]
}

有了更新时间和版本号,检索时就能按时间过滤、按版本取最新;有了 access_groups,就能在检索阶段做权限裁剪。这里有一个必须强调的点:只把元数据存下来是不够的。检索逻辑本身必须带上权限条件,只返回当前用户有权查看的文档。如果检索层不做过滤,指望在返回答案前再拦一道,很容易在中间环节泄露内容。

上下文层还需要处理「关系」。业务数据之间往往有层级和归属关系,比如某个客户属于某个销售、某个销售属于某个部门。把这些关系整理清楚,模型才能拿到真正相关的片段,而不是一堆看似相关实则无关的文本。

第三步:在工具执行前做授权校验

智能体能连上 CRM,不等于它能读取全部客户信息。销售只能看自己负责的客户,管理者能看整个部门,这个边界必须在执行时生效,而不是写在提示词里。

下面是一个简化后的 Python 实现,核心思路是:执行前先问策略引擎,不允许就抛异常,允许才交给工具注册表执行。

def execute_agent_tool(principal, tool_name, arguments,
                       policy_engine, tool_registry):
    if not policy_engine.is_allowed(
        principal=principal,
        tool=tool_name,
        arguments=arguments,
    ):
        raise PermissionError("Tool execution is not permitted")
    return tool_registry.execute(tool_name, arguments)

这段代码刻意做得很短,实际系统里 is_allowed 需要综合考虑目标资源、操作类型、所属组织等条件。几个容易漏掉的细节:

  • 参数也要参与授权判断。同一个工具,参数不同风险不同。查询单个客户和批量导出全部客户,不应共用同一条放行规则。
  • 模型生成的参数不能当作可信输入。类型、取值范围、格式都要校验,非法值直接拒绝,不要尝试「修正后执行」。
  • 「建议」和「允许」是两件事。模型提出操作建议,授权层决定是否放行,这两个环节在代码里要分开,不能合并成一个函数。
  • 主体要能区分。是用户本人的身份在操作,还是智能体以某个服务身份在操作,两者的权限范围通常不同,日志里也要能分辨。

第四步:设计可追溯的执行日志

上线之后,最常被问到的问题是「它为什么拿到了这条数据」。要回答这个问题,日志不能只记最终结果,中间过程也要留下痕迹。一次工具调用至少应记录:

  • 是哪个用户发起的这次处理
  • 调用了哪个工具
  • 访问了哪个资源
  • 授权检查的结果是什么
  • 外部 API 返回了什么(在脱敏前提下)
  • 最终处理是否成功

把这些字段统一成结构化格式,排查问题时才好检索。一个示例记录:

{
  "trace_id": "trace-12345",
  "agent_id": "sales-assistant",
  "user_id": "user-001",
  "tool": "crm.read_customer",
  "resource_id": "customer-204",
  "authorization": "allowed",
  "status": "success",
  "duration_ms": 142
}

当一次会话连续调用多个工具时,让它们共用同一个 trace_id,就能把整条链路串起来。日志设计有两条硬约束:不要把个人敏感信息和认证令牌写进日志;授权结果要单独记录,只记成功调用会掩盖被拒绝的尝试,而后者恰恰是排查越权问题的关键线索。

第五步:以失败为前提设计容错

智能体不会每次都按预期工作。外部 API 会超时,模型可能选错工具,参数可能不合法。正常路径之外的处理逻辑要提前设计。

重试。临时性网络错误适合重试,但不是所有操作都能自动重跑。订单创建、支付这类写入操作如果简单重试,可能造成重复下单或重复扣款。写入类接口要按幂等性设计,让重复请求产生与单次请求相同的效果,再考虑是否允许自动重试。

执行次数与成本上限。智能体反复调用工具会拉长处理时间、推高 API 费用。设置最大步数、最长执行时间、费用上限,可以在异常循环发生时及时止损。这些上限值应结合业务场景确定,不要照搬别处的数字。

人工审批。删除客户资料、修改关键配置这类影响面大的操作,可以设计成「智能体提出方案 → 人工确认 → 系统执行」。这样既保留了自动化的效率,又把不可逆操作的决定权留在人手里。

第六步:上线前按清单验证

测试不能只看回答质量,工具执行和权限控制同样要覆盖。可以按下表逐项检查:

测试项确认内容
任务执行能否完成目标业务动作
工具选择能否选中正确的 API
参数校验非法值是否被拒绝
权限控制未授权的操作是否确实无法执行
错误处理API 故障时能否安全停止
执行限制步数与费用上限是否生效
日志管理执行过程能否被完整追踪
提示注入防护外部文档中的恶意指令能否绕过权限控制

不必一次全部实现。按业务重要度排优先级,但有一条底线:会修改外部系统数据的智能体,验证标准要比只读智能体严格。只读出错最多是信息不准,写入出错可能直接造成业务损失。

一个完整示例

下面把前面的环节串成一个最小可运行的骨架:一个销售助手智能体,接收用户请求,检索上下文,让模型决定调用哪个工具,经授权后执行,并记录日志。

import time
import uuid


def handle_request(user, query, orchestrator, policy_engine,
                   tool_registry, logger):
    trace_id = "trace-" + uuid.uuid4().hex[:8]

    # 1. 检索上下文(检索层内部已按 user 的权限过滤文档)
    context = orchestrator.retrieve_context(user=user, query=query)

    # 2. 让模型给出工具调用建议
    plan = orchestrator.plan(user=user, query=query, context=context)

    results = []
    for step in plan.steps:
        started = time.time()

        # 3. 参数校验:模型输出不作为可信输入
        if not tool_registry.validate(step.tool, step.arguments):
            logger.write({
                "trace_id": trace_id,
                "agent_id": "sales-assistant",
                "user_id": user.id,
                "tool": step.tool,
                "authorization": "invalid_arguments",
                "status": "rejected",
            })
            continue

        # 4. 授权检查
        allowed = policy_engine.is_allowed(
            principal=user,
            tool=step.tool,
            arguments=step.arguments,
        )
        if not allowed:
            logger.write({
                "trace_id": trace_id,
                "agent_id": "sales-assistant",
                "user_id": user.id,
                "tool": step.tool,
                "authorization": "denied",
                "status": "rejected",
            })
            continue

        # 5. 执行并记录
        try:
            output = tool_registry.execute(step.tool, step.arguments)
            status = "success"
        except Exception:
            output = None
            status = "error"

        logger.write({
            "trace_id": trace_id,
            "agent_id": "sales-assistant",
            "user_id": user.id,
            "tool": step.tool,
            "resource_id": step.arguments.get("resource_id"),
            "authorization": "allowed",
            "status": status,
            "duration_ms": int((time.time() - started) * 1000),
        })
        results.append(output)

    # 6. 结果校验后返回
    return orchestrator.finalize(query=query, results=results)

这个骨架里,几个设计点值得对照自己的实现检查一遍:

  • trace_id 在入口处生成,贯穿整次请求,后续所有工具调用共用。
  • 参数校验在授权之前,非法参数不会进入策略判断,减少无意义的判断开销。
  • 被拒绝的调用同样写日志,且 authorization 字段区分了「参数非法」和「权限不足」。
  • 执行异常被捕获后记录为 error,不会让整次请求崩溃,也不会静默吞掉。
  • 最终结果经过 finalize 校验才返回,模型生成的中间内容不直接透传给用户。

把这段骨架接到真实的工具注册表和策略引擎上,再补上最大步数限制与超时控制,就具备了进入测试环境的基础。

注意事项

  • 元数据不等于权限。文档上标了 access_groups,检索时仍必须按当前用户过滤。只在存储层记录权限、检索层不过滤,等于没有权限控制。
  • 提示注入是真实风险。外部文档、网页内容里可能夹带指令,试图让智能体绕过限制。防护手段不能只靠提示词,必须落在授权层:无论模型被引导去做什么,执行前都要过策略判断。
  • 写入操作的重试要谨慎。没有幂等保证的写入接口不要配置自动重试,否则可能产生重复记录或重复扣款。
  • 日志要脱敏。个人身份信息、认证令牌、完整的请求头都不应原样落盘。日志本身也是敏感数据,访问权限要单独控制。
  • 上限值要按业务定。最大步数、执行时长、费用上限没有通用推荐值,取决于业务复杂度与预算,需结合实际情况设定,并定期复核。
  • 只读与写入分级。读取类工具可以放宽自动化程度,修改类工具应引入更严格的校验甚至人工确认。
  • 模型与业务凭据分离。模型调用凭据不应具备直接操作业务数据的能力,避免绕过应用层控制。
  • 产品能力与计费随时变化。涉及具体模型、API 配额、价格的信息,以各厂商官网当前公布的内容为准,不要依赖本文或旧资料中的数字。

智能体的能力边界,最终不取决于模型多聪明,而取决于上下文取得对不对、操作放不放行、过程记不记得住、出错停不停得下来。这四件事在架构图上各占一块,缺任何一块,系统都很难放心地交给业务使用。