简介:从静态依赖注入到动态状态管理,再到跨会话持久化,上下文并非单一概念。本文拆解运行时上下文的多维分类,对比三种上下文管理方式的差异、适用场景与选型依据,帮助开发者在构建AI应用时准确区分并合理选用上下文机制。
在构建现代软件系统,尤其是涉及大语言模型的应用时,”上下文”这个词被频繁使用,却常常指代完全不同的东西。有人说到上下文,指的是代码运行所需的依赖项;有人指的是传递给模型的提示词数据;还有人指的是对话历史记录。这种概念混淆会直接导致架构设计上的误判——把本该注入的依赖硬编码进提示词,或者把本该持久化的数据只放在单次请求的内存里。
因此,理解上下文的关键不在于记住一个定义,而在于掌握它的两个核心分类维度:可变性和生命周期。这两个维度交叉组合,形成了四种逻辑上的上下文类型,而其中三种在实际工程中最为常见,分别对应静态运行时上下文、动态运行时状态和动态跨对话存储。
本文将对这三种上下文管理方式进行系统对比,帮助读者在不同场景下做出合理的技术选择。
静态运行时上下文是指在应用程序启动或单次调用开始时传入的、在整个执行过程中不会发生改变的数据。典型的例子包括:
这类上下文本质上是一种依赖注入形式。它的核心价值在于:将运行所需的依赖项在运行时提供给工具和节点,而不是把它们硬编码在业务逻辑中。
一个示意性的代码结构如下:
from dataclasses import dataclass@dataclassclass ContextSchema:user_name: strdb_connection: objectapi_client: object# 在调用时传入上下文graph.invoke({"messages": [{"role": "user", "content": "hi!"}]},context={"user_name": "John Smith", "db_connection": conn, "api_client": client})
在业务逻辑中,可以通过中间件或节点函数访问这些上下文:
@dynamic_promptdef personalized_prompt(request) -> str:user_name = request.runtime.context.user_namereturn f"You are a helpful assistant. Address the user as {user_name}."
可以看到,用户名称并非硬编码在提示词模板里,而是从运行时上下文中动态获取。这使得同一套代码可以服务于不同用户,而无需修改任何业务逻辑。
动态运行时状态是指在单次运行期间会发生变化的数据。它与静态运行时上下文的关键区别在于”可变性”:静态上下文在运行开始后不再改变,而动态状态会随着程序执行而演进。
典型的动态运行时状态包括:
这类状态的生命周期限定在单次运行或单次调用内。运行结束后,状态通常被丢弃,除非显式地将其持久化。
动态跨对话存储是指在多个对话或会话之间持续存在的持久化数据。它同样具有可变性,但生命周期远长于单次运行。
典型的跨对话存储数据包括:
这类数据的读写通常需要借助外部持久化机制,如数据库、键值存储或文件系统。
尽管三种上下文在可变性和生命周期上存在显著差异,它们也有一些共同特征:
| 共同维度 | 说明 |
|---|---|
| 目的相同 | 都是为AI应用提供”正确的信息”,使任务能够顺利完成 |
| 都服务于上下文工程 | 上下文工程的核心是构建动态系统,以正确格式提供正确信息和工具 |
| 都可通过运行时注入 | 三者都可以在运行时获取,而非全部编译期确定 |
| 都与LLM上下文有交互 | 三者最终都可能影响传递给模型的提示词内容,但它们本身不等于LLM上下文 |
这里需要特别澄清一个常见误解:运行时上下文不等于LLM上下文。运行时上下文是代码运行所需的数据和依赖项;LLM上下文是传递给模型提示词的数据;而”上下文窗口”则是模型能接收的最大token数量。三者是不同层面的概念,不能混为一谈。
这是最根本的差异。静态运行时上下文在运行期间不可变,这带来了可预测性和线程安全性;动态运行时状态和跨对话存储则可变,需要处理并发写入、状态一致性等问题。
生命周期决定了数据的存储位置和清理策略。单次运行的数据可以放在内存中,跨会话的数据则必须持久化。
| 上下文类型 | 典型访问方式 |
|---|---|
| 静态运行时上下文 | 通过调用参数传入,在节点/工具中通过运行时对象访问 |
| 动态运行时状态 | 通过状态对象读写,框架通常提供状态更新机制 |
| 动态跨对话存储 | 通过存储接口读写,通常需要指定命名空间或键 |
| 对比维度 | 静态运行时上下文 | 动态运行时状态 | 动态跨对话存储 |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 生命周期 | 单次运行 | 单次运行 | 跨会话 |
| 典型数据 | 用户元数据、连接、工具 | 对话历史、中间结果 | 长期偏好、业务状态 |
| 存储位置 | 内存/调用参数 | 内存/状态对象 | 外部持久化 |
| 并发考虑 | 低(只读) | 中(需状态管理) | 高(需持久化并发控制) |
| 注入方式 | 依赖注入 | 状态更新 | 存储读写 |
| 对LLM上下文的影响 | 间接(提供数据来源) | 直接(构成提示词内容) | 间接(按需检索注入) |
选型时,可以按照以下决策树进行判断:
数据在运行期间会变化吗?
数据需要在多次对话之间保留吗?
数据是否包含敏感信息?
数据量是否很大?
在实际工程中,三者往往同时存在。例如一个客服AI应用可能同时使用:静态运行时上下文注入用户数据库连接,动态运行时状态维护当前对话历史,动态跨对话存储保存用户的历史工单记录。
当从一种上下文管理方式迁移到另一种时,需要关注以下问题:
上下文不是单一概念,而是由可变性和生命周期两个维度定义的多个类型。静态运行时上下文提供不可变的依赖注入,适合多用户和环境隔离场景;动态运行时状态承载单次运行中的可变数据,适合对话和工具调用链;动态跨对话存储实现跨会话持久化,适合个性化和长期业务状态。
理解这三者的差异,核心在于把握三个问题:数据会不会变、数据活多久、数据放在哪里。回答好这三个问题,上下文管理方式的选择就会变得清晰。在AI应用架构设计中,合理组合这三种上下文机制,才能在灵活性、性能和可维护性之间取得平衡。