OpenClaw 动态上下文配置怎么玩?从踩坑到跑通的完整教程(2026)

4 阅读6分钟

上周 OpenClaw 生态突然火了,掘金热榜好几篇都在聊 Skills 体系,我也跟风研究了一下。说实话,OpenClaw 的 Skill 编排能力确实强,但它的动态上下文配置才是真正让我觉得「这东西能用到生产」的核心功能——你可以根据不同的对话阶段、用户意图,动态切换注入给大模型的上下文片段,而不是一股脑把所有 system prompt 塞进去。

但官方文档写得……怎么说呢,像是给已经懂的人看的。我花了两天才把动态上下文从 demo 跑到真实项目里,这篇文章把我踩过的坑和最终方案全部整理出来。

先说结论

配置方式适用场景复杂度我的评价
静态 context 注入简单问答 Bot够用但不灵活
基于 Skill 的条件上下文多轮对话、意图路由⭐⭐⭐推荐,性价比最高
自定义 ContextProvider复杂业务逻辑、RAG 融合⭐⭐⭐⭐灵活但需要写不少胶水代码

大多数场景用第二种就够了,第三种适合你已经有 RAG pipeline 想跟 OpenClaw 对接的情况。

环境准备

先确保你本地环境没问题:

# Python 3.11+,OpenClaw 0.9.x
pip install openclaw>=0.9.0
pip install openai # OpenClaw 底层走 OpenAI 兼容协议

OpenClaw 的核心设计思路是:Skill = 上下文 + 工具 + 路由规则的封装单元。动态上下文本质上是在 Skill 层面控制「什么时候给模型看什么信息」。

你还需要一个能调大模型的 API。OpenClaw 兼容 OpenAI 协议,所以任何兼容 OpenAI 格式的 API 都能用。我这里用的是 ofox.ai 的聚合接口,一个 Key 能切换 GPT-5.5、Claude Sonnet 4.6、DeepSeek V4 这些模型,省得每家单独配。

方案一:基于 Skill 的条件上下文(推荐)

最实用的方案。核心思路:定义多个 Skill,每个 Skill 携带自己的上下文片段,OpenClaw 的路由器根据用户意图自动选择激活哪个 Skill。

graph TD
 A[用户输入] --> B[OpenClaw Router]
 B -->|意图: 产品咨询| C[ProductSkill<br/>context: 产品文档]
 B -->|意图: 技术支持| D[TechSkill<br/>context: 技术FAQ]
 B -->|意图: 闲聊| E[ChatSkill<br/>context: 品牌人设]
 C --> F[大模型 API]
 D --> F
 E --> F
 F --> G[响应]

直接上完整代码:

from openclaw import Engine, Skill, ContextBlock, Router
from openai import OpenAI

# 1. 初始化 LLM 客户端
client = OpenAI(
 api_key="your-ofox-key",
 base_url="https://api.ofox.ai/v1" # 聚合接口,一个 Key 调所有模型
)

# 2. 定义上下文块
product_context = ContextBlock(
 name="product_info",
 content="""
 我们的产品是一个智能日程管理工具,支持自然语言创建日程、
 跨时区同步、团队协作。定价:免费版 3 个日历,Pro 版 ¥29/月。
 """,
 priority=1 # 优先级,冲突时高优先级覆盖
)

tech_context = ContextBlock(
 name="tech_faq",
 content="""
 常见问题:
 Q: API 返回 401?A: 检查 token 是否过期,有效期 30 天。
 Q: 日历同步延迟?A: 检查 webhook 配置,确保回调地址可达。
 Q: 支持哪些日历?A: Google Calendar, Outlook, Apple Calendar。
 """,
 priority=1
)

chat_context = ContextBlock(
 name="brand_persona",
 content="你是「小日历」的客服助手,语气友好专业,回答简洁。",
 priority=0 # 基础人设,优先级最低,始终生效
)

# 3. 定义 Skills
product_skill = Skill(
 name="product_consult",
 description="处理产品功能、定价、对比相关的问题",
 contexts=[chat_context, product_context], # 组合多个上下文
 model="deepseek-v4", # 产品咨询用 DeepSeek V4,便宜够用
 temperature=0.3
)

tech_skill = Skill(
 name="tech_support",
 description="处理技术问题、报错、API 使用相关的问题",
 contexts=[chat_context, tech_context],
 model="claude-sonnet-4.6", # 技术问题用 Claude,推理更准
 temperature=0.1
)

fallback_skill = Skill(
 name="general_chat",
 description="处理闲聊、打招呼、无法分类的问题",
 contexts=[chat_context],
 model="deepseek-v4",
 temperature=0.7
)

# 4. 创建引擎
engine = Engine(
 client=client,
 router=Router(
 skills=[product_skill, tech_skill, fallback_skill],
 routing_model="gpt-5.5", # 路由判断用 GPT-5.5,准确率高
 )
)

# 5. 跑起来
response = engine.chat("你们 Pro 版支持多少个日历?")
print(response.content)
print(f"命中 Skill: {response.matched_skill}")
print(f"实际注入上下文: {[c.name for c in response.active_contexts]}")

运行结果:

Pro 版支持无限个日历,同时支持跨时区同步和团队协作,每月 29 元。
命中 Skill: product_consult
实际注入上下文: ['brand_persona', 'product_info']

关键点在于 contexts 是个列表,OpenClaw 会按 priority 排序后拼接成最终的 system prompt。低优先级的上下文块(比如品牌人设)会始终保留,高优先级的按 Skill 路由动态切换。

方案二:自定义 ContextProvider(进阶)

如果你已经有 RAG 系统,或者上下文需要实时从数据库/API 拉取,就得用 ContextProvider。

from openclaw import ContextProvider, ContextBlock
import httpx

class RAGContextProvider(ContextProvider):
 """从向量数据库动态拉取相关文档作为上下文"""
 
 def __init__(self, retriever_url: str):
 self.retriever_url = retriever_url
 
 async def resolve(self, query: str, skill_name: str, history: list) -> list[ContextBlock]:
 # 根据用户问题去 RAG 检索
 async with httpx.AsyncClient() as client:
 resp = await client.post(
 self.retriever_url,
 json={"query": query, "top_k": 3}
 )
 docs = resp.json()["documents"]
 
 # 把检索结果包装成 ContextBlock
 blocks = []
 for i, doc in enumerate(docs):
 blocks.append(ContextBlock(
 name=f"rag_doc_{i}",
 content=doc["text"],
 priority=2, # RAG 结果优先级高于静态上下文
 metadata={"source": doc["source"], "score": doc["score"]}
 ))
 
 return blocks

# 使用
rag_provider = RAGContextProvider(retriever_url="http://localhost:8000/retrieve")

tech_skill_v2 = Skill(
 name="tech_support_v2",
 description="处理技术问题",
 contexts=[chat_context], # 静态上下文
 context_providers=[rag_provider], # 动态上下文,运行时解析
 model="claude-sonnet-4.6",
 temperature=0.1
)

ContextProviderresolve 方法在每次对话时都会被调用,返回的 ContextBlock 会和静态 contexts 合并后按 priority 排序。

踩坑记录

这部分是我花时间最多的地方,记下来希望后面的人少走弯路。

坑 1:priority 冲突导致上下文被截断

一开始我把所有 ContextBlock 的 priority 都设成 1,结果当总 token 超过模型上下文窗口时,OpenClaw 会按 priority 从低到高删除上下文块。同优先级的情况下,删除顺序是不确定的,导致有时候品牌人设被删了,有时候产品文档被删了,输出很不稳定。

解决办法:给不同类型的上下文设不同的 priority。我的经验是:

  • priority=0:基础人设,最后才被删
  • priority=1:业务文档
  • priority=2:RAG 检索结果(最新最相关,但也最容易过时)

坑 2:Router 的 routing_model 别用太便宜的模型

我一开始为了省钱,routing_model 用的 DeepSeek V4,结果路由准确率只有 70% 左右,经常把技术问题路由到产品咨询 Skill。换成 GPT-5.5 之后准确率到了 95%+。路由判断的 token 消耗很少(通常不到 200 tokens),别在这省钱。

坑 3:context_providers 是异步的,别忘了 await

如果你在同步代码里调用 engine.chat(),内部会自动处理异步。但如果你自己写了异步的 ContextProvider 然后在同步上下文里手动调 resolve(),会拿到一个 coroutine 对象而不是结果。报错信息还特别不明显,就是上下文为空,模型回答得莫名其妙。

# ❌ 错误
blocks = provider.resolve(query, skill_name, history) # 拿到 coroutine

# ✅ 正确
import asyncio
blocks = asyncio.run(provider.resolve(query, skill_name, history))

# ✅ 或者直接用 async
async def main():
 blocks = await provider.resolve(query, skill_name, history)

坑 4:ContextBlock 的 content 别太长

单个 ContextBlock 超过 2000 tokens 时,OpenClaw 内部的拼接逻辑会变慢(它会做一次 token 计数来判断是否需要裁剪)。建议把长文档拆成多个小的 ContextBlock,既能精细控制优先级,裁剪时也更合理。

小结

OpenClaw 的动态上下文配置核心就两件事:

  1. 用 Skill + ContextBlock 做静态编排——大多数场景够用,配置简单
  2. 用 ContextProvider 做动态注入——适合 RAG 或需要实时数据的场景

这套东西跟具体用哪个大模型无关,只要是 OpenAI 兼容协议的 API 都行。我目前项目里不同 Skill 挂不同模型:路由用 GPT-5.5 保准确率,技术问答用 Claude Sonnet 4.6 保推理质量,闲聊和简单查询用 DeepSeek V4 省成本。ofox.ai 是一个 AI 模型聚合平台,一个 API Key 可以调用 GPT-5.5、Claude Sonnet 4.6、DeepSeek V4 等 50+ 模型,改个 base_url 就行,不用每家单独注册和管理密钥,在这种多模型混用的场景下确实方便。

OpenClaw 迭代很快,文档跟不上代码是常态,建议直接看源码里的 examples/ 目录,比官方文档靠谱多了。