Skip to content

Commit fc2a834

Browse files
committed
FEAT: 添加skills_hub 模块
1 parent 73655ab commit fc2a834

32 files changed

Lines changed: 4235 additions & 0 deletions

docs/mkdocs/en/skill.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,6 +2143,62 @@ The **Dynamic Tool Selection** mechanism has been fully implemented and verified
21432143
- ❌ All tools need to be available simultaneously
21442144
- ❌ Token cost is not a primary concern
21452145

2146+
## Skill Hub — Discovering & Fetching Skills from Remote Sources
2147+
2148+
Everything above assumes a skill already exists on disk under a directory served by `BaseSkillRepository`. **Skill Hub** (`trpc_agent_sdk.skills.hub`) is a separate, standalone layer that answers a different question: *where do skills come from before they're on disk?* It gives you a uniform way to search, inspect, and download skills from public registries, so you (or a harness built on top of the SDK) can install them into a directory that `create_default_skill_repository` then serves as usual.
2149+
2150+
### The `SkillSource` contract
2151+
2152+
Every adapter implements the same four-method interface:
2153+
2154+
```python
2155+
from trpc_agent_sdk.skills.hub import SkillSource, SkillMeta, SkillBundle
2156+
2157+
class SkillSource(ABC):
2158+
def source_id(self) -> str: ...
2159+
def search(self, query: str, limit: int = 10) -> list[SkillMeta]: ...
2160+
def inspect(self, identifier: str) -> SkillMeta | None: ...
2161+
def fetch(self, identifier: str) -> SkillBundle | None: ...
2162+
```
2163+
2164+
- `SkillMeta` — lightweight search/inspect result (`name`, `description`, `source`, `identifier`, optional `repo`/`path`/`tags`/`extra`).
2165+
- `SkillBundle` — the downloaded skill (`name`, `files: dict[str, str | bytes]`, `source`, `identifier`, `metadata`).
2166+
2167+
**Important:** `fetch()` only returns a `SkillBundle` in memory. Writing it to disk (including overwrite policy, atomic staging, and concurrency safety) is intentionally left to the caller, since different harnesses need different install semantics. The SDK also exports three path validators for that purpose:
2168+
2169+
```python
2170+
from trpc_agent_sdk.skills.hub import validate_skill_name, validate_category_name, validate_bundle_rel_path
2171+
```
2172+
2173+
Use them to reject unsafe paths (absolute paths, `..` traversal, Windows drive prefixes) before writing any hub-returned file to disk. See [examples/skills_hub/agent/hub.py](../../../examples/skills_hub/agent/hub.py) for a minimal reference implementation.
2174+
2175+
### Built-in adapters
2176+
2177+
| Adapter | Source | Identifier format |
2178+
| --- | --- | --- |
2179+
| `GitHubSource` | GitHub repos, via the Contents / Git Trees API | `"owner/repo/path/to/skill-dir"` |
2180+
| `WellKnownSkillSource` | Any domain exposing `/.well-known/skills/index.json` | `well-known:{base_url}/{skill_name}` or a raw HTTPS URL |
2181+
| `HermesIndexSource` | A centralized, pre-crawled skills catalog | Same identifiers as the underlying `GitHubSource` entries |
2182+
| `SkillsShSource` | [skills.sh](https://skills.sh) | `skills-sh/{owner}/{repo}/{skill_path}` |
2183+
| `ClawHubSource` | [ClawHub](https://clawhub.ai) | slug, e.g. `"notion"` |
2184+
| `ClaudeMarketplaceSource` | Claude Code marketplace repos (`.claude-plugin/marketplace.json`) | Resolves to a `GitHubSource` identifier |
2185+
| `LobeHubSource` | LobeHub agent marketplace (converted to synthetic `SKILL.md`) | `lobehub/{agent_id}` |
2186+
2187+
`GitHubAuth` provides GitHub API authentication via an explicitly injected personal access token (60 req/hr unauthenticated, 5,000 req/hr with a token). No token auto-detection from the environment or a local `gh` CLI is performed, since the SDK may run multi-tenant.
2188+
2189+
### Minimal usage
2190+
2191+
```python
2192+
from trpc_agent_sdk.skills.hub import GitHubAuth, GitHubSource
2193+
2194+
source = GitHubSource(GitHubAuth()) # unauthenticated is fine for public repos
2195+
meta = source.inspect("anthropics/skills/skills/skill-creator")
2196+
bundle = source.fetch("anthropics/skills/skills/skill-creator")
2197+
# bundle.files: {"SKILL.md": "...", "scripts/...": "...", ...}
2198+
```
2199+
2200+
Combine `fetch()` with your own install helper and `create_default_skill_repository` to make a hub-fetched skill available to an agent exactly like a bundled one — see [examples/skills_hub](../../../examples/skills_hub/README.md) for the full pipeline (fetch → validate paths → write to disk → `create_default_skill_repository``SkillToolSet`).
2201+
21462202
## References and Examples
21472203

21482204
- Background:
@@ -2152,6 +2208,7 @@ The **Dynamic Tool Selection** mechanism has been fully implemented and verified
21522208
- This repository:
21532209
- Interactive demo: [examples/skills/run_agent.py](../../../examples/skills/run_agent.py)
21542210
- Dynamic tool selection full example: [examples/skills_with_dynamic_tools/run_agent.py](../../../examples/skills_with_dynamic_tools/run_agent.py)
2211+
- Skill Hub full example (fetch from GitHub, then run): [examples/skills_hub/run_agent.py](../../../examples/skills_hub/run_agent.py)
21552212
- Example structure guide: [examples/skills/README.md](../../../examples/skills/README.md)
21562213
- Example skills:
21572214
- [examples/skills/skills/python-math/SKILL.md](../../../examples/skills/skills/python-math/SKILL.md)

docs/mkdocs/zh/skill.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,6 +2141,58 @@ Tools:
21412141
- ❌ 所有工具都需要同时可用
21422142
- ❌ Token 成本不是主要考虑因素
21432143

2144+
## Skill Hub —— 从远程来源发现并获取 Skill
2145+
2146+
**Skill Hub**`trpc_agent_sdk.skills.hub`)是一组用于从远程来源发现和获取 skill 的适配器(`SkillSource`)。它能做三件事:搜索某个来源(GitHub、ClawHub、skills.sh 等)上有哪些 skill、查看某个 skill 的元信息、下载它的完整文件内容。用户也可以实现SkillSource 接口来对接自己的skill source.
2147+
2148+
### `SkillSource` 契约
2149+
2150+
每个适配器都实现同样的四方法接口:
2151+
2152+
```python
2153+
from trpc_agent_sdk.skills.hub import SkillSource, SkillMeta, SkillBundle
2154+
2155+
class SkillSource(ABC):
2156+
def source_id(self) -> str: ...
2157+
def search(self, query: str, limit: int = 10) -> list[SkillMeta]: ...
2158+
def inspect(self, identifier: str) -> SkillMeta | None: ...
2159+
def fetch(self, identifier: str) -> SkillBundle | None: ...
2160+
```
2161+
2162+
- `SkillMeta` —— 轻量的 search/inspect 结果(`name``description``source``identifier`,以及可选的 `repo`/`path`/`tags`/`extra`
2163+
- `SkillBundle` —— 下载到的 skill(`name``files: dict[str, str | bytes]``source``identifier``metadata`
2164+
2165+
`fetch()` 只会返回内存中的 `SkillBundle`。把它写到磁盘(包括覆盖策略、原子写入、并发安全)是**调用方**自己的职责,因为不同 harness 对安装语义的要求各不相同。为此 SDK 也导出了三个路径校验函数:
2166+
2167+
```python
2168+
from trpc_agent_sdk.skills.hub import validate_skill_name, validate_category_name, validate_bundle_rel_path
2169+
```
2170+
2171+
在把 hub 返回的文件写到磁盘之前,用它们拒绝不安全的路径(绝对路径、`..` 路径穿越、Windows 盘符前缀)。最小化的参考实现见 [examples/skills_hub/agent/hub.py](../../../examples/skills_hub/agent/hub.py)
2172+
2173+
### 内置适配器
2174+
2175+
| 适配器 | 来源 | 标识符格式 |
2176+
| --- | --- | --- |
2177+
| `GitHubSource` | GitHub 仓库,通过 Contents / Git Trees API | `"owner/repo/path/to/skill-dir"` |
2178+
| `WellKnownSkillSource` | 任何暴露 `/.well-known/skills/index.json` 的站点 | `well-known:{base_url}/{skill_name}` 或原始 HTTPS URL |
2179+
| `HermesIndexSource` | 集中式、预先爬取好的 skill 目录索引 | 与底层 `GitHubSource` 条目相同的标识符 |
2180+
| `SkillsShSource` | [skills.sh](https://skills.sh) | `skills-sh/{owner}/{repo}/{skill_path}` |
2181+
| `ClawHubSource` | [ClawHub](https://clawhub.ai) | slug,例如 `"notion"` |
2182+
| `ClaudeMarketplaceSource` | Claude Code marketplace 仓库(`.claude-plugin/marketplace.json`| 解析为 `GitHubSource` 标识符 |
2183+
| `LobeHubSource` | LobeHub agent marketplace(转换为合成的 `SKILL.md`| `lobehub/{agent_id}` |
2184+
2185+
### 最小用法
2186+
2187+
```python
2188+
from trpc_agent_sdk.skills.hub import GitHubAuth, GitHubSource
2189+
2190+
source = GitHubSource(GitHubAuth()) # 拉取公开仓库无需认证
2191+
meta = source.inspect("anthropics/skills/skills/skill-creator")
2192+
bundle = source.fetch("anthropics/skills/skills/skill-creator")
2193+
# bundle.files: {"SKILL.md": "...", "scripts/...": "...", ...}
2194+
```
2195+
21442196
## 参考和示例
21452197

21462198
- 背景:
@@ -2150,6 +2202,7 @@ Tools:
21502202
- 本仓库:
21512203
- 交互式演示:[examples/skills/run_agent.py](../../../examples/skills/run_agent.py)
21522204
- 动态工具选择完整示例:[examples/skills_with_dynamic_tools/run_agent.py](../../../examples/skills_with_dynamic_tools/run_agent.py)
2205+
- Skill Hub 完整示例(从 GitHub 拉取后运行):[examples/skills_hub/run_agent.py](../../../examples/skills_hub/run_agent.py)
21532206
- 示例结构说明:[examples/skills/README.md](../../../examples/skills/README.md)
21542207
- 示例技能:
21552208
- [examples/skills/skills/python-math/SKILL.md](../../../examples/skills/skills/python-math/SKILL.md)

examples/skills_hub/.env

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Set TRPC_AGENT_API_KEY、TRPC_AGENT_BASE_URL、TRPC_AGENT_MODEL_NAME
2+
TRPC_AGENT_API_KEY=your-api-key
3+
TRPC_AGENT_BASE_URL=your-base-url
4+
TRPC_AGENT_MODEL_NAME=your-model-name
5+
6+
# Optional: a GitHub personal access token (raises the unauthenticated
7+
# 60 req/hr GitHub API limit to 5,000/hr). Not required to run this demo.
8+
# GITHUB_TOKEN=

examples/skills_hub/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data

examples/skills_hub/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Skill Hub 示例
2+
3+
本示例演示 `trpc_agent_sdk.skills.hub`(Skill Hub):在启动 `LlmAgent` 之前,通过 `GitHubSource` 从 GitHub 上按需拉取一个技能(Anthropic 官方的 `skill-creator`),写入本地技能目录,再像本地技能一样交给 `SkillToolSet` 使用。
4+
5+
## 什么是 Skill Hub
6+
7+
Skill Hub(`trpc_agent_sdk.skills.hub`)把"从各种来源发现并获取 skill"统一到同一个接口后面。每个来源都是一个 `SkillSource` 适配器,对外提供一致的 `search` / `inspect` / `fetch` 三种能力:
8+
9+
| 适配器 | 来源 |
10+
| --- | --- |
11+
| `GitHubSource` | GitHub 仓库目录(本示例使用) |
12+
| `WellKnownSkillSource` | 站点 `.well-known/skills` |
13+
| `HermesIndexSource` | Hermes 内置 skill index |
14+
| `SkillsShSource` | skills.sh |
15+
| `ClawHubSource` | ClawHub registry |
16+
| `ClaudeMarketplaceSource` | Claude Skills Marketplace |
17+
| `LobeHubSource` | LobeHub |
18+
19+
`SkillSource.fetch()`只返回内存中的 `SkillBundle``name` + `files` + `metadata`),**把它写到磁盘、决定是否覆盖已有版本,都是调用方自己的策略**——SDK 本身不内置"安装"逻辑,因为不同 harness(本示例、trpc-hermes 的 `SkillSpec`/`preinstall_skills_to_dir`,或你自己的服务)对覆盖策略、原子写入、并发安全的要求都不一样。本示例里最小化的这部分逻辑在 `agent/hub.py``install_skill_from_github()`
20+
21+
## 关键特性
22+
23+
- `GitHubSource(GitHubAuth(token))` 无需认证即可拉取公开仓库(60 次/小时限额,足够本示例使用;设置 `GITHUB_TOKEN` 可提升到 5000 次/小时)
24+
- 用 SDK 导出的 `validate_skill_name` / `validate_bundle_rel_path` 校验 hub 返回的路径,避免恶意 `SkillBundle` 写出到目标目录之外
25+
- 已安装的技能会被跳过,重复运行示例不会重复下载
26+
- 拉取完成后,技能通过标准的 `create_default_skill_repository` + `SkillToolSet` 链路对 agent 可见,和本地技能没有区别
27+
28+
## Agent 层级结构说明
29+
30+
- 根节点:`LlmAgent``skill_hub_demo_agent`),挂载 `SkillToolSet``skill_repository`
31+
- 无子 Agent;单智能体通过 `skill_load` / `skill_list_docs` 等技能工具完成任务
32+
33+
## 关键代码解释
34+
35+
- `agent/hub.py`
36+
- `install_skill_from_github()`:若目标目录已存在则跳过;否则用 `GitHubSource.fetch(identifier)` 拉取 `SkillBundle`,校验并写出每个文件
37+
- `create_skill_tool_set()`:用安装好的目录构造 `create_default_skill_repository` + `SkillToolSet`
38+
- `agent/agent.py``create_agent(skills_dir)` 先调用 `install_skill_from_github`,再把返回的 `skill_repository` / `skill_tool_set` 绑定到 `LlmAgent`
39+
- `run_agent.py`:清空 `data/` 目录(保证每次都重新走一遍 Skill Hub 拉取流程),创建 agent,跑一轮 `skill_load` + 总结的对话,并打印实际下载到的文件列表
40+
41+
## 环境与运行
42+
43+
- Python 3.10+;仓库根目录执行 `pip install -e .`
44+
- 配置 `TRPC_AGENT_API_KEY``TRPC_AGENT_BASE_URL``TRPC_AGENT_MODEL_NAME`(可用 `.env`
45+
- 可选:`GITHUB_TOKEN`,用于提高 GitHub API 限额(本示例只读取公开仓库,不设置也能跑)
46+
47+
```bash
48+
cd examples/skills_hub
49+
python3 run_agent.py
50+
```
51+
52+
运行后会在 `data/skills/skill-creator/` 下看到从 GitHub 拉取的真实文件(`SKILL.md``scripts/``references/` 等)。
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.

examples/skills_hub/agent/agent.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
""" Agent that demonstrates fetching a skill from the Skill Hub before running. """
7+
8+
from pathlib import Path
9+
10+
from trpc_agent_sdk.agents import LlmAgent
11+
from trpc_agent_sdk.models import LLMModel
12+
from trpc_agent_sdk.models import OpenAIModel
13+
14+
from .config import get_model_config
15+
from .hub import GITHUB_SKILL_IDENTIFIER
16+
from .hub import GITHUB_SKILL_NAME
17+
from .hub import create_skill_tool_set
18+
from .hub import install_skill_from_github
19+
from .prompts import INSTRUCTION
20+
21+
22+
def _create_model() -> LLMModel:
23+
""" Create a model"""
24+
api_key, url, model_name = get_model_config()
25+
model = OpenAIModel(model_name=model_name, api_key=api_key, base_url=url)
26+
return model
27+
28+
29+
def create_agent(skills_dir: Path) -> LlmAgent:
30+
"""Fetch a skill from GitHub via the Skill Hub, then build an agent that can use it.
31+
32+
Args:
33+
skills_dir: Local directory to install fetched skills into. Populated
34+
with `skills_dir/skill-creator/` before the agent is constructed.
35+
"""
36+
install_skill_from_github(
37+
skills_dir=skills_dir,
38+
skill_name=GITHUB_SKILL_NAME,
39+
identifier=GITHUB_SKILL_IDENTIFIER,
40+
)
41+
skill_tool_set, skill_repository = create_skill_tool_set(skills_dir)
42+
43+
return LlmAgent(
44+
name="skill_hub_demo_agent",
45+
description="An assistant that fetches skills on demand from the Skill Hub.",
46+
model=_create_model(),
47+
instruction=INSTRUCTION,
48+
tools=[skill_tool_set],
49+
skill_repository=skill_repository,
50+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
""" Agent config module"""
7+
8+
import os
9+
10+
11+
def get_model_config() -> tuple[str, str, str]:
12+
"""Get model config from environment variables"""
13+
api_key = os.getenv('TRPC_AGENT_API_KEY', '')
14+
url = os.getenv('TRPC_AGENT_BASE_URL', '')
15+
model_name = os.getenv('TRPC_AGENT_MODEL_NAME', '')
16+
if not api_key or not url or not model_name:
17+
raise ValueError('''TRPC_AGENT_API_KEY, TRPC_AGENT_BASE_URL,
18+
and TRPC_AGENT_MODEL_NAME must be set in environment variables''')
19+
return api_key, url, model_name

examples/skills_hub/agent/hub.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""Skill Hub glue: fetch a skill from GitHub and install it into a local
7+
skills directory before the agent starts.
8+
9+
`trpc_agent_sdk.skills.hub.SkillSource.fetch()` only returns a `SkillBundle`
10+
in memory -- writing it to disk (and deciding on overwrite/skip semantics) is
11+
intentionally left to the caller, since that policy is harness-specific. This
12+
module shows the minimal version of that glue; a real harness (e.g.
13+
trpc-hermes's `SkillSpec` + `preinstall_skills_to_dir`) typically adds atomic
14+
staging and richer error handling on top of the same `SkillSource` adapters.
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import os
20+
from pathlib import Path
21+
22+
from trpc_agent_sdk.code_executors import create_local_workspace_runtime
23+
from trpc_agent_sdk.skills import BaseSkillRepository
24+
from trpc_agent_sdk.skills import SkillToolSet
25+
from trpc_agent_sdk.skills import create_default_skill_repository
26+
from trpc_agent_sdk.skills.hub import GitHubAuth
27+
from trpc_agent_sdk.skills.hub import GitHubSource
28+
from trpc_agent_sdk.skills.hub import validate_bundle_rel_path
29+
from trpc_agent_sdk.skills.hub import validate_skill_name
30+
31+
# `skill-creator` is Anthropic's own skill for building and iterating on
32+
# skills -- a fitting "meta" skill to fetch through the Skill Hub for a demo.
33+
GITHUB_SKILL_IDENTIFIER = "anthropics/skills/skills/skill-creator"
34+
GITHUB_SKILL_NAME = "skill-creator"
35+
36+
37+
def install_skill_from_github(*, skills_dir: Path, skill_name: str, identifier: str) -> None:
38+
"""Fetch `identifier` via `GitHubSource` and write it into `skills_dir/skill_name/`.
39+
40+
Skips the fetch entirely if the skill directory already exists, so
41+
re-running the demo doesn't re-download the skill every time.
42+
"""
43+
safe_name = validate_skill_name(skill_name)
44+
target_dir = skills_dir / safe_name
45+
if target_dir.exists():
46+
return
47+
48+
# Unauthenticated requests are capped at 60 req/hr, which is plenty for
49+
# this demo. Set GITHUB_TOKEN to raise that limit for repeated runs.
50+
source = GitHubSource(GitHubAuth(os.getenv("GITHUB_TOKEN") or None))
51+
bundle = source.fetch(identifier)
52+
if bundle is None:
53+
raise RuntimeError(
54+
f"Could not fetch skill {identifier!r} from GitHub via the Skill Hub "
55+
"(hit the rate limit? set GITHUB_TOKEN in .env)."
56+
)
57+
58+
skills_dir.mkdir(parents=True, exist_ok=True)
59+
target_dir.mkdir(parents=True)
60+
for rel_path, content in bundle.files.items():
61+
safe_rel_path = validate_bundle_rel_path(rel_path)
62+
dest = target_dir / safe_rel_path
63+
dest.parent.mkdir(parents=True, exist_ok=True)
64+
if isinstance(content, bytes):
65+
dest.write_bytes(content)
66+
else:
67+
dest.write_text(content, encoding="utf-8")
68+
69+
70+
def create_skill_tool_set(skills_dir: Path) -> tuple[SkillToolSet, BaseSkillRepository]:
71+
"""Build a `SkillToolSet` backed by whatever has been installed into `skills_dir`."""
72+
workspace_runtime = create_local_workspace_runtime()
73+
repository = create_default_skill_repository(str(skills_dir), workspace_runtime=workspace_runtime)
74+
skill_toolset = SkillToolSet(repository=repository)
75+
return skill_toolset, repository
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
""" prompts for agent"""
7+
8+
INSTRUCTION = """
9+
Be a concise, helpful assistant that can use Agent Skills.
10+
11+
A skill named "skill-creator" was just fetched on demand from GitHub via the
12+
Skill Hub (`trpc_agent_sdk.skills.hub`) and installed locally before you were
13+
started, so it is available like any other local skill.
14+
15+
When asked about a skill, call skill_load to load its documentation, then
16+
skill_list_docs to see what documentation and files are available. Summarize
17+
what you find concisely; do not run any of the skill's scripts unless
18+
explicitly asked to.
19+
"""

0 commit comments

Comments
 (0)