LLM Wiki理论与实践-06:详细解析OpenWiki如何摄取外部数据源介绍的Connector仅仅是将内容导入到对应的源文件中,并更新自身的状态,并不涉及Wiki页面的生成。为构建的知识库生成Wiki,以及针对Wiki完成与用户(针对Personal模式)和Coding Agent的交互需要借助LLM的力量,其背后自然是一个Agent。作为LangChain生态的一部分,OpenWiki背后的这个Agent是通过DeepAgents创建的。
1. 创建一个基于Wiki的Chat Agent
不论是Wiki内容的生成,还是最终使用Wiki来完成与用户和Coding Agent的对话,其实都不算复杂。为了让大家对背后的这个Agent有一个大概的认识,接下来我会用最少的代码创建一个LangChain Agent,这个Agent会利用LLM Wiki理论与实践-04:使用OpenWiki的Personal模式构建知识库生成的Wiki知识库来回答用户的提问。
OpenWiki(这里只考虑Personal模式)的知识库就是存储在~/.openwiki/wiki/%UserProfile%.openwiki/wiki(可以通过环境变量OPENWIKI_CONFIG_DIR进行设置)的几个通过交叉链接相互引用的Markdown文件。如果我们使用最简单的create_agent函数来创建一个Agent,只需要注册FilesystemMiddleware中间件映射到知识库目录,并提供相应的工具函数来读取Wiki的内容即可。如下面的演示程序所示,我们针对上述的目录创建了一个LocalShellBackend作为FilesystemMiddleware中间件的存储后端。在调用create_agent函数针对指定的LLM(针对gpt-5.4-mini的ChatOpenAI)和这个中间件创建Agent时,我们还提供了系统提示词来指导LLM如何基于Wiki知识库来检索上下文。
from deepagents.backends import LocalShellBackend
from langchain.agents import create_agent
from langchain_core.runnables import RunnableConfig
from deepagents.middleware.filesystem import FilesystemMiddleware
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from dotenv import load_dotenv
import os, asyncio
load_dotenv()
system_prompt = f"""
You are a data retrieval and analysis expert. You need to retrieve relevant context based on the user's questions, and provide the final answer through induction, analysis, and summarization.
**How to retrieve context**
- Retrieve a Wiki knowledge base built via a virtual file system. Each Wiki page is generated from raw data ingested by registered Connectors.
- Read the content of Wiki page files by calling the file system tool functions`ls`,`read_file`,`glob`, and`grep`.
- It is recommended to use`/quickstart.md`as the entry point to start retrieval in the Wiki knowledge base.
"""
open_wiki_home = os.getenv("OPENWIKI_CONFIG_DIR") or os.path.expanduser("~/.openwiki")
open_wiki_kb = os.path.join(open_wiki_home,"wiki")
backend = LocalShellBackend(root_dir=open_wiki_kb,virtual_mode=True)
agent = create_agent(
model= ChatOpenAI(model="gpt-5.4-mini"),
middleware=[FilesystemMiddleware(backend=backend)],
system_prompt=system_prompt,
checkpointer= MemorySaver())
config:RunnableConfig = {
"configurable":{
"thread_id":"thread_123"
}
}
while(True):
user_input = input("\n$: ")
if user_input.lower() in ["quit", "exit"]:
break
stream = agent.stream_events(
input= {"messages": [{"role": "user", "content": user_input}]},
config=config,
version="v3")
for event in stream:
if event.get("method") == "messages":
data = event.get("params", {}).get("data", [])
for item in data:
block = item.get("delta") or {}
if block.get("type") == "text-delta":
print(block.get("text", ""), end="", flush=True)
Agent创建出来后,我们开启了对话循环。在每次对待中,我们将用户输入转换成消息调用Agent的stream_events方法,并实时输出响应的内容。针对LLM Wiki理论与实践-04:使用OpenWiki的Personal模式构建知识库介绍的摄入内容,我们询问了如下两个问题:
$: 整理一份美国中期选举最新进展简报
下面是一份**美国中期选举最新进展简报**(基于当前知识库里最新可用的网页检索结果):
## 美国中期选举最新进展简报
### 一、总体情况
目前检索到的信息显示,相关内容仍以**基础背景与2026年选举日程**为主,**尚未出现明确的最新突发进展或竞选态势变化**。
### 二、已确认信息
- **美国中期选举通常在总统任期第二年的11月举行**
- 选举通常涉及:
- **众议院全部435个席位**
- **参议院33或34个席位**
- 大量州和地方职位
- 当前检索结果指向的**2026年中期选举日期为 2026年11月3日**
- 2026年中期选举将决定**第120届美国国会**
- 2026年参议院选举涉及**35个席位**
- 众议院方面,**435个选区都会进行投票**
### 三、当前可解读的“进展”
从现有资料看,所谓“最新进展”主要体现在:
1. **选举时间表已经明确**
2. **参众两院席位分布已进入完整轮次的中期选举周期**
3. **目前公开检索结果仍偏概览性**,没有显示出新的重大政策变化、关键候选人退选、党派格局大变或初选结果等消息
### 四、简要判断
如果你想要的是“**最新战况**”而不是“**中期选举基础介绍**”,目前知识库里的 web 搜索结果还不够。现有信息只能支持这样一个结论:
> **美国中期选举已进入2026周期,但当前可见信息仍主要是日程和规则层面的背景说明,暂无可确认的实时重大进展。**
### 五、建议
如果你需要,我可以继续帮你整理成以下任一种版本:
- **媒体简报风格**:适合汇报、会议纪要
- **一页纸速览版**:适合快速阅读
- **按党派/席位/州别拆解版**:更像政治分析
- **“最新动态”版**:我可以进一步帮你补充检索,找真正的竞选进展、民调、初选和关键摇摆州变化
如果你愿意,我下一步可以直接给你做一版**“美国中期选举最新动态一页纸”**。
$: 最近Hacker News有哪些关于AI Agent的新闻?
最近 Hacker News 上和 **AI Agent** 相关的新闻,主要集中在下面几类:
## 1) Agent 记忆与可观测性
- **Agent memory needs its OpenTelemetry moment**
72 points / 66 comments
重点在于:给 AI Agent 的记忆系统做标准化、可观测化,类似 OpenTelemetry 在传统软件里的作用。
这是当前最强的“agent 基础设施”信号之一。
## 2) Agent 编排与运行时
- **Launch HN: Rivet (YC W23) – Scale and Orchestrate Agents on S3**
71 points / 38 comments
这是一个自托管的 Agent 编排系统,核心卖点包括:
- actor 风格的长生命周期 worker
- S3-backed storage
- WebSockets
- hibernation
说明社区对“如何稳定运行和调度 Agent”仍然很关注。
## 3) Agent 工具接入:MCP
- **MCP Explained**
233 points / 111 comments
虽然不是纯 Agent 产品,但与 Agent 工具链、外部系统集成、标准化接口密切相关。
这类内容通常代表 Agent 生态正在向“可连接、可复用”的方向发展。
## 4) Agent 安全与隐私
- **Claude Code sends your email address to websites**
370 points / 122 comments
这是当前最热门的一条,主题是 AI coding agent / 工具在与网站交互时泄露用户邮箱地址。
这说明 Agent 安全、隐私、权限边界已经成为非常现实的问题。
## 5) Agent 审计与调试
- **Show HN: Stroq – a local audit of what your coding agent did and why**
66 points / 14 comments
这是一个面向 coding agent 的本地审计工具,关注“Agent 做了什么、为什么这么做”。
与上面的可观测性主题呼应。
## 6) 相关但偏外围的模型/研究信号
- **GPT-6 Astra covertly underperforms on safety research**
284 points / 150 comments
主要是安全研究和模型行为讨论,和 Agent 的直接关系没那么强,但会影响 Agent 未来部署方式。
- **Tencent Launches Hy4**、**Inkling 2 Small**
更偏模型发布,不属于 Agent 新闻本体。
## 一句话总结
最近 HN 上的 AI Agent 话题,核心不是“又出了什么炫酷 Demo”,而是更偏向:
- **记忆与可观测性**
- **编排与运行时**
- **工具接入标准**
- **安全与隐私**
- **审计与可解释性**
如果你愿意,我可以继续帮你整理成:
1. **按热度排序的 HN AI Agent 新闻清单**
2. **适合发群里的中文简报**
3. **“AI Agent 赛道趋势”分析版**
由于我们的Agent只注册了唯一FilesystemMiddleware中间件,所以它只能在Wiki知识库范围内提供答案。比如我们让它从Notion提取最新写入的会议记录的内容时,它就无能为力了。如下所示,由于Wiki中只有会议记录的摘要信息,并没有完整内容,它只能告诉你有这么一份会议记录,但是无法提供具体的内容。
$: 从notion中提取最新写入的会议记录
从这次 Notion 提取结果看,**最新写入的会议记录没有被成功抽取到具体内容**。
当前能确认的只有:
- **Meeting Notes** 数据库存在
- 字段包括:
-`Meeting name`
-`Date`
-`Attendees`
- 排序是按`Date`倒序
但这次检索的结论是:
- **没有从数据库本身提取到可用的会议纪要内容**
- 没有找到可提炼的**会议决策、项目进展或行动项**
- 所以目前**无法直接给出“最新会议记录”的正文摘要**
另外,这轮 Notion 中真正被提取到的最新有价值页面是:
- **Study LLM wiki**
- 类型:待办/任务
- 状态:Not started
- 截止日期:2026-11-15
如果你要,我可以继续帮你做两件事之一:
1. **继续深挖 Notion 里最新的会议页面**,尽量找到具体会议内容
2. **把当前已提取到的 Notion 最新内容整理成一页摘要**
但是LLM Wiki理论与实践-04:使用OpenWiki的Personal模式构建知识库针对OpenWiki的演示却能够提供这项功能,这些因为OpenWiki使用的Agent除了注册FilesystemMiddleware中间件外,还会注册额外的工具,其中就包含提取Notion具体页面的工具。接下来我们就来看看OpenWiki会创建怎样的Agent。
2. 创建Agent的工厂函数
OpenWiki内部会调用如下这个createOpenWikiAgent作为工厂函数创建对应的Agent。返回的Agent类型为DeepAgent,代表DeepAgents通过createDeepAgent函数(Python SDK对应的函数名称为create_deep_agent)。关于DeepAgents,在我们的系列文章12.DeepAgents:LC的Harness中具有系统介绍,不过我们介绍的是Python版本,OpenWiki使用的是TypeScript版本。
export async function createOpenWikiAgent(
options: OpenWikiAgentOptions,
): Promise<DeepAgent> {
if (!path.isAbsolute(options.cwd)) {
throw new Error("OpenWiki agent cwd must be an absolute path.");
}
if (options.outputMode === "repository" && options.command !== "chat") {
throw new Error(
"Repository init/update use the OpenWiki page-job runner; call runOpenWikiAgent instead of createOpenWikiAgent.",
);
}
await syncBundledSkills();
const openWikiIgnore =
options.outputMode === "repository"
? await OpenWikiIgnore.load(options.cwd)
: new OpenWikiIgnore([]);
const context = await createRunContext(
options.cwd,
options.outputMode,
options.language,
);
const checkpointer = await createCheckpointer(
resolveCheckpointTarget(options.command),
);
return createOpenWikiAgentGraph({
...options,
checkpointer,
context,
openWikiIgnore,
runTimestamp: new Date().toISOString(),
});
}
2.1 OpenWikiAgentOptions
createOpenWikiAgent函数的options是一个具有如下定义的OpenWikiAgentOptions对象。作为OpenWiki Agent的配置选项,OpenWikiAgentOptions通过解析openwiki命令行参数生成。
export type OpenWikiAgentOptions = {
command: OpenWikiCommand;
cwd: string;
language?: string | null;
model: BaseChatModel;
onEvent?: (event: OpenWikiRunEvent) => void;
outputMode: OpenWikiOutputMode;
};
export type OpenWikiCommand = "chat" | "init" | "update";
export type OpenWikiOutputMode = "local-wiki" | "repository";
- command:本次运行要执行的OpenWiki命令,决定 Agent 的行为分支;
- cwd:运行时的当前工作目录,作为文件操作的根路径;
- language:输出文档的目标语言;
- model:Agent使用的聊天模型实例(
BaseChatModel); - onEvent:运行过程中的事件回调,用于接收
OpenWikiRunEvent流式输出; - outputMode:输出模式,控制结果的呈现方式(
OpenWikiOutputMode);
outputMode参数对应的OpenWikiOutputMode类型体现了OpenWiki的code(repository)和Personal(local-wiki)两种执行模式。command参数对应的OpenWikiCommand体现对话(chat)、初始化(init)和增量Wiki更新(update)三种命令。OpenWikiCommand和OpenWikiOutputMode之间一共有6种组合,但是createOpenWikiAgent函数的验证逻辑排除了repository+init和repository+update的组合,所以创建的Agent会应用在如下两种场景下:
- Personal模式:用来更新Wiki和完成与用户的对话;
- Code模式:用来完成与用户(独立运行openwiki命令)或者Coding Agent(作为其连接的MCP)的对话。
2.2 OpenWikiAgentGraphOptions
DeepAgent对象的最终创建体现在针对createOpenWikiAgentGraph函数的调用上,传入的参数另一个类型为OpenWikiAgentGraphOptions的对象,它在OpenWikiAgentOptions的基础上为创建的DeepAgent提供了如下的配置选项:
- checkpointer:用于持久化的SqliteSaver对象;
- context:作为调用Agent传入的静态上下文;
- openWikiIgnore:.openwikiignore 文件的规则加载与匹配器,遵循gitignore式的“按文件顺序、最后匹配者胜出、! 可重新包含”语义;
- runTimestamp:调用时间戳。
type OpenWikiAgentGraphOptions = OpenWikiAgentOptions & {
checkpointer: SqliteSaver;
context: RunContext;
openWikiIgnore: OpenWikiIgnore;
runTimestamp: string;
};
export type UpdateRunStatus = "complete" | "interrupted";
export type UpdateMetadata = {
updatedAt: string;
command: OpenWikiCommand;
gitHead?: string;
model: string;
status?: UpdateRunStatus;
language?: string;
};
export type RunContext = {
lastUpdate: UpdateMetadata | null;
language?: string;
wikiGoal?: string;
};
export class OpenWikiIgnore {
readonly patterns: string[]
constructor(patterns: string[])
get isActive(): boolean
ignores(filePath: string, isDirectory = false): boolean
}
2.3 createOpenWikiAgentGraph
DeepAgent的最终创建体现在如下这个createOpenWikiAgentGraph函数上,从针对createDeepAgent函数的调用上可以看出,最终创建的DeepAgent具有的能力和特性体现在如下几个方面:
- 注册了了通过
createOpenWikiConnectorTools函数创建的工具函数; - 采用
OpenWikiAgentGraphOptions提供的SqliteSaver作为Checkpointer,意味着基于Thread的状态会持久化在SQLite数据库中; - 创建的
OpenWikiLocalShellBackend对象被createAgentBackend函数进一步加工,最终生成的CompositeBackend将作为Agent的后端; - 将目录
/skills/下的Markdown文件注册为Agent Skill; - 注册了由
createSystemPrompt函数创建的系统提示词; - 注册了如下的中间件:
- 针对Personal模式:通过
createWikiTranslationMiddleware函数创建的FilesystemMiddleware中间件; - 针对非
chat命令:- 如果需要翻译:通过
createWikiTranslationMiddleware函数创建的用来翻译的中间件; - 通过
createOpenWikiIndexMiddleware函数创建的用来辅助索引的中间件。
- 如果需要翻译:通过
- 针对Personal模式:通过
function createOpenWikiAgentGraph(
options: OpenWikiAgentGraphOptions,
): DeepAgent {
const wikiBackend = new OpenWikiLocalShellBackend({
docsOnly: options.command !== "chat",
openWikiIgnore: options.openWikiIgnore,
maxOutputBytes: 100_000,
outputMode: options.outputMode,
rootDir: options.cwd,
timeout: 120,
virtualMode: true,
});
const backend = createAgentBackend(wikiBackend);
const translation = resolveTranslationPlan(
options.command,
requireResolvedLanguage(options.language),
options.context.lastUpdate?.language,
);
const indexLabels = resolveIndexLabels(options.context.language);
const conceptType = resolveConceptTypeLabel(options.context.language);
return createDeepAgent({
model: options.model,
tools: createOpenWikiConnectorTools(options.outputMode),
checkpointer: options.checkpointer,
backend,
middleware: [
...(options.outputMode === "local-wiki"
? [
createFilesystemMiddleware({
backend,
permissions: AGENT_FILESYSTEM_PERMISSIONS,
tools: [
"ls",
"read_file",
"glob",
"grep",
"write_file",
"edit_file",
],
}),
]
: []),
...(options.command === "chat"
? []
: [
...(translation
? [
createWikiTranslationMiddleware(
wikiBackend,
options.outputMode,
options.model,
translation,
(message) => {
options.onEvent?.({ type: "text", text: message });
process.stderr.write(`${message}\n`);
},
(message) => {
options.onEvent?.({
type: "text",
text:`${message}\n\n`,
});
},
),
]
: []),
createOpenWikiIndexMiddleware(
wikiBackend,
options.outputMode,
indexLabels,
conceptType,
options.runTimestamp,
),
]),
],
skills: ["/skills/"],
subagents: [],
permissions: AGENT_FILESYSTEM_PERMISSIONS,
systemPrompt: createSystemPrompt(
options.command,
options.outputMode,
options.context.language,
options.openWikiIgnore,
),
});
}
3. 工具函数的注册
DeepAgent注册的工具函数列表由如下这个createOpenWikiConnectorTools函数创建。顾名思义,这个函数的工具函数与注册的Connector有关,LLM Wiki理论与实践-06:详细解析OpenWiki如何摄取外部数据源中曾经说过,由于Connector大都需要使用用户凭证,基本上只会应用在Personal模式下,所以在Code模式下不会有任何工具函数被创建。
export function createOpenWikiConnectorTools(
outputMode: OpenWikiOutputMode = "local-wiki",
): StructuredToolInterface[] {
if (outputMode === "repository") {
return [];
}
return [
new DynamicStructuredTool({
name: "openwiki_list_connectors",
description:
"List built-in OpenWiki connectors, their backends, required env var names, config paths, and raw data paths. Secret values are never returned.",
schema: {
type: "object",
properties: {},
additionalProperties: false,
} as const,
func: async () => stringifyToolResult(await listConnectors()),
}),
new DynamicStructuredTool({
name: "openwiki_list_mcp_tools",
description:`List live MCP tools for a configured MCP connector and write discovery under ${openWikiConnectorsDisplayPath}/<id>/raw. Input: {"connectorId":"notion"}. Use exact returned tool names.`,
schema: {
type: "object",
properties: {
connectorId: {
type: "string",
enum: ["custom-mcp", "notion"],
},
},
required: ["connectorId"],
additionalProperties: false,
} as const,
func: async (input) =>
stringifyToolResult(
await listMcpToolsForConnector(getConnectorId(input, "connectorId")),
),
}),
new DynamicStructuredTool({
name: "openwiki_call_mcp_tool",
description:`Call one exact discovered read-only MCP tool and write the result under ${openWikiConnectorsDisplayPath}/<id>/raw. Input: {"connectorId":"notion","toolName":"exact_tool_name","args":{"query":"Applied AI"}}.`,
schema: {
type: "object",
properties: {
args: {
type: "object",
additionalProperties: true,
},
connectorId: {
type: "string",
enum: ["custom-mcp", "notion"],
},
toolName: {
type: "string",
},
},
required: ["connectorId", "toolName"],
additionalProperties: false,
} as const,
func: async (input) =>
stringifyToolResult(
await callMcpToolForConnector(
getConnectorId(input, "connectorId"),
getStringInput(input, "toolName"),
getRecordInput(input, "args") ?? {},
),
),
}),
new DynamicStructuredTool({
name: "openwiki_ingest_connector",
description:`Run deterministic ingestion for one built-in connector and write raw data/manifests under ${openWikiConnectorsDisplayPath}/<id>/raw. Input: {"connectorId":"x","streams":["bookmarks"],"limit":1}.`,
schema: {
type: "object",
properties: {
connectorId: {
type: "string",
enum: [
"custom-mcp",
"git-repo",
"google",
"hackernews",
"notion",
"slack",
"web-search",
"x",
],
},
limit: { type: "number" },
streams: {
type: "array",
items: { type: "string" },
},
windowHours: { type: "number" },
},
required: ["connectorId"],
additionalProperties: false,
} as const,
func: async (input) =>
stringifyToolResult(
await ingestConnector(
getConnectorId(input, "connectorId"),
getIngestOptions(input),
),
),
}),
new DynamicStructuredTool({
name: "openwiki_ingest_all_connectors",
description:
"Run deterministic ingestion for all configured built-in connectors. Connectors that are not configured or enabled are skipped.",
schema: {
type: "object",
properties: {},
additionalProperties: false,
} as const,
func: async () => stringifyToolResult(await ingestAllConnectors()),
}),
new DynamicStructuredTool({
name: "openwiki_list_raw_items",
description:`List raw files for a connector under ${openWikiConnectorsDisplayPath}/<id>/raw. Input: {"connectorId":"x"}.`,
schema: {
type: "object",
properties: {
connectorId: {
type: "string",
enum: [
"custom-mcp",
"git-repo",
"google",
"hackernews",
"langsmith",
"notion",
"slack",
"web-search",
"x",
],
},
},
required: ["connectorId"],
additionalProperties: false,
} as const,
func: async (input) =>
stringifyToolResult(
await listRawItems(getConnectorId(input, "connectorId")),
),
}),
new DynamicStructuredTool({
name: "openwiki_read_raw_item",
description:`Read a raw connector file by connector ID and relative path. Only files inside ${openWikiConnectorsDisplayPath}/<id>/raw are allowed. Input: {"connectorId":"x","path":"2026-.../bookmarks.json","maxBytes":50000}.`,
schema: {
type: "object",
properties: {
connectorId: {
type: "string",
enum: [
"custom-mcp",
"git-repo",
"google",
"hackernews",
"langsmith",
"notion",
"slack",
"web-search",
"x",
],
},
maxBytes: {
type: "number",
},
path: {
type: "string",
},
},
required: ["connectorId", "path"],
additionalProperties: false,
} as const,
func: async (input) =>
stringifyToolResult(
await readRawItem(
getConnectorId(input, "connectorId"),
getStringInput(input, "path"),
getNumberInput(input, "maxBytes") ?? 100_000,
),
),
}),
];
}
- openwiki_list_connectors:列出内置Connector及其backend、所需环境变量名、配置路径与原始数据路径(不返回密钥值);
- openwiki_list_mcp_tools:列出所有MCP Connector(
custom-mcp+notion)提供的MCP工具,并把发现结果写入connectors/{connector_id}/raw目录; - openwiki_call_mcp_tool:按精确工具名调用MCP工具,参数经
args传入,结果写入connectors/{connector_id}/raw目录; - openwiki_ingest_connector:对单个内置Connector执行确定性摄取,支持
streams、limit、windowHours,原始数据/清单写入connectors/{connector_id}/raw; - openwiki_ingest_all_connectors:对所有已配置的内置Connector执行确定性摄取,未配置或未启用的Connector被跳过;
- openwiki_list_raw_items:列出指定Connector所在
connectors/{connector_id}/raw目录下的原始文件; - openwiki_read_raw_item:按Connector ID与相对路径读取原始文件,仅允许访问
connectors/{connector_id}/raw内部文件,支持maxBytes限制(默认 100000)。
OpenWiki之所以能够提取定义在Notion中的会议,但是我们开篇演示的实例没有这个能力,正是因为上述工具注册的缺失。
4. 读写Wiki的后端存储
和开篇演示实例一样,DeepAgent利用FilesystemMiddleware中间件提供的工具来完成针对Wiki的读写,涉及的后端类型为OpenWikiLocalShellBackend。如代码所示,它派生于LocalShellBackend,意味着它可以利用本地的Shell执行bash命令。
export class OpenWikiLocalShellBackend extends LocalShellBackend {
constructor(options: OpenWikiBackendOptions)
}
OpenWikiLocalShellBackend 在 LocalShellBackend 之上叠加了四类访问控制:
.openwikiignore路径排除;- Grounded Claims状态所有权保护;
docs-only写入约束(仅限openwiki/且可选页面白名单);- shell执行限制(Personal模式禁用、Code模式仅允许部分维护命令)。
createOpenWikiAgentGraph函数在根据当前配置将OpenWikiLocalShellBackend创建出来后,会将其传入如下这个createAgentBackend函数,它会合并针对如下两个路径的后端生成一个CompositeBackend对象:
- /skills/: 映射到物理路径
{openwiki-home}/skills上的FilesystemBackend,用来提供Agent Skill; - /conversation_history/: 映射到物理路径
{openwiki-home}/conversation_history上的FilesystemBackend,用来存储对话历史。
export function createAgentBackend(
wikiBackend: OpenWikiLocalShellBackend,
{
historyDir = openWikiConversationHistoryDir,
skillsDir = openWikiSkillsDir,
}: AgentBackendMountOptions = {},
): CompositeBackend {
return new OpenWikiCompositeBackend(wikiBackend, {
[CONVERSATION_HISTORY_MOUNT]: new FilesystemBackend({
rootDir: historyDir,
virtualMode: true,
}),
"/skills/": new FilesystemBackend({
rootDir: skillsDir,
virtualMode: true,
}),
});
}
export const CONVERSATION_HISTORY_MOUNT = "/conversation_history/"
DeepAgent后端正是此CompositeBackend对象,注册的FilesystemMiddleware中间件也是基于该对象创建的。{openwiki-home}/skills目录下默认会定义如下两个Skill。mermaid-diagrams用于在生成的Wiki页面中嵌入 Mermaid 图表,用可视化方式呈现运行时流程、状态机、数据模型等用文字难以清晰表达的内容。write-connector是OpenWiki内置连接器的开发规范文档,规定了从选型到交付的完整流程。。
📁 skills
├── 📁 mermaid-diagrams
| └── 📄 skill.md
└── 📁 write-connector
└── 📄 skill.md
5. 辅助翻译的中间件
如果执行openwiki --update或者openwiki personal --update命令生成Wiki时通过 --language(或 -l)参数指定了具体的语言,用来创建DeepAgent的createOpenWikiAgentGraph函数会调用createWikiTranslationMiddleware函数创建一个用来实施翻译的中间件。承载原始和目标语言TranslationPlan通过resolveTranslationPlan函数解析生成,并作为createWikiTranslationMiddleware函数的核心参数,其translateAll字段决定采用全量翻译还是增量翻译。
export function resolveTranslationPlan(
command: OpenWikiCommand,
requestedLanguage: string | undefined,
currentWikiLanguage: string | undefined,
): TranslationPlan | undefined
export function createWikiTranslationMiddleware(
backend: BackendProtocolV2,
outputMode: OpenWikiOutputMode,
model: BaseChatModel,
plan: TranslationPlan,
onWarning: (message: string) => void = (message) => {
process.stderr.write(`${message}\n`);
},
onStatus: (message: string) => void = (message) => {
process.stderr.write(`${message}\n`);
},
) {
return createMiddleware({
name: "OpenWikiTranslationMiddleware",
beforeAgent: async () => {
await translateWiki(
backend,
outputMode,
model,
plan,
onWarning,
onStatus,
);
},
});
}
export interface TranslationPlan {
target: string;
source: string;
translateAll: boolean;
}
创建的翻译中间件会在调用LLM之前调用translateWiki将Wiki现有的内容翻译成指定的语言。与此同时,语言相关的指令也会出现在调用LLM的提示词中,确保生成的Wiki采用指定的语言。
6. 索引中间件
createOpenWikiAgentGraph函数针对openwiki --update或者openwiki personal --update命令会调用createOpenWikiIndexMiddleware函数创建一个中间件。该中间件对于OpenWiki来说非常重要,它是确保生成的Wiki具有可靠质量的保障。
export interface IndexLabels {
files: string;
directories: string;
}
export const ENGLISH_INDEX_LABELS: IndexLabels = {
files: "Files",
directories: "Directories",
};
export function createOpenWikiIndexMiddleware(
backend: BackendProtocolV2,
outputMode: OpenWikiOutputMode,
labels: IndexLabels = ENGLISH_INDEX_LABELS,
conceptType: string = ENGLISH_CONCEPT_TYPE,
now: string = new Date().toISOString(),
claimSources?: () => ClaimEvidenceResources,
) {
let preparedWiki: PreparedWikiState | undefined;
return createMiddleware({
name: "OpenWikiIndexMiddleware",
beforeAgent: async () => {
preparedWiki = undefined;
preparedWiki = await prepareWikiForAuthoring({
backend,
outputMode,
conceptType,
runOperation: (operation, task) =>
inStage("build", task, {
errorClass: "okf_error",
errorDetail: operation,
}),
});
},
wrapToolCall: async (request, handler) => {
const result = await handler(request);
return addFrontmatterWarning(
result,
backend,
outputMode,
request.toolCall.name,
conceptType,
);
},
afterAgent: async () => {
if (!preparedWiki) {
throw new Error("Wiki finalization requires prepared run state.");
}
await finalizeWikiArtifacts({
backend,
outputMode,
labels,
conceptType,
prepared: preparedWiki,
at: now,
claimSources: claimSources?.(),
runOperation: (operation, task) =>
inStage("finalize", task, {
errorClass: "okf_error",
errorDetail: operation,
}),
});
},
});
}
中间件定义了如下三个钩子:
- beforeAgent:是Agent开始写作前的准备工作,做两件事:
- 按照OKF规范对现有的Wiki页进行修改(如果只有在验证内容不符合规范才会实施文件更新);
- 为每个页面生成一个概念快照(
ConceptSnapshot),记录下从Yaml元数据的generated字段(比如generated: { by: "openwiki/0.5.1", at: "2026-11-08T13:08:12.029Z" });
- wrapToolCall:通过封装工具调用拦截针对Wiki页的写入,并重新加载其内容按照OKF规范进行验证,并在不符合规范的情况下对内容进行修正;
- afterAgent:在Agent结束执行后对Wiki页面的内容进行总体验证和修正:
- 验证Wiki页中生成的Mermaid图表,如果没有通过验证会将mermaid围栏(mermaid fence)降级成纯文本形式;
- 扫描每个子目录,并在其中生成
index.md索引页; - 遍历所有Markdown文件,检查相对链接和GitHub风格标题锚点是否有效,把断链就地打上标记(而非让运行失败),并返回统计报告;
- 将执行本轮update命令的Actor(
openwiki/${OPENWIKI_VERSION})和时间戳以generated字段的形式写入更新的Wiki页。
2957



被折叠的 条评论
为什么被折叠?



