OpenViking 资源管理实战指南:add_resource 资源导入管线、增量更新与 Watch 机制全解析
导读
本指南基于 OpenViking 官方 API 文档,深入剖析资源(Resource)管理模块——这是 OpenViking 面向 Agent 的统一知识底座:外部知识(文档、代码库、网页、云文档)如何被导入、解析、建树、持久化到 AGFS,并经过摘要生成与向量化进入语义检索体系。读完本文,你将掌握 add_resource / temp_upload 两个核心 API 的完整参数语义与调用方式(HTTP / Python / TypeScript / Go SDK 及 CLI),理解递归爬虫、整站(sitemap/RSS)摄取、Git 仓库导入、Feishu 云文档等各类源的接入细节,并能通过 watch_interval 构建自动同步的增量更新机制,让知识库随上游源持续保鲜。
资源类型总览:OpenViking 能摄取哪些外部知识
资源(Resources)是 Agent 可以引用的外部知识。OpenViking 按功能对资源进行分类,覆盖文档、表格与演示文稿、代码、媒体、云文档、网页与整站等七大类别。
文档类(Documents)
| 类型 | 扩展名 | 处理说明 |
|---|---|---|
.pdf |
支持本地解析与 MinerU API 转换 | |
| Markdown | .md, .markdown, .mdown, .mkd |
原生支持,提取结构并按片段存储 |
| HTML | .html, .htm |
清理导航/广告后提取正文,转换为 Markdown |
| Word | .doc, .docx, .docm, .odt, .rtf |
使用 anydoc 将文本、标题、表格与内嵌图片提取为 Markdown |
| 纯文本 | .txt, .text |
直接导入与处理 |
| EPUB | .epub |
使用 anydoc 将电子书正文与内嵌图片转换为 Markdown |
表格与演示文稿
| 类型 | 扩展名 | 处理说明 |
|---|---|---|
| Excel | .xlsx, .xls, .xlsm, .xlsb, .ods, .csv |
使用 anydoc 将工作表转换为 Markdown 表格 |
| PowerPoint | .pptx, .ppt, .pptm, .pps, .ppsx, .ppsm, .pot, .odp |
使用 anydoc 提取幻灯片内容与内嵌图片为 Markdown |
代码类
| 类型 | 资源名 | 说明 |
|---|---|---|
| 代码文件 | *.py, *.js, ... |
支持 Python、JavaScript、Go、Rust、Java 等常见编程语言 |
| Git 协议仓库 | git://... |
Git URL、本地目录、.zip 压缩包;遵循 .gitignore,自动过滤 .git、node_modules 等目录 |
| Git 代码托管平台 | https://github.com/{org}/{repo} |
GitHub、GitLab、Bitbucket 等平台仓库 URL |
| Git 托管平台的 Raw 文件 | https://github.com/{org}/{repo}/raw/{branch}/{path} |
GitHub、GitLab、Bitbucket 等平台的原始文件下载 URL |
代码仓库导入是 OpenViking 的典型场景:本地目录扫描时会以标准 Git 语义尊重根目录及嵌套的 .gitignore,再以 ignore_dirs、include、exclude 进一步精化摄取范围(见下文参数详解)。
媒体类
| 类型 | 资源名 | 说明 |
|---|---|---|
| 图片 | *.jpg, *.jpeg, *.png, *.gif ... |
多种图片格式,通过 VLM(视觉语言模型)生成描述(实验性) |
| 视频 | *.mp4, *.avi, *.mov ... |
提取关键帧并用 VLM 分析(规划中) |
| 音频 | *.mp3, *.wav, *.m4a ... |
执行语音转写(规划中) |
云文档
| 类型 | 说明 |
|---|---|
| 飞书 / Lark | 基于 URL 导入,支持 doc/docx、wiki、电子表格、多维表格。默认使用 FEISHU_APP_ID 与 FEISHU_APP_SECRET 的应用凭证;用户态导入可传 args.feishu_access_token;用户态 Watch 还需传 args.feishu_refresh_token,并可选传 args.feishu_app_id / args.feishu_app_secret 应用对 |
网页(递归爬虫)
| 类型 | 资源名 | 说明 |
|---|---|---|
| 单页 / 递归爬取 | https://host/path |
默认只抓取入口页。设置 args.depth > 0 后按广度优先爬取同站链接;args.max_pages 仅限制收集的页面总数。每个页面由 trafilatura 提取为 Markdown |
递归爬虫支持的 args 包括:depth、max_pages、include_paths、exclude_paths、allow_external_links、skip_download_links。页面中发现的下载链接默认被跳过(skip_download_links=true),以避免导入 llms.txt 等 sidecar 文件;将其设为 false 可下载同站文件链接并计入 max_pages。include_paths / exclude_paths 采用路径前缀匹配(例如 /docs/ 只匹配以 /docs/ 开头的路径,绝不匹配 /blog/docs-tips 这类子串)。
路由规则:sitemap 风格的 URL(
https://host/sitemap.xml、https://host/feed.xml、*.atom等)以及显式指定args.site=true的请求会被路由到下述整站摄取;而https://github.com/{org}/{repo}这类 Git 托管 URL 会被路由到代码类导入。
整站(sitemap / RSS / Atom)
| 类型 | 资源名 | 说明 |
|---|---|---|
| Sitemap | https://host/sitemap.xml、https://host/sitemap-index.xml |
解析 sitemap 并将每个列出的页面作为单一资源树摄入(每个页面一个子节点)。嵌套的 <sitemapindex> 会被递归跟进。整个站点成为 viking://resources/<host> 下的一个资源 |
| RSS / Atom 订阅 | https://host/rss.xml、https://host/atom.xml、https://host/feed |
解析 RSS 2.0 / Atom,将每条条目作为一个树节点;正文从条目链接抓取(当订阅源自带完整内容时直接内联) |
| 整站自动发现 | https://host + args.site=true |
对裸域名或普通页面强制整站摄取:通过 robots.txt、HTML <link rel="alternate"> 自动发现及常规路径发现站点的 sitemap/RSS 后摄入 |
爬取是有界的、且不超过所列页面递归:由 parsers.webfeed 配置约束(max_pages、max_concurrency、politeness_delay、same_host_only、respect_robots、max_depth),并遵循 robots.txt。在 sitemap/feed URL 上设置 watch_interval 可让整个站点保持同步:每次刷新新增页面、自动移除已删除页面。当添加单个主页(未加 args.site)时,响应可能附带一行提示建议整站摄取——但绝不会自动爬取。
资源处理管线:从源头到语义索引的四阶段
资源在被添加时会经历以下处理阶段:
Source Input -> Parse -> Resource Tree Build -> Persistence -> Semantic Processing
↓ ↓ ↓ ↓ ↓
URL/File Parser TreeBuilder AGFS Summarizer/Vector
阶段一:解析(Parse)
- 由
UnifiedResourceProcessor(openviking/utils/media_processor.py)根据资源类型解析内容; - 支持多格式:文档(PDF/Markdown/Word)、表格(Excel/PPT)、代码、媒体文件等;
- 解析结果写入临时 VikingFS 目录;
- 媒体文件由 VLM 生成描述。
阶段二:资源树构建(TreeBuilder)
TreeBuilder.finalize_from_temp()(openviking/parse/tree_builder.py)扫描临时目录结构;- 构建资源树节点,处理 URI 冲突(自动重命名);
- 建立目录与资源之间的关系。
阶段三:持久化
- 检查目标 URI 是否已存在;
- 新资源:将临时文件移动到 AGFS 永久位置;
- 已存在资源:保留临时树以供后续 diff 对比;
- 获取生命周期锁,防止并发修改;
- 清理临时目录。
阶段四:语义处理
- 摘要生成:
Summarizer(openviking/utils/summarizer.py)生成 L0(摘要 abstract)与 L1(概览 overview)层级; - 向量索引:对内容向量化,供语义检索使用;
- 通过
SemanticQueue(openviking/storage/queuefs/semantic_queue.py)异步处理,可传wait=True等待完成。
非等待(wait=false)的 Git 仓库导入
对于 wait=false 的 Git 仓库源,OpenViking 会先校验仓库、解析目标 URI、预留最终 root_uri,然后立即返回;克隆/解析/finalize 在持久化后台任务中继续。立即响应包含 status、root_uri 与 task_id;可通过 GET /api/v1/tasks/{task_id} 轮询任务状态,Git 资源导入任务使用 queued、fetching、parsing、finalizing、processing_queue 等阶段。其他资源源在 wait=false 时会在响应前完成抓取/解析/finalize,返回的 task_id 仅追踪语义与嵌入队列的完成情况。
增量更新:Watch 任务机制
资源的增量更新通过 Watch Task 机制实现。
Watch 任务创建
- 在调用
add_resource时设置watch_interval > 0(单位:分钟)且源可重读(如 URL、sitemap、RSS 订阅)即可创建 Watch 任务; - 通过
temp_file_id引用的上传内容属于静态快照,不可被 Watch;本地源变化时需重新添加; - 可通过
to指定目标 URI;若省略,任务绑定到本次导入返回的root_uri; - 将 Watch 指向 sitemap/RSS/Atom URL 可保持整站同步:每次刷新重新读取订阅并重建资源树,新发布的页面被添加、被删除的页面自动移除;
WatchManager(openviking/resource/watch_manager.py)负责任务持久化;- 支持多租户权限控制(ROOT/ADMIN/USER 权限级别)。
目标所有权规则
- 账户内,原生(native)Watch 独占其解析出的目标,即使暂停期间也保持独占;新建原生 Watch 需要未被占用的目标;Connector Watch 不能与原生 Watch 共享目标;
- 多个 Connector Watch 可以共享同一目标。对相同源与目标重复导入会创建另一个独立 Watch;重复导入不会更新或重新激活已有 Watch。同一源导入到不同目标同样各自创建 Watch;
- Connector Watch 在首次导入前创建,并由调度器持有,直到该次导入记录其结果。
任务调度与执行
WatchScheduler(openviking/resource/watch_scheduler.py)每 60 秒检查一次到期任务(源码中DEFAULT_CHECK_INTERVAL = 60.0,默认max_concurrency = 4、task_timeout = 3 * 60 * 60,并通过asyncio.Semaphore做并发控制);- 默认并发控制防止重复执行;
- 到期任务自动重新调用
add_resource; - 更新任务的上次执行时间与下次执行时间。
任务管理操作
- 创建:
watch_interval > 0创建新任务并受上述目标所有权规则约束;目标占用冲突返回409 Conflict; - 更新或恢复:使用
PATCH /api/v1/watches/{task_id}修改参数或将is_active设为true。重复导入不会更新或重新激活任务; - 暂停或删除:用
PATCH /api/v1/watches/{task_id}配合is_active: false暂停,或用DELETE /api/v1/watches/{task_id}释放目标。原生导入若显式指定to且watch_interval <= 0,会暂停该目标上唯一可访问的 Watch;若有多个可访问 Watch 则返回409 Conflict。一次性 Connector 导入不触碰已有 Watch; - 查询:使用任务 ID,或在目标 URI 唯一标识单个可访问 Watch 时使用目标 URI。URI 查询在多个可访问 Watch 时返回
409 Conflict;请用task_id查询、更新或删除单个任务。
API 参考:add_resource
向知识库添加资源。SDK 支持本地文件/目录、URL 及其他源;裸 HTTP 调用通过 path 接收远程 URL、或通过 temp_file_id 接收已上传的本地文件。上传内容属于静态快照,因此不能与 watch_interval > 0 组合使用。
1. 实现概览与入口
该端点(POST /api/v1/resources)是资源管理的核心入口,支持从各种源添加资源,并可选等待语义处理与向量化完成。
处理流程:
- 识别并校验资源源(URL 或已上传的临时文件);
- 解析目标 URI;
- 调用对应格式的 Parser;
args.parse_mode控制转换后的 Markdown 正文是否可被拆分; - 构建目录树并写入 AGFS;
- 按
processing_mode执行摄取后处理:semantic_and_vectors生成语义产物与向量;vectors_only跳过语义理解、仅入队文件向量化; - 当
wait=true时等待语义处理/向量化完成;wait=false时返回task_id供队列跟踪; - 若
reason非空,将其追加到固定资源原因会话,走常规记忆提取管线,使合适的用户记忆可引用该资源 URI; - 若指定了
watch_interval,创建定时更新任务。
代码入口:
- sdk/python/openviking_sdk/client.py:
AsyncHTTPClient.add_resource—— Python SDK 入口; - openviking/server/routers/resources.py:
add_resource—— HTTP 路由(校验to/parentURI、解析临时文件、将请求转发给服务层,并拒绝"上传内容 + watch_interval > 0"的组合); - openviking/service/resource_service.py:核心服务实现,负责路由到
_submit_resource_ingestion完成摄取; crates/ov_cli/src/handlers.rs:handle_add_resource—— CLI 处理入口。
从源码看,路由层还会调用 resolve_path_variables 展开路径变量(如 {calendar:today})、通过 validate_content_target_uri 校验目标 URI,并在 TempUploadStore.resolve_for_consume 中消费临时上传文件——这些正是 CLI 示例中"路径变量 + 自动建父目录"能力得以实现的底层支撑。
2. 参数详解
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| path | string | 否 | - | 远程资源 URL(HTTP/HTTPS/Git)。与 temp_file_id 互斥 |
| temp_file_id | string | 否 | - | 临时上传文件 ID。与 path 互斥 |
| to | string | 否 | - | 本次导入的最终位置。目标已存在则刷新。与 parent 互斥 |
| parent | string | 否 | - | 父级 Viking URI(资源置于该目录下)。与 to 互斥 |
| create_parent | bool | 否 | False | 父目录不存在时自动创建(服务端标志) |
| reason | string | 否 | "" | 添加资源的理由。非空时 OpenViking 会将其与资源 URI 一起送入常规会话记忆提取管线,在生成的记忆中记录资源引用 |
| instruction | string | 否 | "" | 语义提取的处理指令(实验性功能) |
| wait | bool | 否 | False | 返回前是否等待语义处理与向量化完成 |
| timeout | float | 否 | None | 超时秒数,仅 wait=True 时生效 |
| strict | bool | 否 | False | 是否使用严格模式 |
| ignore_dirs | string | 否 | None | 需要忽略的目录名(逗号分隔) |
| include | string | 否 | None | 需要包含的文件模式(glob) |
| exclude | string | 否 | None | 需要排除的文件模式(glob) |
| directly_upload_media | bool | 否 | True | 是否直接上传媒体文件 |
| preserve_structure | bool | 否 | None | 是否保留目录结构 |
| args | object | 否 | {} |
透传给源解析器/访问器的解析器特定导入选项(详见下文) |
| watch_interval | float | 否 | 0 | 定时更新间隔(分钟)。>0 为可重读源创建新 Watch(受目标所有权规则约束);上传的 temp_file_id 快照不可被 Watch。<=0 不创建 Watch:带显式 to 的原生导入会暂停单个可访问任务(不明确时返回 409),Connector 导入则不动已有 Watch。显式 to 优先,否则 Watch 绑定到导入的 root_uri |
| is_active | bool | 否 | True | Watch 初始调度状态。false 要求 watch_interval > 0 且提供 to 或 parent(parent 仅原生飞书 URL 导入支持;Connector 导入仍要求精确 to)。初始导入仍执行一次,之后 Watch 保持暂停 |
| processing_mode | string | 否 | semantic_and_vectors |
摄取后处理模式。semantic_and_vectors 为常规流程:生成语义产物(.abstract.md、.overview.md)与向量;vectors_only 跳过语义理解/VLM 摘要,仅对当前资源文件做向量化 |
| telemetry | TelemetryRequest | 否 | False | 是否返回遥测数据 |
args 细说:原生 HTTPS Git 导入与 Watch 可通过 TLS 传递 HTTP Basic 凭证:args.auth_config={"username":"oauth2","token":"..."}(username 默认 oauth2)。Git 的 branch 或 commit 保持在 args 顶层。要通过 HTTP(S) URL 导入私有 TOS 对象,需且仅需传一个非空字符串:args.tos_signature(作为 X-Tos-Signature 头发送)或 args.tos_access(作为 X-Tos-Access 头发送)。TOS 凭证仅用于当前 HEAD/GET 抓取(以快照方式暂存),不会持久化到资源元数据或队列任务。args.parse_mode 接受 default(既有拆分行为)或 no_split(将每个源文档解析并转换为一个 Markdown 正文)。例如 args.site=true/false 强制/退出整站(sitemap/RSS)摄取;args.max_pages 等覆盖 webfeed 配置;递归爬虫接受 args.depth、args.max_pages、args.include_paths、args.exclude_paths、args.allow_external_links、args.skip_download_links;飞书用户态导入传 args.feishu_access_token。核心 add_resource 字段如 path、to、watch_interval、include、exclude 不允许出现在 args 内。
其他重要注意事项:
to与parent不能同时指定。to是最终保存位置:目标不存在则创建,已存在则刷新;若目标是目录,当前导入未产生的旧文件或子目录可能被移除。parent是目标目录,适合在既有目录下新增资源;需要自动建目录时用create_parent=true或 CLI 的--parent-auto-create。当导入的root_uri与to相同时,语义与向量处理会复用未变化的内容,仅处理变更部分;- 创建资源需要目标父目录的写权限;更新显式
to需要该目标的写权限。这些检查在任务入队前执行。自动命名按实际 URI 占用情况,因此不可读的冲突名会依次选择_1、_2等,而不是尝试覆盖; wait=false时,status=accepted表示预检通过且任务已入队,不代表资源处理完成,请用返回的task_id查询最终状态;- 若
to与parent均省略,服务端可能使用当前用户的add_targets.resource_uri覆盖项,再退到server.user_config_defaults.add_targets.resource_uri;两者均未设置时保持旧的目标解析行为; - 资源目标可使用公共
viking://resources/...、home 别名viking://~/resources/...、显式用户viking://user/{user_id}/resources/...或 peer 路径viking://user/{user_id}/peers/{peer_id}/resources/...。home 别名会按已认证请求身份展开为规范路径;无 uid 的viking://user/resources/...拼写会被拒绝,并提示改用viking://~/resources/...; user_id与peer_id路径段必须是安全的单段标识符(如alice、web-visitor-alice),包含路径分隔符、.、..、:或+的值会被拒绝;path与temp_file_id不能同时指定;裸 HTTP 导入本地文件需先经 temp_upload 上传获得temp_file_id;- 仅 Git 仓库源在
wait=false时走完整后台导入;OpenViking 在返回task_id前完成仓库预检与目标规划; - 原生 HTTPS Git 凭证在
watch_interval <= 0时仅请求本地有效。当watch_interval > 0时,OpenViking 将仓库绑定的用户名/token 存入私有 Watch 状态,仅在后续 Git 抓取时恢复使用。凭证不会出现在常规队列负载及 Watch API/MCP/CLI 响应中。Git PAT 无通用刷新流程;token 过期或被吊销时请重建 Watch。URL 内嵌凭证(如https://user:token@host/repo.git)仍被接受并原样传递;由于该 URL 同时是源标识,可能被记录在进程参数、日志、队列、资源元数据与 Watch 状态中。新集成建议使用args.auth_config。明文 HTTP 认证及args.auth_config的认证重定向仍被拒绝; - token 通过 HTTPS 请求体传输。生产环境应保持诊断请求体转储关闭,因为显式开启可能记录机密;
- 由
reason生成的记忆与session.commit走同一管线,使用reason、资源 URI、可用源名与可用目录摘要;不检查或展开完整资源内容。OpenViking 写入entities、events、preferences等既有记忆类型,而非专门的资源记忆目录; - 删除资源时,OpenViking 会在删除前扫描当前上下文指向的自有或 peer 记忆,移除由该
reason引入的匹配资源 URI 与内容,并刷新受影响记忆的语义索引; - 其他源在
wait=false时会在返回前完成源解析、目标解析与 AGFS 写入,仅语义与嵌入队列异步继续; processing_mode=vectors_only不调用 VLM 语义理解阶段,不生成/刷新.abstract.md/.overview.md;对既有目标保留已有语义产物与语义向量,仍会更新资源树、在build_index=true时对当前非隐藏文件向量化,并移除刷新时被删除文件的细节向量;processing_mode属于add_resource;管理员reindexAPI/CLI 继续使用mode(vectors_only、semantic_and_vectors、prune_orphans)对已摄取数据做维护操作;watch_interval > 0时,Watch 任务绑定到to(若提供),否则绑定到本次导入返回的root_uri;无稳定root_uri时请求失败并要求显式to;- Connector 导入的
is_active=false在提交前创建暂停的 Watch;原生飞书导入通过资源队列携带is_active,在解析出资源 URI 后创建 Watch。两种情况初始导入都执行一次且定时调度保持禁用; - 飞书/Lark 应用凭证导入不传
args.feishu_access_token,OpenViking 沿用既有应用凭证流程,SDK 由app_id与app_secret获取 app/tenant token,支持一次性导入与watch_interval > 0两种模式; - 飞书/Lark 一次性用户态导入传
args={"feishu_access_token": "u-..."}且watch_interval <= 0,用户 token 仅用于当前导入、不被存储; - 飞书/Lark 用户态 Watch 传
args={"feishu_access_token": "u-...", "feishu_refresh_token": "r-..."}且watch_interval > 0,可同时传feishu_app_id与feishu_app_secret;OpenViking 将该应用对存入私有 Watch 任务状态,用于刷新该 Watch 的用户 token; - 若请求省略应用对,用户态 Watch 使用
FEISHU_APP_ID/FEISHU_APP_SECRET,或ov.conf中的feishu.app_id/feishu.app_secret。飞书刷新 token 绑定其签发应用,使用的应用凭证必须与提供的用户 token 匹配; - Watch 任务 token 状态与请求提供的应用凭证存储在内部控制文件
viking://resources/.watch_tasks.json中,对 Watch API/MCP/CLI 响应隐藏;若启用了 VikingFS 文件加密,该控制文件落盘加密,否则服务端控制文件明文包含该私有状态; - 本地目录输入按标准 Git 语义尊重
.gitignore(根及嵌套),ignore_dirs、include、exclude进一步精化摄取内容; - 目录摄取在至少一个被选文件成功时是尽力而为:失败文件在
meta.failed_files中报告、成功文件提交。嵌套 ZIP 的叶子失败以bundle.zip/path/to/file这类归档限定路径报告并保留其远程任务 ID。若没有文件成功、或过滤器未选中任何可处理文件,任务失败且不保留空资源目录; args.parse_mode=no_split仍调用常规格式 Parser:PDF、Word、PowerPoint、HTML 等受支持文档被转换为 Markdown,但跳过基于标题、段落与尺寸的拆分。目录导入对每个受支持文档独立应用该模式,并继续遵循.gitignore、过滤器与preserve_structure。该模式下配置了 Understanding 的目录文件回退到原生 Parser;无原生解析支持的文件类型记录在meta.failed_files,不阻止其他被选文件成功;- 单文件输入 +
no_split且省略to时,若解析恰好产生一个可见文件,该文件直接存到解析出的父目录下(例如guide.md成为viking://resources/guide.md),不创建包装目录或目录级.abstract.md/.overview.md;若解析同时产生图片等其他可见文件,则保留包装目录。显式to始终作为精确最终 URI 保留; no_split只改变存储的 Markdown 布局;语义处理、文件向量化及内部嵌入分块保持不变。相对 Markdown 链接按同一 no-split 输出布局解析,因此链接不会指向仅拆分存在的路径。该模式下目录文件不调用 Understanding;- 要直接创建或更新纯文本,请使用 content/write 而非
add_resource。资源摄取与内容写入后,语义处理与嵌入会自动刷新。
3. 使用示例
HTTP API
POST /api/v1/resources
Content-Type: application/json
# 从 URL 添加资源
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "https://example.com/guide.md",
"reason": "User guide documentation",
"wait": true
}'
# 导入并 Watch 一个私有 HTTPS Git 仓库
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "https://git.example.com/team/private-repo.git",
"to": "viking://resources/private-repo",
"watch_interval": 60,
"args": {
"branch": "main",
"auth_config": {
"username": "oauth2",
"token": "replace-with-your-token"
}
}
}'
# 只构建向量、不做 VLM 语义理解
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "https://example.com/guide.md",
"to": "viking://resources/guide",
"processing_mode": "vectors_only",
"wait": true
}'
# 递归爬取站点:沿同站链接扩展;depth 限制层数,max_pages 限制收集页数
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "https://docs.openviking.ai/getting-started/01-introduction",
"wait": true,
"timeout": 60,
"args": { "depth": 1, "max_pages": 10 }
}'
# 从本地文件添加(需先 temp_upload)
TEMP_FILE_ID=$(
curl -s -X POST http://localhost:1933/api/v1/resources/temp_upload \
-H "X-API-Key: your-key" \
-F "file=@./documents/guide.md" \
| jq -r '.result.temp_file_id'
)
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d "{
\"temp_file_id\": \"$TEMP_FILE_ID\",
\"to\": \"viking://resources/guide.md\",
\"reason\": \"User guide\"
}"
# 添加到当前用户私有资源根
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d "{
\"temp_file_id\": \"$TEMP_FILE_ID\",
\"parent\": \"viking://~/resources/docs\",
\"create_parent\": true
}"
# 一次性用户 token 添加飞书文档
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "https://example.feishu.cn/docx/doc_token",
"args": {
"feishu_access_token": "u-..."
}
}'
# 带定时用户 token 刷新的飞书文档
curl -X POST http://localhost:1933/api/v1/resources \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "https://example.feishu.cn/docx/doc_token",
"to": "viking://resources/feishu/doc",
"watch_interval": 1440,
"args": {
"feishu_access_token": "u-...",
"feishu_refresh_token": "r-...",
"feishu_app_id": "cli_...",
"feishu_app_secret": "..."
}
}'
Python SDK
from openviking_sdk import SyncHTTPClient
client = SyncHTTPClient(url="http://localhost:1933", api_key="your-key")
client.initialize()
# 添加本地文件
result = client.add_resource(
path="./documents/guide.md",
options={"reason": "User guide documentation"},
)
print(f"Added: {result['root_uri']}")
# 将每个文档解析为单个 Markdown 正文(不拆分)
result = client.add_resource(
path="./documents",
options={"args": {"parse_mode": "no_split"}},
)
# 从 URL 添加到指定位置
result = client.add_resource(
path="https://example.com/api-docs.md",
to="viking://resources/external/api-docs.md",
options={"reason": "External API documentation"},
)
# 递归爬取站点(同站 BFS;depth 层数,max_pages 上限)
result = client.add_resource(
path="https://docs.openviking.ai/getting-started/01-introduction",
wait=True,
timeout=180,
options={
"args": {"depth": 1, "max_pages": 10},
},
)
# 带路径前缀过滤器的递归爬取,同时下载文件链接
result = client.add_resource(
path="https://docs.openviking.ai/",
options={
"args": {
"depth": 2,
"max_pages": 50,
"include_paths": ["/docs/"],
"exclude_paths": ["/changelog"],
"skip_download_links": False,
},
},
)
# 添加到当前用户私有资源根
result = client.add_resource(
path="./documents/guide.md",
parent="viking://~/resources/docs",
options={
"create_parent": True,
},
)
# 等待处理完成
client.wait_processed()
# 启用定时更新
client.add_resource(
path="./documents/guide.md",
to="viking://resources/guide.md",
options={
"watch_interval": 60, # 每 60 分钟更新一次
},
)
# 一次性用户 token 添加飞书文档
client.add_resource(
path="https://example.feishu.cn/docx/doc_token",
options={"args": {"feishu_access_token": "u-..."}},
)
# 带定时用户 token 刷新的飞书文档
client.add_resource(
path="https://example.feishu.cn/docx/doc_token",
to="viking://resources/feishu/doc",
options={
"watch_interval": 1440,
"args": {
"feishu_access_token": "u-...",
"feishu_refresh_token": "r-...",
"feishu_app_id": "cli_...",
"feishu_app_secret": "...",
},
},
)
从 Python SDK 源码看,add_resource 会做三件事:归一化 to/parent URI(VikingURI.normalize)、通过 _build_options_payload 装配受保护字段的请求体、检测本地路径(目录先压缩为 zip 再上传临时文件),最终 POST /api/v1/resources 并返回 result——本地文件上传因此对调用方完全透明。
TypeScript SDK
const task = await client.addResource("https://example.com/docs", {
to: "viking://resources/docs/",
wait: true,
args: { parse_mode: "no_split" },
});
console.log(task);
Go SDK
result, err := client.AddResource(ctx, "./documents/guide.md", &openviking.AddResourceOptions{
Reason: "User guide documentation",
Wait: true,
Args: map[string]any{"parse_mode": "no_split"},
})
if err != nil {
return err
}
fmt.Println(result["root_uri"])
CLI
# 添加本地文件
ov add-resource ./documents/guide.md --reason "User guide"
# 将每个文档解析为一个 Markdown 正文
ov add-resource ./documents --args parse_mode:no_split
# 从 URL 添加
ov add-resource https://example.com/guide.md --to viking://resources/guide.md
# 递归爬取站点:除非 depth>0,否则只抓取入口页
ov add-resource "https://docs.openviking.ai/getting-started/01-introduction" \
--args="depth:1,max_pages:10"
# 带路径前缀过滤器的递归爬取(仅 /docs/,排除 changelog)
ov add-resource "https://docs.openviking.ai/" \
--args='{"depth":2,"max_pages":50,"include_paths":["/docs/"],"exclude_paths":["/changelog"]}'
# 页面下载链接默认跳过;选择接收 PDF/TXT/MD 等文件
ov add-resource "https://example.com/docs" \
--args="depth:1,max_pages:20,skip_download_links:false"
# 等待处理完成
ov add-resource ./documents/guide.md --wait
# 启用定时更新(每 60 分钟检查一次)
ov add-resource https://github.com/example/repo.git --to viking://resources/guide.md --watch-interval 60
# 启用定时更新并绑定到本次导入创建的 URI
ov add-resource https://github.com/example/repo.git --watch-interval 60
# 通过 PATCH /api/v1/watches/{task_id} 以 {"is_active": false} 暂停 Watch。
# 原生导入使用 --watch-interval 0 也会暂停目标上唯一可访问的 Watch。
# 一次性 Connector 导入不动已有 Watch。
# 一次性用户 token 添加飞书文档
ov add-resource https://example.feishu.cn/docx/doc_token --args feishu_access_token:u-...
# 带定时用户 token 刷新的飞书文档
ov add-resource https://example.feishu.cn/docx/doc_token \
--to viking://resources/feishu/doc \
--watch-interval 1440 \
--args feishu_access_token:u-... \
--args feishu_refresh_token:r-... \
--args feishu_app_id:cli_... \
--args feishu_app_secret:...
# 添加并置于父目录下(父目录必须存在)
ov add-resource ./documents/guide.md --parent viking://resources/docs
# 添加到当前用户私有资源根
ov add-resource ./documents/guide.md --parent viking://~/resources/docs
# 添加到指定 peer 的私有资源根
ov add-resource ./documents/guide.md \
--parent viking://user/alice/peers/web-visitor-alice/resources/docs
# 父目录不存在时自动创建
ov add-resource ./documents/guide.md -p viking://resources/docs/2026/05/07
# 或使用完整标志
ov add-resource ./documents/guide.md --parent-auto-create viking://resources/docs/2026/05/07
# 使用路径变量 + 自动创建
ov add-resource ./documents/guide.md -p viking://resources/docs/{calendar:today}
响应示例
HTTP API 响应(JSON,wait=true)
{
"status": "ok",
"result": {
"status": "success",
"root_uri": "viking://resources/guide.md",
"temp_uri": "viking://temp/username/04291108_b62dc7/guide.md",
"source_path": "./documents/guide.md",
"meta": {},
"errors": [],
"queue_status": {
"pending": 5,
"processing": 2,
"completed": 10
}
},
"telemetry": {
"operation_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
HTTP API 响应(JSON,非 Git wait=false)
{
"status": "ok",
"result": {
"status": "accepted",
"root_uri": "viking://resources/guide",
"task_id": "uuid-xxx"
}
}
使用返回的 task_id 轮询 /api/v1/tasks/{task_id} 获取队列完成状态。Git 仓库源在 wait=false 时,同一端点追踪完整后台导入,任务完成后其 result 包含完整导入结果(含 queue_status)。
CLI 响应(默认表格格式)
Note: Resource is being processed in the background.
Use 'ov wait' to wait for completion, or 'ov observer queue' to check status.
status accepted
root_uri viking://resources/01-overview
task_id uuid-xxx
CLI 响应(JSON 格式,使用 -o json)
{
"status": "accepted",
"root_uri": "viking://resources/01-overview",
"task_id": "uuid-xxx"
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
status |
string | 处理状态:accepted 表示已入队,success 表示成功完成,error 表示失败 |
root_uri |
string | 资源在 OpenViking 中的最终 URI |
task_id |
string | (可选,仅 wait=false 时返回)用于轮询 /api/v1/tasks/{task_id} 的任务 ID。非 Git 导入用于队列跟踪;Git 仓库导入用于完整后台导入跟踪 |
temp_uri |
string | 导入过程中产生的临时 URI |
source_path |
string | 原始源文件路径或 URL |
meta |
object | 资源解析得到的元数据(文件类型、大小等) |
errors |
array | 处理过程中遇到的错误列表 |
warnings |
array | (可选)警告列表(仅 strict=False 时返回) |
queue_status |
object | (可选,仅 wait=true 时返回)队列处理状态,含 pending、processing、completed 计数 |
add-resource 任务完成结果
对于 wait=false 的 Git 仓库源,后台任务 task_type="add_resource",resource_id 等于返回的 root_uri。运行中的任务记录可能包含 stage。轮询 /api/v1/tasks/{task_id} 直至完成,其嵌套 result 包含最终队列汇总与 context_count:
{
"status": "ok",
"result": {
"task_id": "uuid-xxx",
"task_type": "add_resource",
"status": "completed",
"resource_id": "viking://resources/guide",
"result": {
"status": "success",
"root_uri": "viking://resources/guide",
"queue_status": {
"Embedding": {
"processed": 11,
"requeue_count": 0,
"error_count": 0,
"errors": []
}
},
"context_count": 11
}
}
}
context_count 是本次上传任务成功生成并建立索引的上下文(context)数量——一条上下文在其嵌入记录成功写入后才被计数。它不是 root_uri 下已存上下文的总数。若服务端在任务持久化最终指标前重启,该字段会被省略而非报告不完整计数。
API 参考:temp_upload
上传临时文件,用于后续通过 add_resource 或 add_skill 导入本地文件。
1. 实现概览与入口
该端点将本地文件上传到服务端管理的临时存储,返回供后续 API 调用使用的 temp_file_id。这是一个辅助端点,通常不经调用方直接调用,而是由 SDK 或 CLI 自动使用。
处理流程:
- 接收上传文件;
- 根据
upload_mode选择临时上传后端; - 保存文件并记录原始文件名;
- 返回临时文件 ID。
代码入口:
- openviking/server/routers/resources.py:
temp_upload—— HTTP 路由; - openviking/service/resource_service.py:服务实现。
2. 参数详解
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| file | UploadFile | 是 | - | 上传文件(multipart/form-data) |
| telemetry | bool | 否 | False | 是否返回遥测数据 |
| upload_mode | string | 否 | "local" |
临时上传模式。local 保持既有单节点行为;shared 上传到共享临时存储,供分布式部署使用 |
补充说明:
- 默认值为
local,既有客户端除非显式选择shared,否则保持原行为; - 仅在明确需要分布式共享临时上传时才使用
upload_mode=shared; shared模式返回形如shared_<upload_id>的temp_file_id。同一账户在其仍可用期间可重复消费;- 新的 shared 上传会创建内部
viking://upload/<created_at_ms>-<uuid>/目录,内含content与meta。目录名中的 13 位 Unix 毫秒时间戳即上传创建时间;meta最后写入,标志上传完成。这些对象不属于常规文件系统浏览面; - shared 上传保留时长由
server.temp_upload.ttl_seconds决定(默认 12 小时)。每次新的 shared 上传会列一次内部上传根目录、从每个一级上传目录名解析创建时间戳,并递归移除过期目录(不依赖文件系统修改时间)。
3. 使用示例
HTTP API
POST /api/v1/resources/temp_upload
Content-Type: multipart/form-data
curl -X POST http://localhost:1933/api/v1/resources/temp_upload \
-H "X-API-Key: your-key" \
-F "file=@./documents/guide.md"
分布式 / 共享上传:
curl -X POST http://localhost:1933/api/v1/resources/temp_upload \
-H "X-API-Key: your-key" \
-F "file=@./documents/guide.md" \
-F "upload_mode=shared"
Python SDK:add_resource、add_skill 等端点会自动处理本地文件上传,无需手动调用本端点。若要在 HTTP 客户端模式下启用分布式共享临时上传,在 ovcli.conf 中设置 upload.mode 为 "shared" 即可。
Go SDK:client.AddResource、client.AddSkill、client.ImportOVPack、client.RestoreOVPack 会自动为本地文件调用 temp_upload。设置 openviking.Config{UploadMode: "shared"} 即可请求共享临时上传。
CLI:CLI 命令同样自动处理本地文件上传,无需手动调用本端点。
响应示例
{
"status": "ok",
"result": {
"temp_file_id": "upload_abc123def456.md"
},
"telemetry": {
"operation_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
shared 模式可能的响应:
{
"status": "ok",
"result": {
"temp_file_id": "shared_7f3c1b8d4f2e4b1bb0f6e8b2d9a4c123"
}
}
进一步阅读
- 文件系统 —— 文件与目录操作;
- Skills —— Skill 管理 API;
- 检索 —— 搜索与上下文获取;
- ovpack 指南 —— ovpack 导入/导出详细文档;
- OpenViking Assets —— 声明式资源集协议与使用指南。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00