尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

AI Agent业务上下文装配:从Prompt工程到权限过滤的完整实践

AI Agent业务上下文装配:从Prompt工程到权限过滤的完整实践 在实际的 AI Agent 项目里有一个比模型选型更隐蔽的问题模型本身没有业务上下文。Odyssey Framework 要解决的正是这个问题——给 AI 提供它需要的业务上下文让大模型从“什么都懂一点”变成“懂你这家公司的用户、订单、规则和边界”。这篇文章会把这套思路拆开从一个客服问答场景出发先讲清楚业务上下文为什么重要再带你实现一个最小可运行的上下文装配链路最后给出参数配置、排查方法和生产环境建议。如果你正在做智能客服、企业知识库问答、RAG 应用或者 AI Agent 后端读完这篇文章后可以明确回答三个问题上下文到底要装什么、从哪里来、怎么安全地交给模型。1. 先用一个具体场景理解“业务上下文”到底缺什么1.1 一个客服 Agent 的真实困境假设你要做一个电商客服 Agent。用户发来一句很常见的消息“我这个订单为什么还没发货你们怎么这么慢”这句话单看没有任何业务信息。模型不知道用户是谁不知道用户说的“这个订单”对应哪个订单号不知道订单在哪个仓库不知道当前物流节点也不知道企业承诺的发货时效。如果直接把这句话发给大模型模型只能给出非常通用的回答比如“很抱歉给您带来不便我们正在催促仓库尽快发货”。这种回答不是不能用但它没有解决问题的实际价值。用户真正想知道的是他的订单会在什么时候发货、为什么会延误、有没有办法优先处理。Odyssey Framework 的核心思想就是把回答这个问题需要的背景信息提前准备好再交给模型。这个背景信息就是“业务上下文”。模型需要哪些上下文可以拆成几类上下文类型具体内容缺了会怎样用户上下文用户ID、姓名、会员等级、所属租户无法区分用户无法做个性化答复订单上下文订单号、状态、金额、下单时间、预计发货时间不知道用户问的是哪一笔订单业务规则发货时效、会员优先规则、退款规则回答内容可能与公司实际政策冲突工具上下文可调用的 API、查询参数、操作权限模型无法主动查物流、改地址会话上下文用户刚才问了什么、已经回答过什么多轮对话经常重复提问或前后矛盾1.2 为什么“把资料都塞进 Prompt”不可行很多团队刚开始做 AI 应用时第一个想法是把用户数据、商品信息、公司制度全部拼进 Prompt 里模型不就有上下文了吗这个思路方向没错但直接拼接在真实项目里走不通原因有四条。第一是 token 天花板。企业级数据量远远超过模型上下文窗口。一个中型电商的订单表可能有几千万行员工的权限列表可能也有几十万条不可能一次性塞入。第二是权限风险。不同用户能看到的数据不同。如果系统把所有用户的订单、所有内部备注都拼进 Prompt模型即使不主动泄露也很可能在回答中引用到其他用户的内容。权限过滤必须在模型调用之前完成。第三是数据时效。订单状态、物流轨迹、库存数量都在实时变化。把静态文本拼进 Prompt用户问第二次时数据就过期了。第四是排障困难。所有信息混在一大段文本里模型回答错了你很难判断是哪条上下文导致的。你需要的是每条上下文都可追踪、可过滤、可审计。1.3 Odyssey Framework 的核心思路把上下文变成一条装配管线Odyssey Framework 没有把这些信息当作“一段文本”去管理而是把它当作一条“上下文装配管线”Context Pipeline。一次请求进入系统后框架会按固定流程处理识别当前用户和会话。从数据库、接口、缓存中加载用户信息、订单信息、权限信息。根据用户问题补充相关的业务规则和可调用工具。执行权限过滤删除当前用户无权看到的数据。按优先级剪裁上下文长度。把最终结构化上下文渲染成模型输入。这样做最直接的好处是你不是在“尝试写一个更好的 Prompt”而是在“搭建一套可编程、可测试、可观测的业务上下文系统”。这也是 Odyssey Framework 这个名字想表达的意思模型像一位进入陌生业务现场的访问者框架负责给它准备好地图、资料和边界规则。2. 理解 Odyssey 的上下文编排模型2.1 五个核心组件Odyssey Framework 的上下文编排模型可以归纳为五个核心组件下面用一张表说明每个组件的作用和常见实现方式。组件职责常见实现方式Context Loader从数据库、Redis、外部服务加载原始数据SQL 查询、HTTP 调用、RPC 调用、缓存读取Business Rule Engine把企业线下规则转成模型可理解的约束规则表、配置文件、Python 函数Tool Registry注册 Agent 可以调用的工具和方法JSON Schema、OpenAPI 描述、函数注册表Session Memory保存当前会话的关键信息避免重复查询Redis、内存缓存、向量数据库Policy Filter负责脱敏、权限校验、租户隔离装饰器、中间件、独立过滤层在实际项目中大多数团队会把前三个组件做得很重却忽视了 Policy Filter。这是很危险的。因为模型本身没有判断数据边界的能力你给它的上下文越多它越容易把不该展示的数据“顺手”放进回答里。2.2 一次请求的上下文装配流程整个装配流程可以在代码里描述成下面这样。这不是可运行代码而是用来说明执行顺序的伪代码。request(user_id, query) - 解析请求得到 user_id、session_id、raw_query - 并行加载基础上下文 user_context load_user(user_id) session_context load_session(session_id) - 根据 query 意图加载业务数据 orders load_orders(user_id) inventory load_inventory(order_ids) - 注入业务规则 rules build_business_rules(user_context, orders) - 注册工具 tools register_tools([query_logistics, apply_refund]) - 执行 Policy Filter filtered apply_policy(user, orders, tools, current_user_id) - 渲染成模型输入 prompt render_prompt(filtered) - 调用模型 reply call_llm(prompt) - 记录审计日志 write_audit_log(user_id, filtered, reply)这个顺序很重要尤其要注意Policy Filter 必须放在渲染 Prompt 之前。一旦数据进入 Prompt模型就有可能把它输出到回答里届时再拦截就晚了。3. 环境准备和最小项目结构3.1 技术栈和依赖为了让整条链路可运行下面会用一个最小示例来演示。示例采用 Python 技术栈因为 Python 在 AI 工程领域生态最成熟调试也直观。依赖用途Python 3.11运行环境FastAPI提供 HTTP 接口uvicorn启动 Web 服务pydantic定义上下文数据结构pydantic-settings读取环境变量openai调用 OpenAI 兼容接口如果原始项目没有给出明确版本落地前要先确认本地 Python 版本和 pip 源。下面这份requirements.txt可以用于快速搭建fastapi0.110.0 uvicorn0.29.0 pydantic2.6.0 pydantic-settings2.2.0 openai1.30.03.2 项目目录结构推荐使用下面的目录结构。它把数据加载、业务规则、权限过滤和 Prompt 渲染拆开方便后续替换真实数据源。odyssey-mini/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置读取 │ ├── models.py # 上下文数据模型 │ ├── loaders.py # 数据加载器 │ ├── business_rules.py # 业务规则 │ ├── policy.py # 权限过滤 │ ├── pipeline.py # 上下文装配管线 │ └── prompt.py # Prompt 渲染 ├── requirements.txt └── .env.example3.3 环境变量和配置创建一个.env.example文件把可能变化的参数都放在里面。示例项目没有配置 OpenAI Key 时也能运行以便先验证上下文装配逻辑配置 Key 后再接入真实模型。LLM_API_KEY LLM_BASE_URL LLM_MODELgpt-4o-mini CONTEXT_TIMEOUT_MS2000 MAX_CONTEXT_CHARS6000 ENABLE_POLICY_FILTERtrue CACHE_TTL_SECONDS60对应的config.py实现如下from pydantic_settings import BaseSettings class Settings(BaseSettings): llm_api_key: str llm_base_url: str llm_model: str gpt-4o-mini context_timeout_ms: int 2000 max_context_chars: int 6000 enable_policy_filter: bool True cache_ttl_seconds: int 60 class Config: env_file .env settings Settings()这里把配置集中在一个类里后续调整上下文超时、长度上限时不用改动业务代码。4. 动手实现一个最小上下文装配链路4.1 定义上下文数据模型上下文的结构越清晰后面的权限过滤和 Prompt 渲染就越容易。这里用 pydantic 定义UserContext、OrderInfo和BusinessContext。from enum import Enum from typing import Literal, Optional from pydantic import BaseModel, Field class UserLevel(str, Enum): NORMAL normal VIP vip class UserContext(BaseModel): user_id: str name: str level: UserLevel tenant_id: str email: str class OrderInfo(BaseModel): order_id: str user_id: str status: str amount: float created_at: str estimated_ship_at: str item_title: str class BusinessContext(BaseModel): user: Optional[UserContext] None orders: list[OrderInfo] Field(default_factorylist) business_rules: list[str] Field(default_factorylist) tool_schemas: list[dict] Field(default_factorylist) raw_query: str 数据模型要尽量窄只保留模型回答必需的关键字段。不要把整张订单表的所有列都塞进去字段越多越容易泄露内部信息。4.2 实现数据加载器loaders.py里实现用户信息和订单信息的加载。示例中用 mock 数据代替真实数据库生产环境可以替换为 SQL、HTTP 或 RPC 调用。import asyncio from models import OrderInfo, UserContext, UserLevel async def load_user(user_id: str) - UserContext: # 生产环境替换为数据库查询或用户中心 API await asyncio.sleep(0.01) return UserContext( user_iduser_id, name张明, levelUserLevel.VIP, tenant_idtenant_001, emailzhangmingexample.com, ) async def load_orders(user_id: str) - list[OrderInfo]: # 生产环境替换为订单服务 SDK 或 SQL 查询 await asyncio.sleep(0.02) return [ OrderInfo( order_idSO202507130001, user_iduser_id, statuspaid, amount1299.00, created_at2025-07-13 10:20:00, estimated_ship_at2025-07-15 18:00:00, item_title人体工学椅, ) ]两个加载函数都声明为async并且内部有模拟耗时。这样在装配管线里可以用asyncio.gather并行执行减少整体响应时间。4.3 实现业务规则注入业务规则不能只写在产品文档里必须变成模型可以读取的结构化文本。下面是一个最简单的规则构造器from models import OrderInfo, UserContext, UserLevel SHIPMENT_RULES [ 已支付订单默认在 48 小时内发货。, VIP 用户订单优先安排发货。, 偏远地区配送时间可能延长 1 到 2 天。, ] def build_business_rules(user: UserContext, orders: list[OrderInfo]) - list[str]: rules list(SHIPMENT_RULES) if user.level UserLevel.VIP: rules.append(本次咨询用户是 VIP答复时可以告知优先处理通道。) if any(order.status paid for order in orders): rules.append(当前存在已支付但未发货订单需要先说明发货时间承诺再提供查询物流入口。) return rules这里的关键点在于业务规则和商品数据是分开管理的。因为规则常变而历史订单数据基本不变。把规则抽成独立模块后运营修改发货策略时只需要改一处。4.4 实现权限过滤权限过滤是整个上下文装配链路里最不能省的一步。下面实现两个最基本的动作数据越权校验和邮箱脱敏。from models import BusinessContext, UserContext def apply_policy(ctx: BusinessContext, current_user_id: str, is_admin: bool False) - BusinessContext: if not is_admin and ctx.user is not None and ctx.user.user_id ! current_user_id: raise PermissionError(user context mismatch) if ctx.user is not None: ctx.user.email mask_email(ctx.user.email) # 非管理员只能看到自己的订单 if not is_admin: ctx.orders [order for order in ctx.orders if order.user_id current_user_id] return ctx def mask_email(email: str) - str: if not in email: return email local, domain email.split(, 1) visible local[:2] if len(local) 2 else local return f{visible}***{domain}实际项目中Policy Filter 还要处理更多场景比如租户隔离、角色权限、内部成本字段删除等。这里只演示最核心的逻辑进入 Prompt 之前把不该出现的数据直接去掉。4.5 装配管线、Prompt 渲染和模型调用pipeline.py是整条链路的编排入口。它先并行加载用户和订单再注入业务规则然后执行权限过滤最后渲染 Prompt。import asyncio import time from business_rules import build_business_rules from config import settings from loaders import load_orders, load_user from models import BusinessContext from policy import apply_policy from prompt import render_prompt async def build_context(user_id: str, query: str) - BusinessContext: start time.perf_counter() user, orders await asyncio.gather( load_user(user_id), load_orders(user_id), ) ctx BusinessContext(useruser, ordersorders, raw_queryquery) ctx.business_rules build_business_rules(user, orders) if settings.enable_policy_filter: ctx apply_policy(ctx, current_user_iduser_id) ctx.tool_schemas [ { name: query_logistics, description: 查询订单物流轨迹, parameters: {order_id: string}, } ] elapsed_ms (time.perf_counter() - start) * 1000 print( f[context] build finished, fsourcesuser,orders,business_rules,tool_schemas, elapsed_ms{elapsed_ms:.1f} ) return ctx def call_llm(prompt: str) - str: if not settings.llm_api_key: return 未配置 LLM_API_KEY已跳过真实模型调用。上下文装配成功接下来模型会基于订单信息和发货规则回答。 from openai import OpenAI client OpenAI( api_keysettings.llm_api_key, base_urlsettings.llm_base_url or None, ) response client.chat.completions.create( modelsettings.llm_model, messages[{role: system, content: prompt}], temperature0.2, ) return response.choices[0].message.content async def chat(user_id: str, query: str) - str: ctx await build_context(user_id, query) prompt render_prompt(ctx) print(prompt) return call_llm(prompt)prompt.py负责把BusinessContext渲染成最终发给模型的文本。from models import BusinessContext def render_prompt(ctx: BusinessContext) - str: user ctx.user order_lines \n.join( f- 订单 {order.order_id}状态 {order.status} f金额 {order.amount}下单时间 {order.created_at} f预计发货 {order.estimated_ship_at}商品 {order.item_title} for order in ctx.orders ) rules \n.join(f- {rule} for rule in ctx.business_rules) tools \n.join(f- {tool[name]}: {tool[description]} for tool in ctx.tool_schemas) prompt f 你是一名企业客服助手。请严格基于下面的业务上下文回答用户问题。不要假设上下文之外的数据。 如果上下文信息不足以回答请明确说需要哪些信息。 【当前用户】 用户ID: {user.user_id} 姓名: {user.name} 等级: {user.level} 邮箱: {user.email} 【订单信息】 {order_lines or 无} 【业务规则】 {rules or 无} 【可调用的工具】 {tools or 无} 【用户问题】 {ctx.raw_query} 请用简洁、专业的中文回复。 return prompt.strip()最后是 FastAPI 入口main.pyfrom fastapi import FastAPI from pydantic import BaseModel from pipeline import chat app FastAPI(titleodyssey-mini) class ChatRequest(BaseModel): user_id: str message: str class ChatResponse(BaseModel): reply: str app.post(/api/v1/chat, response_modelChatResponse) async def chat_api(req: ChatRequest): reply await chat(req.user_id, req.message) return ChatResponse(replyreply)到这一步最小链路已经完整请求进入接口加载上下文注入规则过滤权限渲染 Prompt调用模型。5. 运行验证从日志看上下文是否真正生效5.1 启动服务在项目根目录执行pip install -r requirements.txt uvicorn app.main:app --reload --port 8000启动后FastAPI 会监听8000端口。不要只验证服务能启动还要验证一次完整请求是否输出了正确的上下文。5.2 发一个测试请求打开另一个终端发送一条标准客服问题curl -X POST http://127.0.0.1:8000/api/v1/chat \ -H Content-Type: application/json \ -d {user_id:user_001,message:我的订单为什么还没发货}如果没有配置LLM_API_KEY接口会返回一条提示说明真实模型调用被跳过{ reply: 未配置 LLM_API_KEY已跳过真实模型调用。上下文装配成功接下来模型会基于订单信息和发货规则回答。 }5.3 检查日志中的上下文来源服务端控制台会打印两段关键日志。第一段说明装配完成[context] build finished, sourcesuser,orders,business_rules,tool_schemas, elapsed_ms31.2第二段是渲染后的完整 Prompt。这段 Prompt 里应该能看到当前用户、订单信息、业务规则和工具描述。如果这段 Prompt 里出现空列表说明上下文加载链路没有生效。5.4 对比无上下文和有上下文的效果差异为了直观理解业务上下文的作用可以对比两版 Prompt。无上下文时的模型输入相当于你是一名企业客服助手。用户问题我的订单为什么还没发货请回答。有上下文时的模型输入则包含【当前用户】 user_001张明vip 【订单信息】 SO202507130001statuspaid预计发货 2025-07-15 18:00:00 【业务规则】 已支付订单默认在 48 小时内发货。VIP 用户订单优先安排发货。后者会让模型知道用户是 VIP、订单已支付但尚未到 48 小时承诺期、有权告知优先处理通道。前者只能给出泛泛的安抚话术。这就是业务上下文在工程上的实际收益。6. 关键参数和配置详解6.1 上下文超时时间参数名CONTEXT_TIMEOUT_MS默认2000毫秒。这个参数控制数据加载器的总超时时间。调小会让系统更快失败避免用户长时间等待但也可能导致订单接口偶发抖动时上下文不完整。调大能提高数据完整性但会增加接口响应时间。推荐做法本地调试用2000生产环境根据实际接口 P99 延迟设置。超时后应该有降级逻辑比如跳过非关键上下文而不是直接报错。6.2 上下文长度上限参数名MAX_CONTEXT_CHARS默认6000字符。模型上下文窗口虽然越来越大但并不是越长越好。上下文越长模型越容易受到无关信息干扰成本和延迟也会上升。建议把上下文按优先级排序用户问题。当前订单或当前文档。关键业务规则。可调用工具。可选背景资料。超过长度上限时从低优先级开始丢弃保证最核心信息始终进入模型。6.3 缓存策略参数名CACHE_TTL_SECONDS默认60秒。用户基础信息、组织架构这类低频变化数据适合缓存。订单状态、实时库存这类高频变化数据不适合缓存。在真实项目里不要对所有 Loader 使用同一套缓存策略建议为每个 Loader 单独配置 TTL。6.4 权限过滤开关参数名ENABLE_POLICY_FILTER默认true。这个开关只建议在本地开发环境关闭方便调试数据结构。生产环境必须保持开启并且建议在 CI 里加入权限过滤的测试用例防止后续改动出现越权回归。下面用一张表汇总关键参数参数默认值含义调低影响调高影响CONTEXT_TIMEOUT_MS2000上下文装配总超时失败率升高响应时间变长MAX_CONTEXT_CHARS6000进入 Prompt 的文本上限信息可能被裁掉token 消耗和成本上升CACHE_TTL_SECONDS60低频数据缓存时长数据更新及时但压力大数据过期且占用缓存ENABLE_POLICY_FILTERtrue是否执行权限过滤本地调试方便更安全但增加过滤耗时生产环境还需要额外考虑配置外置化不要把数据库连接串和模型 API Key 直接写进代码或镜像。建议使用环境变量或配置中心管理。7. 常见问题排查7.1 模型回答仍然像没有业务上下文现象Prompt 已经打印出来但模型回答还是泛泛而谈。排查顺序先检查build_context日志中sources是否包含预期来源。再检查render_prompt输出的文本确认订单、规则确实在 Prompt 中。确认模型版本是否支持足够长的输入是否有内容被系统截断。检查温度设置如果温度过高模型可能不严格遵循上下文约束。建议降到0.2以下。一个很常见的坑是上下文装配成功但 Prompt 渲染时把关键字段拼错了比如从ctx.user.name取不到值渲染成空字符串。出现这种情况时优先查看渲染后的完整文本不要只看控制台的缩短日志。7.2 上下文装配太慢现象日志显示elapsed_ms达到几百毫秒甚至上千毫秒。常见原因有三个Loader 串行执行没有使用asyncio.gather。某个接口超时没有设置默认值。高延迟接口被放在关键路径上。处理方式把互相独立的 Loader 改为并行执行。给每个 Loader 设置独立的超时时间。对非关键上下文做异步降级比如用户历史偏好加载失败时不阻断主流程。7.3 用户 A 看到了用户 B 的订单数据这是最严重的问题通常不是模型导致的而是上下文装配阶段已经发生越权。检查路径Loader 是否只按当前user_id查询数据。后端服务是否验证了身份令牌中的用户和请求参数中的用户一致。Policy Filter 是否真的在渲染前执行而不是只写在代码里没有被调用。测试环境是否用多个账号验证过数据隔离。防止越权的关键原则是兜底过滤永远不能依赖模型判断。凡是会进入 Prompt 的数据后端必须先过滤。7.4 上下文太长导致 token 超限现象调用模型接口时报token limit exceeded或类似错误。处理方式遵循“先核心、后补充”的顺序截断。优先保留订单号、状态、发货时间裁剪商品备注和内部字段。对列表型数据先取最近的 N 条而不是全部返回。如果确实需要长文本先做摘要再把摘要放入上下文。下面的表格汇总了这几类高频问题问题现象常见原因检查方式处理建议回答没有业务信息上下文未装配或渲染遗漏看 build_context 日志和渲染后的 Prompt修复 Loader 或 render_prompt上下文装配慢Loader 串行、接口超时看 elapsed_ms 和每个 Loader 耗时改为并行、设置超时和降级跨用户数据泄露身份校验缺失或过滤未生效用多个账号做权限回归测试在渲染前强制执行 Policy Filtertoken 超限上下文过长且无截断检查 Prompt 字符数和模型限制按优先级截断保留核心字段8. 生产环境最佳实践与扩展方向8.1 学习环境和生产环境的差距上面的最小示例可以在本地快速跑通但生产环境不能直接照搬。下面列出两者的关键差异关注点学习环境生产环境数据源mock 数据真实数据库、微服务 API、消息队列配置环境变量配置中心、密钥管理、动态刷新日志print 输出结构化日志、链路追踪、指标监控权限简单 user_id 校验SSO、RBAC、租户隔离、审计日志模型调用无 Key 可跳过限流、重试、熔断、敏感词过滤缓存不需要多级缓存、缓存失效和预热回滚重启服务版本发布、开关控制、灰度发布生产环境的上下文装配链路本质上是一个有性能要求、有安全边界、有审计需求的数据服务。不能只把它当成“拼 Prompt 的临时脚本”。8.2 可复用的上下文工程检查清单在每次上线或评审上下文改动时可以按下面这份清单检查[ ] 每个 Loader 的数据来源都有日志记录。[ ] 用户身份校验发生在所有上下文加载之前。[ ] 权限过滤在渲染 Prompt 之前执行且有单元测试覆盖。[ ] 上下文超时和降级策略已经配置。[ ] 上下文长度上限和截断顺序明确。[ ] 进入 Prompt 的数据不包含 API Key、数据库连接串、内部成本字段。[ ] 模型回答可追踪到对应上下文来源。[ ] 缓存策略区分低频和高频数据。[ ] 有至少两个不同权限账号的端到端测试用例。[ ] 上下文装配耗时已接入监控和告警。这份清单可以直接贴进项目的代码评审模板里避免上下文相关的改动只凭“我感觉没问题”就上线。8.3 扩展方向Odyssey Framework 的核心思想打通之后可以朝几个方向深入。第一个方向是接入 RAG 检索。当业务知识超过上下文窗口时可以用向量数据库召回与用户问题相关的文档片段再作为 Context Loader 的一种补充来源。第二个方向是增强工具调用。让模型不只回答问题还能通过 Tool Registry 调用查物流、修改地址、申请退款等操作并在操作前后继续校验权限。第三个方向是会话记忆落地。把 Session Memory 从简单缓存升级为短期记忆和用户偏好画像多轮对话时只补充增量上下文而不是每次重复拼接全量数据。第四个方向是观测和调优。为每条上下文记录来源标识例如sourceorder_service、sourcebusiness_rule在模型回答不符合预期时可以通过日志快速定位是哪一条上下文造成的。最终需要记住的技术判断是给大模型更强的模型能力不如给它更准确的业务上下文。Odyssey Framework 这类方案的价值不是帮你写一个更长的 Prompt而是把“让 AI 理解业务”变成一条可持续维护、可安全执行、可快速排查的工程链路。新手实践时从最小客服场景开始先把一条上下文链路跑通再加入权限、缓存、监控和工具调用逐步完善。
返回列表