OpenViking Server/CLI 模式技术方案
一、背景与目标
1.1 当前架构
OpenViking 目前主要以 Python SDK 形式提供服务,支持两种部署模式:
- Embedded 模式:自动启动本地 AGFS 子进程和 VikingVectorIndex(单例)
- Service 模式:连接远程 AGFS 和 VectorDB 服务(非单例)
1.2 现有问题
| 问题 | 描述 |
|---|---|
| 缺少统一 CLI | 无法通过命令行管理 OpenViking |
| 服务分散 | AGFS 和 VectorDB 需要分别启动 |
| 无 HTTP API | 核心功能没有 HTTP API 暴露 |
| 部署复杂 | 生产环境需要手动协调多个服务 |
1.3 目标
- CLI 模式:提供完整的命令行工具
- Server 模式:提供统一的 HTTP API 服务
- 保持兼容:现有 SDK 使用方式不变
二、竞品分析与最佳实践
2.1 竞品对比
| 产品 | CLI 设计 | Server 模式 | API 风格 | 部署模式 |
|---|---|---|---|---|
| Chroma | chroma run/db/login |
内置 FastAPI | RESTful | in-memory/persistent/client-server |
| Qdrant | 无独立 CLI | 独立服务 | RESTful + gRPC | standalone/distributed |
| Milvus | milvus_cli 交互式 |
独立服务 | RESTful + gRPC | lite/standalone/distributed |
| Mem0 | 无 CLI | Platform + OSS | RESTful | managed/self-hosted |
| LlamaIndex | 无统一 CLI | 微服务架构 | RESTful | self-hosted |
2.2 Chroma 设计参考
Chroma 的 CLI 设计简洁实用:
chroma run --path <directory> # 启动服务器
chroma db create <name> # 创建数据库
chroma login # 认证登录
chroma browse # 浏览数据
借鉴点:
- 三种运行模式(in-memory/persistent/client-server)
- 简洁的命令结构
- 环境变量配置支持
2.3 Qdrant API 设计参考
Qdrant 的 REST API 采用资源层级结构:
POST /collections/{collection_name}/points/search
GET /collections/{collection_name}/points/{id}
PUT /collections/{collection_name}/points
借鉴点:
- 资源层级 URL 设计
- POST 用于复杂查询(请求体包含向量和过滤条件)
- 完善的过滤 DSL
- 一致性级别控制
2.4 Milvus CLI 设计参考
Milvus CLI 采用模块化架构:
milvus_cli/
├── Connection.py # 连接管理
├── Collection.py # 集合操作
├── Database.py # 数据库管理
├── Index.py # 索引管理
├── Partition.py # 分区管理
├── Data.py # 数据操作
├── Role.py # 角色管理
└── User.py # 用户管理
借鉴点:
- 模块化命令组织
- 交互式 CLI 支持
- 完整的 RBAC 支持
2.5 Mem0 架构参考
Mem0 的双部署模式:
- Platform API:托管服务,API Key 认证
- OSS SDK:自托管,直接调用
借鉴点:
- 统一的搜索管道(Platform 和 OSS 共用)
- 复杂的过滤 DSL(AND/OR 逻辑)
- 用户隔离(user_id scoping)
2.6 Redis/MySQL 客户端设计参考
Redis 和 MySQL 的客户端设计模式:
- Server 独立运行:Server 作为独立进程启动,不依赖 Client
- Client 仅连接:Client 只负责连接 Server,不负责启动或管理 Server
- 配置分离:Server 配置和 Client 配置分开管理
# Redis 示例
import redis
r = redis.Redis(host='localhost', port=6379) # 仅连接,不启动 Server
r.set('foo', 'bar')
# MySQL 示例
import mysql.connector
conn = mysql.connector.connect(host='localhost', user='root') # 仅连接
借鉴点:
- Client 无需
initialize()方法 - 连接参数通过构造函数传入
- Server 生命周期独立于 Client
2.7 响应格式与错误处理最佳实践
Qdrant 响应格式
{
"status": "ok",
"time": 0.123,
"result": {...},
"usage": {"cpu": 100, "vector_io_read": 40}
}
- 统一的
status/time/result包装 - 可选的
usage字段用于资源监控 - 错误通过 HTTP 状态码 + 消息字符串返回
Milvus 错误码
# 数字错误码 + 消息
(code=1, message="UnexpectedError...")
(code=2, message="Connection timeout...")
(code=2200, message="Retry exhausted...")
- 基于 gRPC 的数字错误码
- 包含
code,message,reason三元组
gRPC 标准状态码(业界标准)
| 码 | 名称 | HTTP | 说明 |
|---|---|---|---|
| 0 | OK | 200 | 成功 |
| 3 | INVALID_ARGUMENT | 400 | 参数无效 |
| 5 | NOT_FOUND | 404 | 资源不存在 |
| 7 | PERMISSION_DENIED | 403 | 权限不足 |
| 13 | INTERNAL | 500 | 内部错误 |
| 14 | UNAVAILABLE | 503 | 服务不可用 |
| 16 | UNAUTHENTICATED | 401 | 未认证 |
借鉴点:
- gRPC 17 种标准状态码覆盖所有常见场景
- 明确的 HTTP 状态码映射
- 可重试 vs 不可重试错误分类
2.8 最佳实践总结
| 方面 | 最佳实践 | 来源 |
|---|---|---|
| CLI 结构 | 动词-名词命令(run, db create) |
Chroma |
| API 路由 | 资源层级(/collections/{name}/points) |
Qdrant |
| 认证 | API Key + 可选 JWT | Qdrant, Mem0 |
| 部署模式 | 三层(embedded/standalone/distributed) | Milvus |
| 过滤系统 | JSON DSL(AND/OR/比较操作符) | Mem0, Qdrant |
| 模块化 | 按资源类型组织命令 | Milvus |
| Client 设计 | 仅连接,不管理 Server 生命周期 | Redis, MySQL |
| 响应格式 | {status, time, result} 统一包装 |
Qdrant |
| 错误码 | gRPC 标准状态码(字符串形式) | gRPC, Milvus |
| HTTP 映射 | 错误码到 HTTP 状态码的标准映射 | gRPC |
2.9 参考资料
- Chroma Documentation - CLI 设计参考
- Qdrant API Reference - REST API 设计参考
- Milvus CLI - 模块化 CLI 参考
- Mem0 Documentation - 双部署模式参考
- LlamaIndex Architecture - 微服务架构参考
- gRPC Status Codes - 标准错误码定义
- gRPC HTTP Mapping - HTTP 状态码映射
- Qdrant Common Errors - 错误处理参考
- Chroma Troubleshooting - 异常类设计参考
三、架构设计
3.0 接口定位
OpenViking 提供三种接口,面向不同使用场景:
| 接口 | 使用者 | 场景 | 特点 |
|---|---|---|---|
| Python SDK | Agent 开发者 | Agent 代码中调用 | 对象化 API,支持异步,client 实例维护状态 |
| Bash CLI | Agent / 运维人员 | Agent subprocess 调用、运维脚本 | 非交互式,通过环境变量管理状态 |
| HTTP API | 任意语言客户端 | 跨语言集成、微服务调用 | RESTful,无状态,每次请求带身份信息 |
选型建议:
- Agent 开发:优先使用 Python SDK,可以维护 client 实例状态
- Agent 调用外部工具:使用 Bash CLI,通过环境变量传递身份信息
- 非 Python 环境:使用 HTTP API
3.1 分层架构
┌─────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ openviking <command> [options] │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Client Layer │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ LocalClient │ │ HTTPClient │ │
│ │ (直接调用Service)│ │ (HTTP调用Server)│ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Service Layer │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ OpenVikingService ││
│ │ - ResourceService (资源解析、向量化) ││
│ │ - SearchService (语义搜索) ││
│ │ - SessionService (会话管理) ││
│ │ - FSService (文件系统操作) ││
│ │ - DebugService (调试服务) ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ VikingFS │ │ VikingDB │ │ AGFS │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
3.2 部署模式
选型决策
为什么支持多种部署模式:
- 嵌入式模式:降低入门门槛,开发者无需部署服务即可使用
- 独立服务模式:支持团队共享、多 Agent 并发访问
- 混合模式:灵活组合,适应不同基础设施环境
参考业界实践:
- Chroma:in-memory / persistent / client-server 三种模式
- Milvus:lite / standalone / distributed 三种模式
- SQLite:嵌入式 vs PostgreSQL/MySQL 独立服务
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 嵌入式 | SDK 自动启动本地 AGFS + VectorDB | 本地开发、单机部署 |
| 独立服务 | 通过 CLI 启动 Server,SDK/CLI 通过 HTTP 连接 | 团队共享、生产环境 |
| 混合模式 | 本地 Server + 远程 AGFS/VectorDB | 分布式部署 |
嵌入式模式:
┌─────────────────────────────────────┐
│ Python Process │
│ ┌─────────┐ ┌─────────┐ │
│ │ SDK │──│ Service │ │
│ └─────────┘ └────┬────┘ │
│ │ │
│ ┌─────────┐ ┌────┴────┐ │
│ │ AGFS │ │ VectorDB│ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────────┘
独立服务模式:
┌─────────────┐ ┌─────────────────────────────────────┐
│ CLI / SDK │─HTTP─│ OpenViking Server │
│ (Client) │ │ ┌─────────┐ ┌─────────┐ │
└─────────────┘ │ │ Service │──│ AGFS │ │
│ └────┬────┘ └─────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ VectorDB│ │
│ └─────────┘ │
└─────────────────────────────────────┘
3.3 身份与隔离
支持多租户场景,通过 user 和 agent 实现数据隔离:
| 参数 | 说明 | 作用域 |
|---|---|---|
user |
用户标识 | viking://user/{user}/memories/ |
agent |
Agent 标识 | viking://agent/{agent}/memories/ |
3.4 配置管理
Server 配置 (~/.openviking/server.yaml)
storage:
path: ~/.openviking/data
server:
host: 0.0.0.0
port: 8000
api_key: your-api-key
embedding:
provider: openai
model: text-embedding-3-small
api_key: ${OPENAI_API_KEY}
vlm:
provider: openai
model: gpt-4o
api_key: ${OPENAI_API_KEY}
Client 配置 (~/.openviking/client.yaml)
url: http://localhost:8000
api_key: your-api-key
# 注意:user 和 agent 通过环境变量管理,不存配置文件
环境变量
| 环境变量 | 说明 | 示例 |
|---|---|---|
OPENVIKING_URL |
Server URL | http://localhost:8000 |
OPENVIKING_API_KEY |
API Key | sk-xxx |
OPENVIKING_USER |
用户标识 | alice |
OPENVIKING_AGENT |
Agent 标识 | agent-a1 |
OPENVIKING_PATH |
本地数据路径(嵌入式模式) | ./data |
为什么 user/agent 用环境变量而不是配置文件:
- 进程隔离:环境变量是进程级的,多个 Agent 进程互不干扰
- 无并发冲突:不会出现多个进程同时写配置文件的问题
- 易于设置:Agent 启动时设置一次,后续调用自动继承
配置优先级
从高到低:
- 命令行参数(
--url,--user等) - 环境变量(
OPENVIKING_URL,OPENVIKING_USER等) - 配置文件(
~/.openviking/client.yaml)
四、模块设计
4.1 模块职责
| 模块 | 职责 | 依赖 |
|---|---|---|
| service/ | 核心业务逻辑,与传输层无关 | Infrastructure Layer |
| server/ | HTTP API 服务,调用 Service 层 | service/ |
| cli/ | 命令行工具,调用 Client 层 | client/ |
| client/ | 客户端抽象,支持本地和 HTTP 两种模式 | service/ 或 HTTP |
设计原则:
- Service 层独立:业务逻辑不依赖传输层(HTTP/CLI),便于测试和复用
- Client 层抽象:LocalClient 和 HTTPClient 实现相同接口,上层无感知
- CLI 薄层:CLI 只负责参数解析和输出格式化,业务逻辑在 Service 层
4.2 目录结构
openviking/
├── service/ # Service 层(业务逻辑)
│ ├── __init__.py
│ ├── core.py # OpenVikingService
│ ├── fs_service.py # 文件系统操作
│ ├── resource_service.py # 资源导入、技能添加
│ ├── search_service.py # 语义搜索
│ ├── session_service.py # 会话管理
│ ├── relation_service.py # 关联管理
│ ├── pack_service.py # 导入导出
│ └── debug_service.py # 调试服务
│
├── server/ # HTTP Server
│ ├── __init__.py
│ ├── app.py # FastAPI 应用
│ ├── bootstrap.py # 服务启动器
│ ├── config.py # 服务器配置
│ ├── dependencies.py # 依赖注入
│ └── routers/
│ ├── resources.py # /api/v1/resources
│ ├── filesystem.py # /api/v1/fs
│ ├── content.py # /api/v1/content
│ ├── search.py # /api/v1/search
│ ├── relations.py # /api/v1/relations
│ ├── sessions.py # /api/v1/sessions
│ └── debug.py # /api/v1/debug
│
├── cli/ # CLI 模块
│ ├── __init__.py
│ ├── main.py # CLI 入口
│ └── output.py # 输出格式化
│
├── client/ # Client 层
│ ├── __init__.py
│ ├── local.py # LocalClient
│ └── http.py # HTTPClient
│
├── async_client.py # 兼容层
└── sync_client.py # 兼容层
4.3 pyproject.toml
[project.scripts]
openviking = "openviking.cli.main:app"
4.4 新增依赖
dependencies = [
# ... 现有依赖
"typer>=0.9.0", # CLI 框架
"rich>=13.0.0", # CLI 美化输出
]
五、接口设计
5.1 核心方法定义
| 方法 | 参数 | 说明 |
|---|---|---|
add_resource |
path, target, wait, timeout |
导入资源 |
add_skill |
data, wait |
添加技能 |
ls |
uri, simple, recursive |
列出目录 |
tree |
uri |
目录树 |
stat |
uri |
状态信息 |
mkdir |
uri |
创建目录 |
rm |
uri, recursive |
删除 |
mv |
from_uri, to_uri |
移动 |
read |
uri |
读取完整内容 (L2) |
abstract |
uri |
读取摘要 (L0) |
overview |
uri |
读取概览 (L1) |
find |
query, target_uri, limit, threshold |
语义搜索 |
search |
query, target_uri, session, limit |
带上下文搜索 |
grep |
uri, pattern, case_insensitive |
内容搜索 |
glob |
pattern, uri |
模式匹配 |
link |
from_uri, uris, reason |
创建关联 |
unlink |
from_uri, uri |
删除关联 |
relations |
uri |
查看关联 |
export_ovpack |
uri, to |
导出 |
import_ovpack |
file, target, force, vectorize |
导入 |
session |
user |
创建/获取 Session 对象 |
sessions |
- | 获取所有 Session 对象列表 |
wait_processed |
timeout |
等待处理完成 |
get_status |
- | 获取系统状态(包含 queue/vikingdb/vlm 组件状态) |
is_healthy |
- | 快速健康检查 |
Session 对象方法:
| 方法 | 参数 | 说明 |
|---|---|---|
add_message |
role, content |
添加消息 |
compress |
- | 压缩会话 |
extract |
- | 提取记忆 |
delete |
- | 删除会话 |
5.2 统一返回值格式
参考 Qdrant 的响应格式设计,采用 {status, time, result} 统一包装:
成功响应:
{
"status": "ok",
"result": {...},
"time": 0.123,
"usage": { // 可选,用于监控
"tokens": 100,
"vectors_scanned": 1000
}
}
错误响应:
{
"status": "error",
"error": {
"code": "NOT_FOUND", // gRPC 风格错误码
"message": "Resource not found: viking://...",
"details": {...} // 可选,额外错误信息
},
"time": 0.05
}
Python 数据模型:
@dataclass
class Response:
status: str # "ok" | "error"
result: Optional[Any] = None
error: Optional[ErrorInfo] = None
time: float = 0.0
usage: Optional[UsageInfo] = None
@dataclass
class ErrorInfo:
code: str # NOT_FOUND, INVALID_URI, ...
message: str
details: Optional[dict] = None
@dataclass
class UsageInfo:
tokens: Optional[int] = None
vectors_scanned: Optional[int] = None
5.3 各方法返回值
ls / tree
{
"status": "ok",
"result": {
"entries": [
{"name": "doc.md", "type": "file", "uri": "viking://resources/doc.md", "size": 1024},
{"name": "images/", "type": "dir", "uri": "viking://resources/images/", "children_count": 5}
]
},
"time": 0.05
}
find / search
{
"status": "ok",
"result": {
"items": [
{
"uri": "viking://resources/doc.md",
"score": 0.92,
"content": "...",
"abstract": "...",
"metadata": {"type": "markdown", "scope": "resources"}
}
],
"query": "how to use openviking",
"total": 10
},
"time": 0.15,
"usage": {"vectors_scanned": 1000}
}
read / abstract / overview
{
"status": "ok",
"result": {
"uri": "viking://resources/doc.md",
"content": "...",
"level": "L2"
},
"time": 0.02
}
stat
{
"status": "ok",
"result": {
"uri": "viking://resources/doc.md",
"type": "file",
"size": 1024,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z",
"has_abstract": true,
"has_overview": true,
"vectorized": true
},
"time": 0.01
}
add_resource
{
"status": "ok",
"result": {
"uri": "viking://resources/my-docs/",
"files_count": 10,
"processing_status": "processing",
"queue": {
"vectorize": {"processed": 5, "total": 10, "failed": 0}
}
},
"time": 0.5
}
session(Python CLI 返回 Session 对象,HTTP API 返回 JSON)
# Python CLI
session = client.session(user="alice")
session.id # "session-abc123"
session.user # "alice"
session.created_at # datetime
session.message_count # 10
session.compressed # False
# HTTP API 返回
{
"status": "ok",
"result": {
"session_id": "session-abc123",
"user": "alice",
"created_at": "2024-01-01T00:00:00Z",
"message_count": 10,
"compressed": false
},
"time": 0.02
}
错误示例
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "Resource not found: viking://resources/nonexistent/"
},
"time": 0.01
}
{
"status": "error",
"error": {
"code": "INVALID_URI",
"message": "Invalid URI format: 'invalid-uri'",
"details": {
"expected": "viking://<scope>/<path>",
"received": "invalid-uri"
}
},
"time": 0.001
}
debug/status
{
"status": "ok",
"result": {
"is_healthy": true,
"components": {
"queue": {"name": "queue", "is_healthy": true, "has_errors": false, "details": {...}},
"vikingdb": {"name": "vikingdb", "is_healthy": true, "has_errors": false, "details": {...}},
"vlm": {"name": "vlm", "is_healthy": true, "has_errors": false, "details": {...}}
},
"errors": []
},
"time": 0.05
}
debug/health
{
"healthy": true
}
5.4 错误处理
基于 gRPC 标准状态码,结合 OpenViking 业务场景定制。
通用错误码(基于 gRPC 标准)
| 错误码 | HTTP 状态 | 说明 | 适用场景 |
|---|---|---|---|
OK |
200 | 成功 | 所有成功操作 |
INVALID_ARGUMENT |
400 | 参数无效 | URI 格式错误、参数缺失、类型错误 |
NOT_FOUND |
404 | 资源不存在 | URI 指向的资源不存在 |
ALREADY_EXISTS |
409 | 资源已存在 | 创建重复资源、重复导入 |
PERMISSION_DENIED |
403 | 权限不足 | API Key 无权限、用户隔离 |
UNAUTHENTICATED |
401 | 未认证 | API Key 缺失或无效 |
RESOURCE_EXHAUSTED |
429 | 资源耗尽 | 配额超限、速率限制 |
FAILED_PRECONDITION |
412 | 前置条件失败 | 资源未就绪、依赖缺失 |
ABORTED |
409 | 操作中止 | 并发冲突、事务失败 |
DEADLINE_EXCEEDED |
504 | 超时 | 处理超时、等待超时 |
UNAVAILABLE |
503 | 服务不可用 | AGFS/VectorDB 不可用 |
INTERNAL |
500 | 内部错误 | 未预期的服务端错误 |
UNIMPLEMENTED |
501 | 未实现 | 功能未实现 |
OpenViking 特定错误码
| 错误码 | HTTP 状态 | 说明 | 适用场景 |
|---|---|---|---|
INVALID_URI |
400 | URI 格式无效 | viking:// 协议解析失败 |
PROCESSING |
202 | 处理中 | 资源正在处理,尚未就绪 |
EMBEDDING_FAILED |
500 | 向量化失败 | Embedding API 调用失败 |
VLM_FAILED |
500 | VLM 调用失败 | 摘要/概览生成失败 |
SESSION_EXPIRED |
410 | 会话过期 | 会话已被删除或过期 |
Python 异常类
参考 Chroma 的特定异常类设计,异常类与错误码一一对应:
# openviking/exceptions.py
class OpenVikingError(Exception):
"""Base exception for all OpenViking errors."""
def __init__(self, code: str, message: str, details: dict = None):
self.code = code
self.message = message
self.details = details or {}
super().__init__(f"[{code}] {message}")
# 参数错误
class InvalidArgumentError(OpenVikingError): pass
class InvalidURIError(InvalidArgumentError): pass
# 资源错误
class NotFoundError(OpenVikingError): pass
class AlreadyExistsError(OpenVikingError): pass
# 认证/权限错误
class UnauthenticatedError(OpenVikingError): pass
class PermissionDeniedError(OpenVikingError): pass
# 服务错误
class UnavailableError(OpenVikingError): pass
class InternalError(OpenVikingError): pass
class DeadlineExceededError(OpenVikingError): pass
# 业务错误
class ProcessingError(OpenVikingError): pass
class EmbeddingFailedError(OpenVikingError): pass
class SessionExpiredError(OpenVikingError): pass
错误码到异常的映射:
ERROR_CODE_TO_EXCEPTION = {
"INVALID_ARGUMENT": InvalidArgumentError,
"INVALID_URI": InvalidURIError,
"NOT_FOUND": NotFoundError,
"ALREADY_EXISTS": AlreadyExistsError,
"UNAUTHENTICATED": UnauthenticatedError,
"PERMISSION_DENIED": PermissionDeniedError,
"UNAVAILABLE": UnavailableError,
"INTERNAL": InternalError,
"DEADLINE_EXCEEDED": DeadlineExceededError,
"PROCESSING": ProcessingError,
"EMBEDDING_FAILED": EmbeddingFailedError,
"SESSION_EXPIRED": SessionExpiredError,
}
def raise_for_error(response: dict):
"""Convert error response to exception."""
if response.get("status") == "error":
error = response["error"]
exc_class = ERROR_CODE_TO_EXCEPTION.get(error["code"], OpenVikingError)
raise exc_class(error["code"], error["message"], error.get("details", {}))
六、CLI 实现
6.1 Bash CLI
模式选型:非交互式 CLI
选型决策:采用非交互式 CLI。
为什么选择非交互式:
- Agent 调用场景:Agent 通过 subprocess 调用 CLI,非交互式更自然
- 无状态设计:每次调用独立,避免状态污染和并发冲突
- 易于集成:可以直接在 shell 脚本、CI/CD 中使用
- 参考业界实践:kubectl、aws cli、gh cli 都是非交互式设计
多 Agent 场景
OpenViking 支持多 user、多 agent,CLI 需要处理多进程并发场景:
┌─────────────────────────────────────────────────────────────┐
│ OpenViking Server │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ user:alice│ │ user:bob │ │ user:carol│ │
│ │ agent:a1 │ │ agent:b1 │ │ agent:c1 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ Agent A │ │ Agent B │ │ Agent C │
│ (进程1) │ │ (进程2) │ │ (进程3) │
└─────────┘ └─────────┘ └─────────┘
多个 Agent 进程可能同时运行,各自有不同的 user/agent 身份。因此 user/agent 通过环境变量管理(详见 3.4 配置管理)。
使用示例
Agent 调用场景:
# Agent 启动时设置环境变量
export OPENVIKING_URL=http://localhost:8000
export OPENVIKING_API_KEY=sk-xxx
export OPENVIKING_USER=alice
export OPENVIKING_AGENT=agent-a1
# 后续 CLI 调用自动使用环境变量,无需重复指定
openviking find "how to use openviking"
openviking ls viking://resources/
openviking read viking://resources/doc.md
临时覆盖:
# 命令行参数覆盖环境变量
openviking --user bob find "query"
设计原则
- 与接口一致:命令名、参数与接口定义保持一致
- 体现核心特性:突出文件系统范式、分层上下文、语义搜索、会话管理
- 简洁直观:常用操作简单,复杂操作通过子命令组织
命令列表
# 服务管理
openviking serve --path <dir> [--port 8000] [--host 0.0.0.0]
openviking serve --port 8000 --agfs-url <url> --vectordb-url <url>
openviking status
# 资源导入
openviking add-resource <path> [--to <uri>] [--wait] [--timeout N]
openviking add-skill <file> [--wait]
# 文件系统操作
openviking ls <uri> [--simple] [--recursive]
openviking tree <uri>
openviking stat <uri>
openviking mkdir <uri>
openviking rm <uri> [--recursive]
openviking mv <from> <to>
# 内容读取
openviking read <uri>
openviking abstract <uri>
openviking overview <uri>
# 搜索
openviking find <query> [<uri>] [--limit N] [--threshold F]
openviking search <query> [<uri>] [--session-id ID] [--limit N]
openviking grep <uri> <pattern> [-i]
openviking glob <pattern> [<uri>]
# 关联管理
openviking link <from> <to>... [--reason TEXT]
openviking unlink <from> <to>
openviking relations <uri>
# 会话管理
openviking session new [--user <name>]
openviking session list
openviking session get <id>
openviking session compress <id>
openviking session extract <id>
# 导入导出
openviking export <uri> <file.ovpack>
openviking import <file.ovpack> <uri> [--force] [--no-vectorize]
# 工具命令
openviking wait [--timeout N]
openviking config show
openviking config init
# 调试命令
openviking status # 系统整体状态(包含 queue/vikingdb/vlm 组件状态)
openviking health # 快速健康检查
命令分组说明
| 命令组 | 体现特性 | 说明 |
|---|---|---|
add-resource/add-skill |
自动处理 | 导入后自动解析、分层、向量化 |
ls/tree/stat/mkdir/rm/mv |
文件系统范式 | 像操作文件一样管理上下文 |
read/abstract/overview |
分层上下文 | L0/L1/L2 按需加载 |
find/search |
目录递归检索 | 语义搜索 + 目录定位 |
session * |
会话管理 | 自动压缩、记忆提取 |
status/health |
调试诊断 | 系统状态查询、健康检查 |
实现 (使用 Typer)
# openviking/cli/main.py
import typer
from rich.console import Console
app = typer.Typer(name="openviking", help="OpenViking - Context Database for AI Agents")
console = Console()
# 顶层命令
@app.command()
def serve(port: int = 8000, host: str = "0.0.0.0"):
"""Start OpenViking server."""
...
@app.command()
def ls(uri: str, simple: bool = False, recursive: bool = False):
"""List directory contents."""
...
@app.command()
def find(query: str, uri: str = "viking://", limit: int = 10):
"""Semantic search."""
...
# 子命令组
session_app = typer.Typer(help="Session management")
app.add_typer(session_app, name="session")
@session_app.command("new")
def session_new(user: str = None):
"""Create new session."""
...
6.2 Python SDK
使用方式
from openviking import OpenViking
# 嵌入式模式
client = OpenViking(path="./data")
# HTTP 模式
client = OpenViking(url="http://localhost:8000", api_key="xxx")
完整示例
from openviking import OpenViking
# 连接
client = OpenViking(url="http://localhost:8000", api_key="xxx")
# 导入资源
result = client.add_resource("./docs/", target="viking://resources/my-docs/", wait=True)
print(f"导入完成: {result.data.uri}, 文件数: {result.data.files_count}")
# 分层读取
abstract = client.abstract("viking://resources/my-docs/") # L0
overview = client.overview("viking://resources/my-docs/") # L1
content = client.read("viking://resources/my-docs/README.md") # L2
# 语义搜索
results = client.find("how to configure", target_uri="viking://resources/my-docs/", limit=10)
for r in results.data.results:
print(f"{r.uri}: {r.score:.2f}")
# 会话管理(OOP 模式)
session = client.session(user="alice")
session.add_message("user", "What is OpenViking?")
results = client.search("What is OpenViking?", session=session)
# Session 操作
session.compress()
memories = session.extract()
# 获取所有 session
all_sessions = client.sessions()
实现方式
class Session:
"""Session 对象,封装会话操作"""
def __init__(self, client, session_id: str, user: str):
self._client = client
self.id = session_id
self.user = user
def add_message(self, role: str, content: str):
"""添加消息"""
...
def compress(self):
"""压缩会话"""
...
def extract(self):
"""提取记忆"""
...
class OpenViking:
def __init__(
self,
path: Optional[str] = None,
url: Optional[str] = None,
api_key: Optional[str] = None,
user: Optional[str] = None,
agent: Optional[str] = None,
):
if url:
self._backend = HTTPBackend(url, api_key)
else:
self._backend = LocalBackend(path)
self._user = user
self._agent_id = agent
def session(self, user: str = None) -> Session:
"""创建或获取 Session 对象"""
...
def sessions(self) -> List[Session]:
"""获取所有 Session 对象"""
...
6.3 使用指南
安装
pip install openviking
环境变量
详见 3.4 配置管理中的环境变量定义。
viking:// 目录结构
viking://
├── resources/ # 外部资源(文档、代码等)
├── user/
│ └── memories/ # 用户记忆
├── agent/
│ ├── memories/ # Agent 记忆
│ ├── skills/ # Agent 技能
│ └── instructions/ # Agent 指令
└── session/ # 会话数据
分层上下文
| 层级 | 方法 | 说明 | 适用场景 |
|---|---|---|---|
| L0 | abstract() |
一句话摘要 | 快速浏览、列表展示 |
| L1 | overview() |
核心信息 | 初步了解、决策参考 |
| L2 | read() |
完整内容 | 详细阅读、代码执行 |
七、HTTP API 实现
7.1 设计原则
选型决策:
RESTful 风格:资源层级 URL,标准 HTTP 方法
- 参考 Qdrant API 设计:
/collections/{name}/points - 便于理解和使用,符合开发者习惯
- 参考 Qdrant API 设计:
POST 用于复杂查询:搜索等操作使用 POST,请求体包含查询参数
- 原因:GET 的 URL 长度有限制,复杂查询参数(如向量、过滤条件)不适合放 URL
- 参考:Qdrant、Elasticsearch 都采用 POST 进行搜索
统一响应格式:所有 API 返回
{status, time, result}结构- 参考 Qdrant 响应格式
- 便于客户端统一处理
为什么不用 GraphQL:
- OpenViking 的查询模式相对固定,不需要 GraphQL 的灵活性
- RESTful 更简单,学习成本低
- 便于缓存和调试
7.2 认证机制
采用 API Key 认证:
X-API-Key: your-api-key
# 或
Authorization: Bearer your-api-key
7.3 API 端点设计
所有 API 响应格式遵循 5.2 统一返回值格式,具体返回值结构见 5.3 各方法返回值。
资源管理 /api/v1/resources
POST /api/v1/resources # add_resource
Body: {"path": "...", "target": "...", "wait": false}
POST /api/v1/skills # add_skill
Body: {"data": {...}}
文件系统 /api/v1/fs
GET /api/v1/fs/ls?uri=viking:// # ls
GET /api/v1/fs/tree?uri=viking:// # tree
GET /api/v1/fs/stat?uri=viking://... # stat
POST /api/v1/fs/mkdir # mkdir
Body: {"uri": "viking://..."}
DELETE /api/v1/fs?uri=viking://...&recursive=false # rm
POST /api/v1/fs/mv # mv
Body: {"from": "...", "to": "..."}
内容读取 /api/v1/content
GET /api/v1/content/read?uri=viking://... # read (L2)
GET /api/v1/content/abstract?uri=viking://... # abstract (L0)
GET /api/v1/content/overview?uri=viking://... # overview (L1)
搜索 /api/v1/search
POST /api/v1/search/find # find
Body: {"query": "...", "uri": "viking://", "limit": 10}
POST /api/v1/search/search # search (with session)
Body: {"query": "...", "uri": "...", "session_id": "..."}
POST /api/v1/search/grep # grep
Body: {"uri": "...", "pattern": "...", "case_insensitive": false}
POST /api/v1/search/glob # glob
Body: {"pattern": "**/*.md", "uri": "viking://"}
关联 /api/v1/relations
POST /api/v1/relations/link # link
Body: {"from": "...", "to": "...", "reason": "..."}
DELETE /api/v1/relations/link # unlink
Body: {"from": "...", "to": "..."}
GET /api/v1/relations?uri=viking://... # relations
会话 /api/v1/sessions
POST /api/v1/sessions # session new
Body: {"user": "alice"}
GET /api/v1/sessions # session list
GET /api/v1/sessions/{id} # session get
DELETE /api/v1/sessions/{id} # session delete
POST /api/v1/sessions/{id}/compress # session compress
POST /api/v1/sessions/{id}/extract # session extract
POST /api/v1/sessions/{id}/messages # add message
Body: {"role": "user", "content": "..."}
导入导出 /api/v1/pack
POST /api/v1/pack/export # export
Body: {"uri": "...", "file": "..."}
POST /api/v1/pack/import # import
Body: {"file": "...", "uri": "...", "force": false}
系统 /api/v1/system
GET /health # 健康检查
GET /api/v1/system/status # 系统状态
POST /api/v1/system/wait # wait_processed
Body: {"timeout": 60}
调试 /api/v1/debug
GET /api/v1/debug/status # 系统整体状态(包含 queue/vikingdb/vlm 组件状态)
GET /api/v1/debug/health # 快速健康检查(返回 bool)
7.4 Server 实现
# openviking/server/app.py
from fastapi import FastAPI, Depends
from openviking.server.auth import verify_api_key
from openviking.server.routers import resources, filesystem, content, search, sessions, debug
app = FastAPI(
title="OpenViking API",
description="Context Database for AI Agents",
version="1.0.0"
)
# 注册路由
app.include_router(resources.router, prefix="/api/v1/resources", dependencies=[Depends(verify_api_key)])
app.include_router(filesystem.router, prefix="/api/v1/fs", dependencies=[Depends(verify_api_key)])
app.include_router(content.router, prefix="/api/v1/content", dependencies=[Depends(verify_api_key)])
app.include_router(search.router, prefix="/api/v1/search", dependencies=[Depends(verify_api_key)])
app.include_router(sessions.router, prefix="/api/v1/sessions", dependencies=[Depends(verify_api_key)])
app.include_router(debug.router, prefix="/api/v1/debug", dependencies=[Depends(verify_api_key)])
@app.get("/health")
async def health():
return {"status": "healthy"}
八、实现任务拆解
8.1 任务概览
| 任务 | 描述 | 依赖 | 优先级 | 适合社区开发者 |
|---|---|---|---|---|
| T1 | Service 层抽取 | - | P0 | |
| T2 | HTTP Server | T1 | P1 | |
| T3 | CLI 基础框架 | T1 | P1 | |
| T4 | Python SDK | T2 | P2 | |
| T5 | CLI 完整命令 | T3 | P2 | |
| T6 | 集成测试 | T4, T5 | P3 | |
| T7 | 文档更新 | T6 | P3 | |
| T8 | Docker 部署 | T2 | P1 | ✅ |
| T9 | MCP Server | T1 | P1 | ✅ |
| T10 | TypeScript SDK | T2 | P2 | ✅ |
| T11 | Golang SDK | T2 | P2 | ✅ |
8.2 依赖关系
T1 (Service层)
├── T2 (Server) ──┬── T4 (Python SDK) ──┐
│ ├── T8 (Docker) │
│ ├── T10 (TS SDK) ├── T6 (测试) ── T7 (文档)
│ └── T11 (Go SDK) │
│ │
├── T3 (CLI基础) ── T5 (CLI完整) ────────┘
│
└── T9 (MCP Server)
8.3 任务详情
T1: Service 层抽取(P0)
目标:从 async_client.py 抽取业务逻辑到独立的 Service 层
交付物:
openviking/service/core.py- OpenVikingServiceopenviking/service/resource_service.py- 资源导入、技能添加openviking/service/search_service.py- 语义搜索openviking/service/session_service.py- 会话管理openviking/service/fs_service.py- 文件系统操作
验收标准:
- 现有单元测试全部通过
async_client.py作为兼容层,API 不变
T2: HTTP Server(P1)
目标:实现 FastAPI HTTP Server
交付物:
openviking/server/app.py- FastAPI 应用openviking/server/bootstrap.py- 服务启动器openviking/server/auth.py- API Key 认证openviking/server/routers/*.py- 所有 API 路由openviking/server/models.py- 统一响应模型
验收标准:
# 启动服务
openviking serve --path ./data --port 8000
# 验证 API
curl http://localhost:8000/health
curl -X POST http://localhost:8000/api/v1/resources \
-H "X-API-Key: test" -d '{"path": "./docs"}'
curl http://localhost:8000/api/v1/fs/ls?uri=viking://
T3: CLI 基础框架(P1)
目标:实现 CLI 核心命令
交付物:
openviking/cli/main.py- CLI 入口openviking/cli/output.py- 输出格式化- 核心命令:
serve,ls,find,read,abstract,overview,add-resource
验收标准:
openviking serve --path ./data --port 8000
openviking add-resource ./docs/ --wait
openviking ls viking://resources/
openviking find "how to use"
T4: Python SDK(P2)
目标:支持通过 HTTP 连接远程 Server
交付物:
openviking/client/http.py- HTTPClientopenviking/client/local.py- LocalClient(重构)- 修改
async_client.py支持server_url参数
验收标准:
# HTTP 模式
client = OpenViking(server_url="http://localhost:8000", api_key="test")
client.initialize()
results = client.find("how to use")
T5: CLI 完整命令(P2)
目标:补全所有 CLI 命令
交付物:
- 会话命令:
session new/list/get/compress/extract - 文件系统命令:
tree,stat,mkdir,rm,mv - 搜索命令:
search,grep,glob - 关联命令:
link,unlink,relations - 导入导出:
export,import - 配置命令:
config show/init - 工具命令:
wait,status
验收标准:
openviking --help # 显示所有命令
openviking session new --user alice
openviking export viking://resources/docs/ ./backup.ovpack
T6: 集成测试(P3)
目标:端到端测试
交付物:
tests/integration/test_cli.pytests/integration/test_server.pytests/integration/test_http_client.py
验收标准:
- 所有集成测试通过
- 覆盖主要使用场景
T7: 文档更新(P3)
目标:更新用户文档
交付物:
- README.md 更新(CLI 和 Server 使用说明)
- OpenAPI 文档(自动生成)
- CHANGELOG.md 更新
T8: Docker 部署(P1)
目标:提供 Docker 镜像和 docker-compose 配置
交付物:
Dockerfile- 多阶段构建docker-compose.yml- 本地部署配置.dockerignore- 忽略文件- GitHub Actions 自动构建发布
验收标准:
docker build -t openviking .
docker run -p 8000:8000 -e OPENAI_API_KEY=xxx openviking
curl http://localhost:8000/health
T9: MCP Server(P1)
目标:实现 Model Context Protocol 服务端,让 Claude 等 AI 可直接调用
交付物:
openviking/mcp/server.py- MCP Server 主入口openviking/mcp/tools.py- Tool 定义(find, read, ls, abstract, overview)- CLI 命令:
openviking mcp [--path <dir>]
验收标准:
- Claude Desktop 可成功连接并调用 tools
- 支持 stdio 传输
T10: TypeScript SDK(P2)
目标:基于 HTTP API 实现 TypeScript/JavaScript 客户端
交付物:
openviking-js/- 独立 npm 包- 完整类型定义
- 错误处理
- README 和示例
验收标准:
import { OpenViking } from '@openviking/sdk';
const client = new OpenViking({ url: 'http://localhost:8000', apiKey: 'xxx' });
const results = await client.find('how to configure');
T11: Golang SDK(P2)
目标:基于 HTTP API 实现 Golang 客户端
交付物:
openviking-go/- 独立 Go 模块- 完整类型定义
- 错误处理
- README 和示例
验收标准:
client := openviking.NewClient(openviking.Config{URL: "http://localhost:8000", APIKey: "xxx"})
results, _ := client.Find("how to configure", nil)
8.4 关键文件
| 文件 | 操作 | 说明 |
|---|---|---|
pyproject.toml |
修改 | 添加依赖和入口点 |
openviking/exceptions.py |
新增 | 异常类定义 |
openviking/cli/main.py |
新增 | CLI 入口 |
openviking/server/app.py |
新增 | FastAPI 应用入口 |
openviking/server/auth.py |
新增 | API Key 认证 |
openviking/server/models.py |
新增 | 响应模型、错误码定义 |
openviking/server/routers/*.py |
新增 | API 路由 |
openviking/client/http.py |
新增 | HTTP 客户端 |
openviking/async_client.py |
修改 | 支持 HTTP 模式 |
8.5 验证方案
Bash CLI 验证
openviking config init
openviking serve --port 8000
openviking add-resource ./docs/ --wait
openviking ls viking://
openviking find "how to use"
HTTP API 验证
curl http://localhost:8000/health
curl -X POST http://localhost:8000/api/v1/resources \
-H "X-API-Key: xxx" -d '{"path": "./docs"}'
curl "http://localhost:8000/api/v1/fs/ls?uri=viking://" \
-H "X-API-Key: xxx"
Python CLI 验证
from openviking import OpenViking
client = OpenViking(url="http://localhost:8000", api_key="xxx")
result = client.find("how to use")
print(result)