技能
技能是智能体可以调用的能力定义。本模块提供技能的添加和管理功能。
核心概念
技能类型
OpenViking 支持多种技能定义格式:
- 结构化技能数据:包含 name、description、content 等字段的字典
- SKILL.md 文件:带有 YAML frontmatter 的 Markdown 文件
- MCP Tool 格式:自动检测并转换为 OpenViking 技能格式
技能存储结构
技能存储在当前用户的 skills 根。短 URI viking://user/skills/ 会按认证请求身份解析为 viking://user/{user_id}/skills/:
viking://user/{user_id}/skills/
+-- search-web/
| +-- .abstract.md # L0:简要描述
| +-- .overview.md # L1:参数和使用概览
| +-- SKILL.md # L2:完整文档
| +-- [auxiliary files] # 其他辅助文件
+-- calculator/
| +-- .abstract.md
| +-- .overview.md
| +-- SKILL.md
+-- ...SKILL.md 格式
技能可以使用带有 YAML frontmatter 的 SKILL.md 文件来定义:
---
name: skill-name
description: Brief description of the skill
allowed_tools:
- Tool1
- Tool2
tags:
- tag1
- tag2
---
# Skill Name
Full skill documentation in Markdown format.
## Parameters
- **param1** (type, required): Description
- **param2** (type, optional): Description
## Usage
When and how to use this skill.
## Examples
Concrete examples of skill invocation.必填字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| name | str | 技能名称(建议使用 kebab-case) |
| description | str | 简要描述 |
可选字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| allowed_tools | List[str] | 该技能可使用的工具 |
| tags | List[str] | 用于分类的标签 |
MCP 格式自动转换
OpenViking 会自动检测并将 MCP Tool 定义转换为技能格式。
检测规则:如果字典包含 inputSchema 字段,则被视为 MCP 格式。
转换过程:
- 名称转换为 kebab-case
- 描述保持不变
- 从
inputSchema.properties中提取参数 - 从
inputSchema.required中标记必填字段 - 生成 Markdown 内容
转换示例:
输入(MCP 格式):
{
"name": "search_web",
"description": "Search the web",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"limit": {
"type": "integer",
"description": "Max results"
}
},
"required": ["query"]
}
}输出(技能格式):
{
"name": "search-web",
"description": "Search the web",
"content": """---
name: search-web
description: Search the web
---
# search-web
Search the web
## Parameters
- **query** (string) (required): Search query
- **limit** (integer) (optional): Max results
## Usage
This tool wraps the MCP tool `search-web`. Call this when the user needs functionality matching the description above.
"""
}API 参考
add_skill
向知识库添加技能。
1. API 实现介绍
技能是一种特殊的资源,用于定义智能体可以执行的操作或工具。
处理流程:
- 接收技能数据或上传的临时文件
- 检测数据格式(结构化数据、SKILL.md 内容、MCP 格式)
- 解析技能定义
- 存储到当前用户的
viking://user/{user_id}/skills/路径下 - 如指定
wait=True,等待向量化完成
代码入口:
openviking/client/local.py:LocalClient.add_skill- SDK 入口(嵌入式)openviking_cli/client/http.py:AsyncHTTPClient.add_skill- SDK 入口(HTTP)openviking/server/routers/resources.py:add_skill- HTTP 路由openviking/service/resource_service.py:ResourceService.add_skill- 核心服务实现crates/ov_cli/src/handlers.rs:handle_add_skill- CLI 处理
2. 接口和参数说明
参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| data | Any | 否 | - | 内联技能内容或结构化数据。与 temp_file_id 二选一 |
| temp_file_id | string | 否 | - | 临时上传文件 ID(通过 temp_upload 获取)。与 data 二选一 |
| wait | bool | 否 | False | 是否等待技能处理完成 |
| timeout | float | 否 | None | 超时时间(秒),仅 wait=True 时生效 |
| telemetry | TelemetryRequest | 否 | False | 是否返回遥测数据 |
补充说明:
本地文件处理:
- Python SDK 和 CLI 可以直接接收本地
SKILL.md文件或目录。处于 HTTP 模式时,它们会先自动上传,再调用服务端 API。 - 裸 HTTP 调用有三种推荐方式:
- 在
data中直接传结构化 skill 数据 - 在
data中直接传原始SKILL.md内容 - 先调用
POST /api/v1/resources/temp_upload上传本地SKILL.md文件/zip 目录,再调用POST /api/v1/skills并传入temp_file_id temp_upload默认使用本地临时存储;只有在明确需要分布式共享临时上传时,才传upload_mode=shared。在 Python HTTP client / CLI 流程里,也可以通过ovcli.conf的upload.mode = "shared"驱动这一行为
- 在
POST /api/v1/skills不接受在data中直接传宿主机本地路径。
- Python SDK 和 CLI 可以直接接收本地
目标规则:
- Skills 始终是 user-scoped;
add_skill不接受to、parent或root_uri。 - 不支持 peer-scoped skill 根;actor peer 过滤只作用于 peer memories/resources,不作用于 peer skills。
- 列出、读取、删除或搜索技能时,可以使用
viking://user/skills/...作为当前用户短写。
- Skills 始终是 user-scoped;
支持的数据格式:
- 字典(技能格式):包含
name、description、content等字段 - 字典(MCP Tool 格式):包含
name、description、inputSchema字段,会自动检测并转换 - 字符串(SKILL.md 内容):完整的 SKILL.md 内容
- 路径(文件或目录):指向
SKILL.md文件的路径,或包含SKILL.md的目录路径(辅助文件会一并包含)
- 字典(技能格式):包含
3. 使用示例
HTTP API:
POST /api/v1/skills
Content-Type: application/json# 使用内联结构化数据
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": {
"name": "search-web",
"description": "Search the web for current information",
"content": "# search-web\n\nSearch the web for current information.\n\n## Parameters\n- **query** (string, required): Search query\n- **limit** (integer, optional): Max results, default 10"
},
"wait": true
}'
# 使用内联 SKILL.md 内容
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": "---\nname: my-skill\ndescription: My custom skill\n---\n\n# My Skill\n\nSkill content here."
}'
# 使用 MCP Tool 格式(自动检测并转换)
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": {
"name": "calculator",
"description": "Perform mathematical calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate"
}
},
"required": ["expression"]
}
}
}'
# 使用本地文件(需先使用 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=@./skills/my-skill.json" \
| jq -r '.result.temp_file_id'
)
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d "{
\"temp_file_id\": \"$TEMP_FILE_ID\"
}"Python SDK:
import openviking as ov
client = ov.SyncHTTPClient(url="http://localhost:1933", api_key="your-key")
client.initialize()
# 方式 1:使用结构化技能数据
skill = {
"name": "search-web",
"description": "Search the web for current information",
"content": """# search-web
Search the web for current information.
## Parameters
- **query** (string, required): Search query
- **limit** (integer, optional): Max results, default 10
"""
}
result = client.add_skill(skill)
print(f"Added: {result['root_uri']}")
# 方式 2:使用 MCP Tool 格式(自动检测并转换)
mcp_tool = {
"name": "calculator",
"description": "Perform mathematical calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate"
}
},
"required": ["expression"]
}
}
result = client.add_skill(mcp_tool)
print(f"Added: {result['uri']}")
# 方式 3:从本地 SKILL.md 文件添加
result = client.add_skill("./skills/search-web/SKILL.md")
print(f"Added: {result['uri']}")
# 方式 4:从包含 SKILL.md 的目录添加(辅助文件会一并包含)
result = client.add_skill("./skills/code-runner/")
print(f"Added: {result['uri']}")
print(f"Auxiliary files: {result['auxiliary_files']}")
# 等待处理完成
result = client.add_skill("./skills/my-skill/", wait=True)
client.wait_processed()Go SDK
result, err := client.AddSkill(ctx, "./skills/my-skill/", &openviking.AddSkillOptions{
Wait: true,
})
if err != nil {
return err
}
fmt.Println(result["uri"])CLI:
# 添加技能(从文件或目录)
ov add-skill ./skills/my-skill.json
ov add-skill ./skills/search-web/SKILL.md
ov add-skill ./skills/code-runner/
# 等待处理完成
ov add-skill ./skills/my-skill/ --wait
# 使用 JSON 输出格式
ov add-skill ./skills/my-skill/ -o json响应示例:
HTTP API 响应 (JSON):
{
"status": "ok",
"result": {
"status": "success",
"root_uri": "viking://user/alice/skills/my-skill",
"uri": "viking://user/alice/skills/my-skill",
"name": "my-skill",
"auxiliary_files": 2,
"queue_status": {
"pending": 0,
"processing": 0,
"completed": 1
}
},
"telemetry": {
"operation_id": "550e8400-e29b-41d4-a716-446655440000"
},
"time": 0.1
}CLI 响应(默认表格格式):
Note: Skill is being processed in the background.
Use 'ov wait' to wait for completion, or 'ov observer queue' to check status.
status success
root_uri viking://user/alice/skills/my-skill
uri viking://user/alice/skills/my-skill
name my-skill
auxiliary_files 2CLI 响应(JSON 格式,使用 -o json):
{
"status": "success",
"root_uri": "viking://user/alice/skills/my-skill",
"uri": "viking://user/alice/skills/my-skill",
"name": "my-skill",
"auxiliary_files": 2
}字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
status | string | 处理状态:success 成功,error 失败 |
root_uri | string | 技能在 OpenViking 中的 canonical 最终 URI(同 uri) |
uri | string | 技能在 OpenViking 中的 canonical 最终 URI(同 root_uri) |
name | string | 技能名称 |
auxiliary_files | number | 技能附带的辅助文件数量 |
queue_status | object | (可选,仅当 wait=True 时)队列处理状态,包含 pending、processing、completed 计数 |
4. 错误处理
同步处理错误:
如果 skill 解析或处理同步失败,裸 HTTP 会返回标准错误 envelope,并使用非 2xx HTTP 状态码:
{
"status": "error",
"error": {
"code": "PROCESSING_ERROR",
"message": "Skill parse error: invalid skill metadata"
}
}Python HTTP SDK 会把该响应映射为对应异常(ProcessingError)。
技能管理操作
Python HTTP SDK 和 Go SDK 都暴露专用技能管理方法。Python 方法包括 list_skills、find_skills、validate_skill、get_skill、update_skill 和 delete_skill;Go 方法包括 ListSkills、FindSkills、ValidateSkill、 GetSkill、UpdateSkill 和 DeleteSkill。通用文件系统、内容和检索方法仍可用于 URI 级访问。
列出技能
Python SDK:
skills = client.list_skills(node_limit=1000)
for skill in skills["skills"]:
print(skill["name"])Go SDK:
skills, err := client.ListSkills(ctx, nil)
_ = skillsHTTP API:
curl -X GET "http://localhost:1933/api/v1/skills?node_limit=1000" \
-H "X-API-Key: your-key"读取技能
Python SDK:
skill = client.get_skill("search-web", include_content=True, include_files=True)
print(skill["name"])
print(skill.get("content"))Go SDK:
skill, err := client.GetSkill(ctx, "search-web", &openviking.GetSkillOptions{
IncludeContent: openviking.Bool(true),
IncludeFiles: openviking.Bool(true),
})
_ = skillHTTP API:
curl -X GET "http://localhost:1933/api/v1/skills/search-web?include_content=true&include_files=true" \
-H "X-API-Key: your-key"搜索技能
Python SDK:
results = client.find_skills("search the internet", limit=5)
for skill in results["skills"]:
print(skill["name"], skill["score"])Go SDK:
results, err := client.FindSkills(ctx, "search the internet", &openviking.FindSkillsOptions{
Limit: 5,
})
_ = resultsHTTP API:
curl -X POST http://localhost:1933/api/v1/skills/find \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"query": "search the internet",
"limit": 5
}'校验和更新技能
Python SDK:
validated = client.validate_skill({"name": "search-web", "description": "..."})
updated = client.update_skill("search-web", "./skills/search-web", wait=True)Go SDK:
validated, err := client.ValidateSkill(ctx, map[string]any{
"name": "search-web",
"description": "...",
}, nil)
updated, err := client.UpdateSkill(ctx, "search-web", "./skills/search-web", &openviking.UpdateSkillOptions{
Wait: true,
})
_, _ = validated, updatedHTTP API:
curl -X POST http://localhost:1933/api/v1/skills/validate \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{"data": {"name": "search-web", "description": "..."}}'删除技能
Python SDK:
client.delete_skill("old-skill")Go SDK:
deleted, err := client.DeleteSkill(ctx, "old-skill")
_ = deletedHTTP API:
curl -X DELETE "http://localhost:1933/api/v1/skills/old-skill" \
-H "X-API-Key: your-key"最佳实践
清晰的描述
# 好 - 具体且可操作
skill = {
"name": "search-web",
"description": "Search the web for current information using Google",
...
}
# 不够好 - 过于模糊
skill = {
"name": "search",
"description": "Search",
...
}命名一致性建议
技能名称使用 kebab-case:
search-web(推荐)searchWeb(避免)search_web(避免)
