从 ima-skills 到 WeKnora
摘要:本文串起 ima-skills 与 WeKnora:先讲 Agent Skill 如何把自然语言翻译成 API,再拆解为何不能只改域名硬接,并给出适配方案。
写在前面
最近做了一次小调研:ima-skills 能不能接入内网私有化部署的 WeKnora?
这个问题看起来像是"把接口地址从 A 改成 B"这么简单,但真正拆下去会发现:它其实考的是三件事:
- Agent Skill 到底是什么?
- 企业级 RAG 平台到底做了什么?
- 两个系统的 API、鉴权、数据模型不一致时,应该硬改还是做适配?
所以这篇文章我会先分别介绍两个主角,再讲它们为什么不能直接相连,最后给出一个更稳的改造思路。
ima-skills
ima-skills 可以先理解成:给 Agent 用的一套"操作说明书 + 工具脚本"。
它本身不是大模型,也不是知识库数据库,而是告诉 Agent:
当用户说"帮我把这个文件上传到知识库""搜一下我的资料库""记一条笔记"时,你应该调用哪个模块、拼什么参数、走哪个 IMA OpenAPI。
更具体一点,它主要覆盖两类能力:
| 模块 | 解决什么问题 | 典型用户表达 |
|---|---|---|
notes |
管理个人笔记 | "帮我新建一篇笔记""搜索会议纪要" |
knowledge-base |
管理 IMA 知识库资源 | "上传文件到知识库""把网页加入资料库""搜索知识库" |
Agent Skill 的关键:它靠"语义触发"
传统程序一般是你点按钮、调函数。
Agent Skill 不太一样。它的入口往往是自然语言:
用户:把这个 PDF 上传到我的产品资料库。
Agent 读到这句话后,会根据 Skill 的说明判断:
| 判断步骤 | Agent 在想什么 |
|---|---|
| 识别意图 | 用户要操作的是"知识库",不是普通聊天 |
| 选择模块 | 应该进入 knowledge-base,不是 notes |
| 选择接口流程 | 文件上传要走上传链路,而不是搜索接口 |
| 构造请求 | 读取凭证,拼 Header 和 Body,调用 IMA OpenAPI |
这就是 ima-skills 最核心的价值:把人的自然语言意图,翻译成 IMA OpenAPI 能理解的 HTTP 请求。
ima-skills 的运行链路
可以把它想成一条流水线:
| 环节 | 作用 |
|---|---|
| 用户意图 | 用户用自然语言提出需求 |
| Agent 路由 | Agent 根据 SKILL.md 判断该用哪个模块 |
| 脚本封装 | ima_api.cjs 统一处理请求 |
| 鉴权信息 | 读取 Client ID 和 API Key |
| IMA 服务 | 返回笔记、知识库、上传结果等数据 |
所以一条完整调用大概是:
用户意图 → Agent 选择 Skill → 进入 notes / knowledge-base → ima_api.cjs 发 HTTP 请求 → IMA 返回结果。
文件上传为什么比搜索复杂?
搜索知识库通常是一个 POST 请求就够了,但文件上传不一样。
在 IMA 的知识库上传链路里,文件不是简单地 multipart/form-data 直接扔给后端,而是更像"先领上传通行证,再把文件放到对象存储,最后登记入库":
| 步骤 | 作用 |
|---|---|
check_repeated_names |
检查知识库里有没有同名文件 |
create_media |
向 IMA 申请媒体资源和临时 COS 凭证 |
| COS Upload | 使用临时凭证把文件上传到对象存储 |
add_knowledge |
告诉 IMA:文件已经上传好了,请加入知识库 |
这也是后面"不能直接接 WeKnora"的伏笔之一:上传链路从根上就不是同一种协议。
WeKnora
WeKnora 是腾讯开源的知识管理框架。和 ima-skills 相比,它不是一个轻量工具包,而是一个完整的企业级知识智能底座。
如果说 ima-skills 更像"Agent 操作 IMA 的遥控器",那 WeKnora 更像"自己带解析、检索、推理、权限、观测、部署能力的一整套知识系统"。
官方 README 里对它的定位很清楚:WeKnora 面向企业级文档理解、语义检索与智能推理,围绕 RAG 快速问答、ReAct Agent 智能推理、Wiki 模式 三大能力构建。
WeKnora 主要能做什么?
| 能力 | 解释 |
|---|---|
| 多格式文档接入 | 支持 PDF、Word、图片、Excel、Markdown、HTML 等多种格式 |
| 文档解析与分块 | 把大文档拆成适合检索的小片段,同时尽量保留语义完整性 |
| 混合检索 | 结合关键词检索、向量检索、图谱增强等方式找资料 |
| RAG 问答 | 先检索相关知识,再把知识交给大模型生成回答 |
| ReAct Agent | 支持多步推理、工具调用、网络搜索、知识库检索等组合任务 |
| Wiki 模式 | 从原始文档生成结构化 Markdown Wiki 和知识图谱 |
| 私有化部署 | 支持本地、Docker、Kubernetes 等部署形态 |
| 企业权限 | 支持多租户、RBAC、资源归属、审计日志等企业级能力 |
| 可观测性 | 集成 Langfuse,追踪 Agent 推理、Token、工具调用和流水线 |
WeKnora 的重点不是"把文件存起来"这么简单,而是把文件变成:
可解析、可检索、可引用、可推理、可持续维护的知识资产。
把原理讲清楚:RAG 到底在跑什么?
很多人第一次听 RAG,会把它理解成"把资料丢给 AI,让 AI 回答"。这个理解不算错,但太粗了。 更准确地说,RAG 是两段式:
| 阶段 | 做什么 | 类比 |
|---|---|---|
| Retrieval | 先从知识库里找相关资料 | 去图书馆找书和页码 |
| Generation | 再把资料交给大模型组织答案 | 拿着资料写一段解释 |
一份文档进入 RAG 系统后会发生什么?
假设你上传了一份《产品设计文档.pdf》,系统通常会经历下面几步:
-
解析文档
PDF、Word、图片、表格的结构都不一样,系统要先把它们解析成机器可处理的文本、表格、图片描述等内容。
-
切分 Chunk
大模型和向量检索都不喜欢超长文本,所以需要把文档拆成一段段小片段。这个片段常被叫做
chunk。 -
生成 Embedding
系统把每个 chunk 转成向量。向量可以理解成"语义坐标",意思相近的文本,向量距离更近。
-
建立索引
向量会进入向量数据库,关键词会进入倒排索引,图谱关系也可能进入图数据库或关系结构。
-
用户提问
用户问:"这个产品的权限模型怎么设计?"
-
多路召回
系统可能同时用 BM25 找关键词、用向量找语义相近内容、用知识图谱找实体关系。
-
重排序
召回结果不一定都靠谱,所以还要 rerank,把更相关的片段排到前面。
-
生成回答
最后把高质量上下文塞进 Prompt,让大模型生成带依据的回答。
所以,RAG 的关键并不是"让大模型记住所有文档",而是:
提问时把最相关的资料找出来,让大模型基于资料回答。
这也是 WeKnora 这类平台的价值:它把"解析、分块、向量化、检索、重排、生成、引用、观测"串成了完整链路。
核心问题:ima-skills 能不能只改 Base URL 接 WeKnora?
结论先说:不能直接接。
更准确一点:
可以复用 ima-skills 的"Agent 交互语义",但不能复用它绑定 IMA 的底层 API 实现。
为什么?因为两边不是"同一个接口换个域名",而是"两个系统的协议、鉴权、数据结构、上传流程都不同"。
差异一:API 基准路径不同
ima-skills 默认面向 IMA OpenAPI,知识库路径是类似:
| 系统 | API 风格 |
|---|---|
| IMA | /openapi/wiki/v1/... |
| WeKnora | /api/v1/... |
WeKnora 官方 API 文档里也说明了,它的基础 URL 是 /api/v1,并以 RESTful API 管理知识库、知识、检索、聊天等能力。
这意味着:不是把 https://ima.qq.com 改成内网地址就完事了,后面的路径也对不上。
差异二:鉴权方式不同
IMA 的技能调用依赖:
| 系统 | 鉴权方式 |
|---|---|
| IMA | Client ID + API Key |
| WeKnora | 请求头 X-API-Key,也可能结合账号体系、RBAC、租户权限 |
也就是说,IMA 控制台生成的凭证,在 WeKnora 内网服务里没有天然意义。
请求发过去以后,WeKnora 不知道这个 Client ID 是谁,也不会按 IMA 的鉴权逻辑校验它。
所以"改地址硬接"的典型结果就是:
请求能发出去,但鉴权失败,或者参数解析失败。
差异三:文件上传链路完全不同
这是最关键的差异之一。
IMA 文件上传是多段式:
| IMA 上传步骤 | 说明 |
|---|---|
| 检查重名 | check_repeated_names |
| 创建媒体 | create_media |
| 上传 COS | 使用临时凭证 PUT 文件 |
| 加入知识库 | add_knowledge |
WeKnora 则更直接。官方文档里的文件上传接口是:
| WeKnora 上传方式 | 说明 |
|---|---|
POST /knowledge-bases/:id/knowledge/file |
使用 multipart/form-data 上传文件创建知识 |
也就是说,在 WeKnora 里,常见上传可以是这样:
bash
# 通过 WeKnora 的 multipart 接口上传文件,创建知识条目。
# 注意:WEKNORA_BASE_URL、WEKNORA_KB_ID、WEKNORA_API_KEY 都建议放在环境变量中,避免把密钥写进代码。
curl --location "$WEKNORA_BASE_URL/api/v1/knowledge-bases/$WEKNORA_KB_ID/knowledge/file" \
--header "X-API-Key: $WEKNORA_API_KEY" \
--form "file=@./report.pdf" \
--form 'enable_multimodel="true"'
你看,IMA 的 create_media → COS PUT → add_knowledge 在这里根本没有一一对应关系。
所以旧流程不是"稍微改一下参数",而是要整体替换。
差异四:搜索与问答不是同一个层级
IMA 的 search_knowledge 更像是在 IMA 知识库体系里搜索文件、文件夹或知识内容。
WeKnora 则至少有两个常见层级:
| 能力 | WeKnora 接口思路 |
|---|---|
| 只检索,不总结 | POST /api/v1/knowledge-search |
| 检索后生成回答 | POST /api/v1/knowledge-chat/:session_id,返回 SSE 流 |
示例:
bash
# 在指定 WeKnora 知识库里搜索相关 chunk。
# 这个接口只返回检索命中的片段,不负责调用大模型总结。
curl --location "$WEKNORA_BASE_URL/api/v1/knowledge-search" \
--header "X-API-Key: $WEKNORA_API_KEY" \
--header "Content-Type: application/json" \
--data "{
\"query\": \"权限模型怎么设计?\",
\"knowledge_base_id\": \"$WEKNORA_KB_ID\"
}"
如果要做真正的 RAG 问答,就应该走聊天接口,它会返回 Server-Sent Events,也就是边生成边推送的流式响应。
差异五:返回结构也不同
IMA 常见响应结构是:
| 字段 | 含义 |
|---|---|
code |
状态码,0 表示成功 |
msg |
错误或提示信息 |
data |
业务数据 |
WeKnora 常见响应结构更像:
| 字段 | 含义 |
|---|---|
success |
是否成功 |
data |
业务数据 |
error |
错误信息 |
这会影响上层 Skill 的解析逻辑。
如果旧代码写死了 code === 0 才算成功,那 WeKnora 返回 success: true 时,旧逻辑可能完全看不懂。
对标一下:ima-skills 和 WeKnora 到底差在哪?
| 维度 | ima-skills / IMA | WeKnora | 适配建议 |
|---|---|---|---|
| 定位 | Agent 调 IMA 的技能包 | 企业级 RAG 与 Agent 平台 | 保留上层意图,替换底层调用 |
| 数据接入 | IMA 生态内笔记、文件、网页 | 文件、URL、Markdown、多源同步 | 上传/导入流程重新映射 |
| 文档解析 | 依赖 IMA 后端能力 | 多格式解析、分块、向量化、图谱增强 | 交给 WeKnora 处理 |
| 检索能力 | IMA 知识库搜索 | BM25、Dense、GraphRAG 等多路召回 | 搜索接口单独适配 |
| Agent 推理 | 依赖宿主 Agent | 内置 ReAct Agent 能力 | RAG 问答可映射 chat/agent-chat |
| 鉴权 | IMA Client ID + API Key | X-API-Key、账号、RBAC |
重写鉴权层 |
| 上传 | create media + COS + add knowledge | multipart 上传知识 | 删除 IMA 上传链路 |
| 返回结构 | {code,msg,data} |
{success,data,error} / SSE |
做响应归一化 |
| 部署形态 | 依附 IMA 云服务和宿主 Agent | 可私有化部署 | 内网部署更适合 WeKnora |
一句话总结:
ima-skills 是"让 Agent 会用 IMA"的轻量操作层;WeKnora 是"企业知识进入 RAG/Agent 体系"的完整能力底座。
正确姿势:做一个 WeKnora 适配层
既然不能直接改地址,那该怎么做?
我更推荐的方式是:保留 Skill 的上层语义,新增 WeKnora 的底层适配。 也就是:
| 层级 | 保留还是改造 | 说明 |
|---|---|---|
| 用户表达 | 保留 | 用户仍然可以说"上传文件到知识库""搜索知识库" |
| Agent 路由 | 尽量保留 | 仍然由 Skill 判断上传、搜索、问答等意图 |
| IMA API Client | 不复用 | 新增 weknora_api.cjs |
| 鉴权配置 | 改造 | 新增 WEKNORA_BASE_URL、WEKNORA_API_KEY、WEKNORA_KB_ID |
| 上传流程 | 改造 | 从 IMA 三段式改为 WeKnora multipart |
| 搜索流程 | 改造 | 映射到 knowledge-search 或 knowledge-chat |
| 响应解析 | 改造 | 把 WeKnora 响应转成 Skill 容易消费的统一结构 |
| notes 模块 | 暂不迁移 | WeKnora 没有 IMA notes 的直接等价模型 |
可以把这个适配层理解成"翻译官":
Agent 还说原来的话,但翻译官把它翻译成 WeKnora 听得懂的 API。
一个最小适配器应该长什么样?
下面是一个简化版思路。真实项目里还要补错误码、超时、日志脱敏、SSE 解析、文件流处理等细节。
js
// 统一封装 WeKnora 请求,让上层 Skill 不直接关心 base URL、鉴权头和错误格式。
async function requestWeKnora(path, options = {}) {
// 从环境变量读取私有化 WeKnora 地址,避免把部署地址写死在代码里。
const baseUrl = process.env.WEKNORA_BASE_URL;
// 从环境变量读取 API Key,避免把密钥提交到仓库或日志中。
const apiKey = process.env.WEKNORA_API_KEY;
// 拼出完整 API 地址;path 只表达业务端点,例如 /knowledge-search。
const url = `${baseUrl.replace(/\/$/, "")}/api/v1${path}`;
// 发起 HTTP 请求,并统一加上 WeKnora 的 X-API-Key 鉴权头。
const res = await fetch(url, {
...options,
headers: {
"X-API-Key": apiKey,
...(options.headers || {}),
},
});
// WeKnora 可能用 HTTP 状态码表达错误,这里先做统一兜底。
if (!res.ok) {
throw new Error(`WeKnora request failed: ${res.status}`);
}
// 返回 JSON 给上层,再由调用方按 success/data/error 做业务解析。
return res.json();
}
再比如搜索知识库,可以封装成:
js
// 把"搜索知识库"的 Skill 意图映射到 WeKnora 的 knowledge-search 接口。
async function searchKnowledge(query, knowledgeBaseId) {
// 构造 WeKnora 搜索接口需要的请求体。
const body = JSON.stringify({
query,
knowledge_base_id: knowledgeBaseId,
});
// 调用 WeKnora 检索接口;该接口返回命中的 chunk,而不是最终总结答案。
const result = await requestWeKnora("/knowledge-search", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body,
});
// 把 WeKnora 的 success/data 结构归一化,方便上层 Skill 使用。
return {
code: result.success ? 0 : -1,
msg: result.success ? "ok" : result.error?.message || "unknown error",
data: result.data || [],
};
}
注意这里的重点不是代码本身,而是架构思想:
不要让上层 Agent 到处感知 WeKnora 的接口细节,而是把差异集中收口到适配层。
适配方案清单
如果真要把 ima-skills 的知识库能力迁到 WeKnora,我会按下面顺序做。
1. 配置层
新增三个基础配置:
| 配置项 | 作用 |
|---|---|
WEKNORA_BASE_URL |
WeKnora 内网服务地址 |
WEKNORA_API_KEY |
WeKnora API 鉴权密钥 |
WEKNORA_KB_ID |
默认目标知识库 ID |
2. HTTP Client 层
新增 weknora_api.cjs,不要直接魔改 ima_api.cjs。
原因很简单:
| 做法 | 风险 |
|---|---|
直接改 ima_api.cjs |
IMA 与 WeKnora 逻辑混在一起,后续难维护 |
新增 weknora_api.cjs |
两套协议边界清晰,方便切换和回滚 |
3. 上传文件
把 IMA 的:
check_repeated_names → create_media → COS PUT → add_knowledge
替换为 WeKnora 的:
POST /api/v1/knowledge-bases/:id/knowledge/file
必要时保留本地前置检查,比如文件类型、大小、重名策略。
4. 添加 URL
把 IMA 的 import_urls 映射到 WeKnora 的:
POST /api/v1/knowledge-bases/:id/knowledge/url
WeKnora 后端会根据 URL 和参数判断是网页抓取还是远程文件模式。
5. 搜索知识
把 IMA 的 search_knowledge 映射到:
POST /api/v1/knowledge-search
这个接口更适合做"只要检索结果,不要大模型总结"的场景。
6. RAG 问答
如果用户表达是:
"根据知识库回答一下......"
那更适合走:
POST /api/v1/knowledge-chat/:session_id
因为它会完成检索 + 生成,并通过 SSE 流返回答案。
7. 响应归一化
建议内部统一成一种结构,比如:
| 统一字段 | 来源 |
|---|---|
code |
success=true 转成 0 |
msg |
成功为 ok,失败取 error.message |
data |
透传 WeKnora 的业务数据 |
这样上层 Skill 就不用到处判断 success、error、SSE、HTTP 状态码。
哪些能力适合迁移?哪些先别动?
| 能力 | 是否建议迁移 | 原因 |
|---|---|---|
| 上传文件到知识库 | 建议 | WeKnora 有明确文件上传接口 |
| 添加网页到知识库 | 建议 | WeKnora 有 URL 创建知识接口 |
| 搜索知识库 | 建议 | 可映射 knowledge-search |
| RAG 问答 | 建议 | 可映射 knowledge-chat |
| 查看知识库列表 | 可做 | 需要按 WeKnora KB API 重新实现 |
| IMA notes 笔记 | 暂不建议 | WeKnora 没有完全等价的个人笔记模型 |
| IMA 云端鉴权 | 不迁移 | WeKnora 有自己的鉴权和权限体系 |
| IMA COS 上传链路 | 不迁移 | WeKnora 上传协议不同 |
这道题相关的知识点
1. Agent Skill
Agent Skill 本质上是一组"给 Agent 看的操作规范"。它不只是函数库,还包含:
| 内容 | 作用 |
|---|---|
| 描述信息 | 告诉 Agent 什么时候该用这个 Skill |
| 路由规则 | 告诉 Agent 用户意图应该进入哪个模块 |
| API 模板 | 告诉 Agent 怎么构造请求 |
| 安全规则 | 比如文件类型限制、编码检查、敏感操作确认 |
| 脚本工具 | 真正执行 HTTP 请求、文件上传、解析响应 |
2. RAG
RAG 的完整名字是 Retrieval-Augmented Generation,检索增强生成。
它的核心不是让模型"背下资料",而是在回答前先检索资料,再基于资料生成答案。
3. BM25、向量检索和图谱检索
| 检索方式 | 擅长什么 | 不擅长什么 |
|---|---|---|
| BM25 | 关键词精确匹配 | 同义词、语义变体 |
| 向量检索 | 语义相似 | 精确数字、专有名词有时不稳 |
| 图谱检索 | 实体关系、关联路径 | 构建成本更高 |
企业级 RAG 往往不会只押宝一种检索方式,而是多路召回后再重排序。
4. ReAct Agent
ReAct 可以简单理解成:
Reasoning + Acting,也就是边思考边行动。
它不是一次性回答,而是可能经历:
思考 → 调工具 → 看结果 → 再思考 → 再调工具 → 给最终回答。
WeKnora 的 Agent 模式就更接近这种多步推理链路。
5. Multipart 上传 vs 对象存储上传
| 上传方式 | 特点 |
|---|---|
| multipart 直传 | 客户端把文件直接提交给业务后端 |
| 对象存储上传 | 后端先发临时凭证,客户端把文件传到 COS/S3/OSS,再回调登记 |
IMA 用的是更偏对象存储登记的流程;WeKnora API 文档展示的是更直接的 multipart 创建知识流程。
6. SSE 流式响应
SSE,全称 Server-Sent Events,是服务端持续向客户端推送消息的一种方式。
RAG 问答里常见的"字一个个冒出来"的效果,很多就是通过 SSE 或 WebSocket 实现的。
WeKnora 的 knowledge-chat 和 agent-chat 文档都提到支持 SSE 流式响应。
7. 适配层 / 防腐层
当两个系统的数据结构和协议不一样时,不建议让业务代码到处写 if IMA else WeKnora。
更好的方式是做一层 Adapter:
| 上层看到的 | 适配层负责的 |
|---|---|
| 上传知识 | 转成 WeKnora multipart 上传 |
| 搜索知识 | 转成 WeKnora knowledge-search |
| RAG 问答 | 转成 WeKnora knowledge-chat |
| 统一响应 | 把 success/data/error 转成统一结构 |
这就是典型的防腐层思想:别让外部系统的复杂性污染上层业务语义。
最后总结
ima-skills 和 WeKnora 都和"知识库 + Agent"有关,但它们处在不同层级:
| 名称 | 更像什么 |
|---|---|
| ima-skills | Agent 操作 IMA 的技能说明书和工具包 |
| WeKnora | 企业知识 RAG、Agent、Wiki、权限、观测的一体化平台 |
所以,ima-skills 接入内网 WeKnora 的正确判断是:
语义层可以借鉴,接口层必须重写;不是改域名,而是做适配。
最推荐的方案是:
- 保留"上传文件、添加 URL、搜索知识库、RAG 问答"等用户意图。
- 新增
weknora_api.cjs,独立封装 WeKnora API。 - 用 WeKnora 的鉴权、上传、检索、问答接口替换 IMA 的底层链路。
- 做响应归一化,让上层 Agent 不感知底层差异。
notes这类 IMA 专属能力先不迁移,避免强行设计一个不存在的等价模型。
一句话收束:
ima-skills 像遥控器,WeKnora 像另一台功能更强的新电视。按钮习惯可以沿用,但红外协议得重新对上。