axi-cli 0.0.3__tar.gz → 0.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {axi_cli-0.0.3 → axi_cli-0.0.5}/CHANGELOG.md +23 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/PKG-INFO +89 -10
- axi_cli-0.0.5/README.md +193 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/pyproject.toml +1 -1
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/cli.py +1 -1
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/providers/mcp.py +1 -60
- axi_cli-0.0.5/src/axi/providers/native.py +194 -0
- axi_cli-0.0.3/README.md +0 -114
- axi_cli-0.0.3/src/axi/providers/native.py +0 -66
- {axi_cli-0.0.3 → axi_cli-0.0.5}/.gitignore +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/__init__.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/config.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/daemon/client.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/daemon/protocol.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/daemon/server.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/executor.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/models.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/providers/__init__.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/registry.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/search/__init__.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/search/bm25.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/search/cache.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/search/embedding.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/search/hybrid.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/search/regex.py +0 -0
- {axi_cli-0.0.3 → axi_cli-0.0.5}/src/axi/search/tokenize.py +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.5] - 2026-04-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Python entry_points 自动发现原生工具:扩展包在 `pyproject.toml` 里声明 `[project.entry-points."axi.native_tools"]` 即可被 axi 自动发现,无需 `axi.json` 登记
|
|
7
|
+
- `docs/configuration.md` 补充 entry_points 机制说明与合并规则(去重、同名 server collision、加载失败降级)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- `load_native_tool_modules` 从 `providers/mcp.py` 搬到 `providers/native.py`,职责回归原生工具 Provider
|
|
11
|
+
- 模块加载失败时不再占位,避免对后续合法包误报 server 名 collision
|
|
12
|
+
|
|
13
|
+
## [0.0.4] - 2026-04-20
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `AXI_CONFIG` 环境变量:自定义 `axi.json` 配置文件路径
|
|
17
|
+
- GitHub Actions workflow:打 `v*` tag 时自动通过 trusted publishing 发布到 PyPI
|
|
18
|
+
- `pyproject.toml` 新增 sdist 打包排除规则(`.github/`、`docs/`、`tests/`、`uv.lock` 等)
|
|
19
|
+
- README 补充 daemon 自动管理、生命周期、状态输出、文件位置等完整说明
|
|
20
|
+
- `docs/architecture.md` 补充 daemon 启动流程、idle watchdog、关闭流程、通信协议章节
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- 包名由 `axi` 重命名为 `axi-cli`(PyPI 发布名)
|
|
24
|
+
- 安装方式从 `uv pip install -e .` 改为 `uv sync`
|
|
25
|
+
|
|
3
26
|
## [0.0.3] - 2026-04-01
|
|
4
27
|
|
|
5
28
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axi-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Agent eXecution Interface - unified tool layer for AI Agents
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: bm25s>=0.3.3
|
|
@@ -29,8 +29,8 @@ Agent eXecution Interface — AI Agent 与外部系统之间的统一工具层
|
|
|
29
29
|
## 安装
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
# 需要 Python 3.12
|
|
33
|
-
uv
|
|
32
|
+
# 需要 Python 3.12+, uv
|
|
33
|
+
uv sync
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## 快速开始
|
|
@@ -57,28 +57,45 @@ axi run jina/jina_search --query "hello world" --count 3
|
|
|
57
57
|
"command": "npx",
|
|
58
58
|
"args": ["jina-mcp-tools"],
|
|
59
59
|
"env": { "JINA_API_KEY": "your-key" }
|
|
60
|
+
},
|
|
61
|
+
"retrieval": {
|
|
62
|
+
"url": "http://localhost:8000/mcp"
|
|
60
63
|
}
|
|
61
64
|
},
|
|
62
65
|
"nativeTools": [
|
|
63
66
|
{"module": "my_project.tools"},
|
|
64
67
|
{"module": "./scripts/tools.py", "name": "scripts"}
|
|
65
|
-
]
|
|
68
|
+
],
|
|
69
|
+
"search": {
|
|
70
|
+
"embedding": {
|
|
71
|
+
"provider": "jina"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"daemon": {
|
|
75
|
+
"idleTimeoutMinutes": 30
|
|
76
|
+
}
|
|
66
77
|
}
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
- **mcpServers** — MCP server 配置,key
|
|
80
|
+
- **mcpServers** — MCP server 配置,key 名作为工具命名空间。支持 `command`(本地进程)和 `url`(HTTP streaming)两种模式
|
|
70
81
|
- **nativeTools** — 原生 Python 工具,`module` 支持文件路径和模块路径,`name` 可选(省略则自动推导)
|
|
82
|
+
- **search** — 搜索配置,`embedding` 段启用语义搜索(支持 Jina/OpenAI)
|
|
83
|
+
- **daemon** — daemon 进程配置,如 idle 超时时间
|
|
84
|
+
|
|
85
|
+
完整配置参考见 [docs/configuration.md](docs/configuration.md)。
|
|
71
86
|
|
|
72
87
|
## CLI 命令
|
|
73
88
|
|
|
74
89
|
| 命令 | 说明 |
|
|
75
90
|
|------|------|
|
|
76
91
|
| `axi list [server]` | 列出所有 server 及工具 |
|
|
77
|
-
| `axi search <query>` | 混合搜索工具(BM25 + Embedding,支持 `--top-k`) |
|
|
78
|
-
| `axi grep <pattern>` | 正则表达式搜索工具(支持 `--
|
|
92
|
+
| `axi search <query>` | 混合搜索工具(BM25 + Embedding,支持 `--top-k/-k`) |
|
|
93
|
+
| `axi grep <pattern>` | 正则表达式搜索工具(支持 `--limit/-l`) |
|
|
79
94
|
| `axi describe <tool>` | 查看工具完整 schema |
|
|
80
95
|
| `axi run <tool> --key value` | 执行工具(也支持 `-j '{...}'` 传 JSON) |
|
|
81
|
-
| `axi daemon start
|
|
96
|
+
| `axi daemon start` | 手动启动 daemon(通常无需手动,CLI 会自动拉起) |
|
|
97
|
+
| `axi daemon status` | 查看 daemon 状态(PID、运行时长、空闲时长、工具数量等) |
|
|
98
|
+
| `axi daemon stop` | 手动停止 daemon |
|
|
82
99
|
|
|
83
100
|
所有输出统一为紧凑 JSON。`axi run` 返回统一信封:
|
|
84
101
|
|
|
@@ -110,18 +127,80 @@ search = tool("jina/jina_search")
|
|
|
110
127
|
results = search(query="python async", count=5)
|
|
111
128
|
```
|
|
112
129
|
|
|
130
|
+
## 搜索
|
|
131
|
+
|
|
132
|
+
axi 提供两种搜索方式:
|
|
133
|
+
|
|
134
|
+
- **`axi search`** — 混合搜索,默认 BM25 关键词搜索(bm25s + jieba 分词,支持中英文)。配置 `search.embedding` 后启用 BM25 + Embedding 混合搜索(RRF 融合排序,分数归一化 0-1)
|
|
135
|
+
- **`axi grep`** — 正则表达式搜索,按工具名和描述匹配
|
|
136
|
+
|
|
137
|
+
搜索结果包含 `score` 字段,便于 Agent 判断相关性。
|
|
138
|
+
|
|
113
139
|
## Daemon 模式
|
|
114
140
|
|
|
115
|
-
MCP 工具通过后台 daemon 长连接执行,支持有状态 MCP server(如 browser MCP
|
|
141
|
+
MCP 工具通过后台 daemon 长连接执行,支持有状态 MCP server(如 browser MCP)。
|
|
116
142
|
|
|
117
143
|
```
|
|
118
144
|
axi CLI ──(Unix socket)──> daemon ──(stdio)──> MCP server A
|
|
119
145
|
──(stdio)──> MCP server B
|
|
120
146
|
```
|
|
121
147
|
|
|
148
|
+
### 自动管理
|
|
149
|
+
|
|
150
|
+
执行 `axi search`、`describe`、`run` 等命令时,CLI 会自动检测 daemon 是否运行,未运行则自动拉起。大多数情况下无需手动管理。
|
|
151
|
+
|
|
152
|
+
### 生命周期
|
|
153
|
+
|
|
154
|
+
| 阶段 | 行为 |
|
|
155
|
+
|------|------|
|
|
156
|
+
| **启动** | 加载 `axi.json` → 连接所有 MCP server → 创建 Unix socket → 写入 PID 文件 → 启动 idle watchdog |
|
|
157
|
+
| **运行** | 通过 `~/.axi/daemon.sock` 监听请求,路由到对应 MCP server 执行 |
|
|
158
|
+
| **空闲关闭** | 默认 30 分钟无活动自动关闭(每 60 秒检查一次;`status` 和 `shutdown` 请求不重置计时器) |
|
|
159
|
+
| **手动关闭** | `axi daemon stop` 发送关闭指令,daemon 优雅退出 |
|
|
160
|
+
| **信号关闭** | 收到 SIGTERM / SIGINT 时优雅关闭(清理 socket 和 PID 文件) |
|
|
161
|
+
|
|
162
|
+
空闲超时可通过 `axi.json` 的 `daemon.idleTimeoutMinutes` 配置:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{ "daemon": { "idleTimeoutMinutes": 60 } }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 手动管理
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
axi daemon start # 手动启动(通常不需要)
|
|
172
|
+
axi daemon status # 查看状态
|
|
173
|
+
axi daemon stop # 手动停止
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`axi daemon status` 输出示例:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"status": "running",
|
|
181
|
+
"pid": 12345,
|
|
182
|
+
"uptime_seconds": 1800,
|
|
183
|
+
"idle_seconds": 120,
|
|
184
|
+
"idle_timeout_seconds": 1800,
|
|
185
|
+
"idle_remaining_seconds": 1680,
|
|
186
|
+
"server_tools": { "jina": 5, "browser": 3 },
|
|
187
|
+
"native_tools": 2
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 文件位置
|
|
192
|
+
|
|
193
|
+
daemon 运行时文件位于 `~/.axi/`:
|
|
194
|
+
|
|
195
|
+
| 文件 | 说明 |
|
|
196
|
+
|------|------|
|
|
197
|
+
| `daemon.sock` | Unix socket,CLI 与 daemon 的通信通道 |
|
|
198
|
+
| `daemon.pid` | daemon 进程 PID,用于检测是否存活 |
|
|
199
|
+
| `daemon.log` | daemon 启动和错误日志 |
|
|
200
|
+
|
|
122
201
|
## 技术栈
|
|
123
202
|
|
|
124
|
-
Python 3.12+ / [Typer](https://typer.tiangolo.com/) / [Pydantic](https://docs.pydantic.dev/) / [MCP SDK](https://github.com/modelcontextprotocol/python-sdk)
|
|
203
|
+
Python 3.12+ / [Typer](https://typer.tiangolo.com/) / [Pydantic](https://docs.pydantic.dev/) / [MCP SDK](https://github.com/modelcontextprotocol/python-sdk) / [bm25s](https://github.com/xhluca/bm25s) + [jieba](https://github.com/fxsjy/jieba) / [LangChain](https://python.langchain.com/)(Embedding)
|
|
125
204
|
|
|
126
205
|
## License
|
|
127
206
|
|
axi_cli-0.0.5/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# axi
|
|
2
|
+
|
|
3
|
+
Agent eXecution Interface — AI Agent 与外部系统之间的统一工具层。
|
|
4
|
+
|
|
5
|
+
通过 CLI 作为万能适配器,将 MCP server 和 Python 函数统一为可搜索、可描述、可调用的命令。Agent 无需猜测命令格式或解析人类可读输出,所有交互都是结构化 JSON。
|
|
6
|
+
|
|
7
|
+
## 核心理念
|
|
8
|
+
|
|
9
|
+
几乎所有主流 Agent 框架都把 bash 作为基础能力。axi 在这个"万能通道"之上建一层 Agent-native 的工具层:
|
|
10
|
+
|
|
11
|
+
- **渐进式披露** — `search → describe → run`,按需获取信息,不吃 context window
|
|
12
|
+
- **Agent-first 输出** — 统一紧凑 JSON,无表格、无颜色、无进度条
|
|
13
|
+
- **双来源统一** — MCP server 和原生 Python 工具,对 Agent 来说是同一套接口
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 需要 Python 3.12+, uv
|
|
19
|
+
uv sync
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 快速开始
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 搜索工具
|
|
26
|
+
axi search "web"
|
|
27
|
+
|
|
28
|
+
# 查看工具详情
|
|
29
|
+
axi describe jina/jina_search
|
|
30
|
+
|
|
31
|
+
# 执行工具
|
|
32
|
+
axi run jina/jina_search --query "hello world" --count 3
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 配置
|
|
36
|
+
|
|
37
|
+
在项目根目录创建 `axi.json`(参考 `axi.json.example`):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"jina": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["jina-mcp-tools"],
|
|
45
|
+
"env": { "JINA_API_KEY": "your-key" }
|
|
46
|
+
},
|
|
47
|
+
"retrieval": {
|
|
48
|
+
"url": "http://localhost:8000/mcp"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"nativeTools": [
|
|
52
|
+
{"module": "my_project.tools"},
|
|
53
|
+
{"module": "./scripts/tools.py", "name": "scripts"}
|
|
54
|
+
],
|
|
55
|
+
"search": {
|
|
56
|
+
"embedding": {
|
|
57
|
+
"provider": "jina"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"daemon": {
|
|
61
|
+
"idleTimeoutMinutes": 30
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **mcpServers** — MCP server 配置,key 名作为工具命名空间。支持 `command`(本地进程)和 `url`(HTTP streaming)两种模式
|
|
67
|
+
- **nativeTools** — 原生 Python 工具,`module` 支持文件路径和模块路径,`name` 可选(省略则自动推导)
|
|
68
|
+
- **search** — 搜索配置,`embedding` 段启用语义搜索(支持 Jina/OpenAI)
|
|
69
|
+
- **daemon** — daemon 进程配置,如 idle 超时时间
|
|
70
|
+
|
|
71
|
+
完整配置参考见 [docs/configuration.md](docs/configuration.md)。
|
|
72
|
+
|
|
73
|
+
## CLI 命令
|
|
74
|
+
|
|
75
|
+
| 命令 | 说明 |
|
|
76
|
+
|------|------|
|
|
77
|
+
| `axi list [server]` | 列出所有 server 及工具 |
|
|
78
|
+
| `axi search <query>` | 混合搜索工具(BM25 + Embedding,支持 `--top-k/-k`) |
|
|
79
|
+
| `axi grep <pattern>` | 正则表达式搜索工具(支持 `--limit/-l`) |
|
|
80
|
+
| `axi describe <tool>` | 查看工具完整 schema |
|
|
81
|
+
| `axi run <tool> --key value` | 执行工具(也支持 `-j '{...}'` 传 JSON) |
|
|
82
|
+
| `axi daemon start` | 手动启动 daemon(通常无需手动,CLI 会自动拉起) |
|
|
83
|
+
| `axi daemon status` | 查看 daemon 状态(PID、运行时长、空闲时长、工具数量等) |
|
|
84
|
+
| `axi daemon stop` | 手动停止 daemon |
|
|
85
|
+
|
|
86
|
+
所有输出统一为紧凑 JSON。`axi run` 返回统一信封:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{"status": "success", "data": "..."}
|
|
90
|
+
{"status": "error", "error": "错误信息"}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 注册原生工具
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from axi import tool
|
|
97
|
+
|
|
98
|
+
@tool(name="query_orders", description="按区域查询订单")
|
|
99
|
+
def query_orders(region: str, limit: int = 10) -> dict:
|
|
100
|
+
return {"orders": [...], "total": 42}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
装饰器自动从函数签名提取参数 schema。注册后同时获得 CLI 调用和 PTC 调用能力。
|
|
104
|
+
|
|
105
|
+
## PTC(Programmatic Tool Calling)
|
|
106
|
+
|
|
107
|
+
Agent 可以写 Python 代码批量调用工具,在本地做数据过滤和聚合,避免反复 LLM round-trip:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from axi import tool
|
|
111
|
+
|
|
112
|
+
search = tool("jina/jina_search")
|
|
113
|
+
results = search(query="python async", count=5)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 搜索
|
|
117
|
+
|
|
118
|
+
axi 提供两种搜索方式:
|
|
119
|
+
|
|
120
|
+
- **`axi search`** — 混合搜索,默认 BM25 关键词搜索(bm25s + jieba 分词,支持中英文)。配置 `search.embedding` 后启用 BM25 + Embedding 混合搜索(RRF 融合排序,分数归一化 0-1)
|
|
121
|
+
- **`axi grep`** — 正则表达式搜索,按工具名和描述匹配
|
|
122
|
+
|
|
123
|
+
搜索结果包含 `score` 字段,便于 Agent 判断相关性。
|
|
124
|
+
|
|
125
|
+
## Daemon 模式
|
|
126
|
+
|
|
127
|
+
MCP 工具通过后台 daemon 长连接执行,支持有状态 MCP server(如 browser MCP)。
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
axi CLI ──(Unix socket)──> daemon ──(stdio)──> MCP server A
|
|
131
|
+
──(stdio)──> MCP server B
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 自动管理
|
|
135
|
+
|
|
136
|
+
执行 `axi search`、`describe`、`run` 等命令时,CLI 会自动检测 daemon 是否运行,未运行则自动拉起。大多数情况下无需手动管理。
|
|
137
|
+
|
|
138
|
+
### 生命周期
|
|
139
|
+
|
|
140
|
+
| 阶段 | 行为 |
|
|
141
|
+
|------|------|
|
|
142
|
+
| **启动** | 加载 `axi.json` → 连接所有 MCP server → 创建 Unix socket → 写入 PID 文件 → 启动 idle watchdog |
|
|
143
|
+
| **运行** | 通过 `~/.axi/daemon.sock` 监听请求,路由到对应 MCP server 执行 |
|
|
144
|
+
| **空闲关闭** | 默认 30 分钟无活动自动关闭(每 60 秒检查一次;`status` 和 `shutdown` 请求不重置计时器) |
|
|
145
|
+
| **手动关闭** | `axi daemon stop` 发送关闭指令,daemon 优雅退出 |
|
|
146
|
+
| **信号关闭** | 收到 SIGTERM / SIGINT 时优雅关闭(清理 socket 和 PID 文件) |
|
|
147
|
+
|
|
148
|
+
空闲超时可通过 `axi.json` 的 `daemon.idleTimeoutMinutes` 配置:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{ "daemon": { "idleTimeoutMinutes": 60 } }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 手动管理
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
axi daemon start # 手动启动(通常不需要)
|
|
158
|
+
axi daemon status # 查看状态
|
|
159
|
+
axi daemon stop # 手动停止
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`axi daemon status` 输出示例:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"status": "running",
|
|
167
|
+
"pid": 12345,
|
|
168
|
+
"uptime_seconds": 1800,
|
|
169
|
+
"idle_seconds": 120,
|
|
170
|
+
"idle_timeout_seconds": 1800,
|
|
171
|
+
"idle_remaining_seconds": 1680,
|
|
172
|
+
"server_tools": { "jina": 5, "browser": 3 },
|
|
173
|
+
"native_tools": 2
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 文件位置
|
|
178
|
+
|
|
179
|
+
daemon 运行时文件位于 `~/.axi/`:
|
|
180
|
+
|
|
181
|
+
| 文件 | 说明 |
|
|
182
|
+
|------|------|
|
|
183
|
+
| `daemon.sock` | Unix socket,CLI 与 daemon 的通信通道 |
|
|
184
|
+
| `daemon.pid` | daemon 进程 PID,用于检测是否存活 |
|
|
185
|
+
| `daemon.log` | daemon 启动和错误日志 |
|
|
186
|
+
|
|
187
|
+
## 技术栈
|
|
188
|
+
|
|
189
|
+
Python 3.12+ / [Typer](https://typer.tiangolo.com/) / [Pydantic](https://docs.pydantic.dev/) / [MCP SDK](https://github.com/modelcontextprotocol/python-sdk) / [bm25s](https://github.com/xhluca/bm25s) + [jieba](https://github.com/fxsjy/jieba) / [LangChain](https://python.langchain.com/)(Embedding)
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
|
@@ -34,7 +34,7 @@ _executor = Executor(_registry)
|
|
|
34
34
|
@app.callback(invoke_without_command=True)
|
|
35
35
|
def main_callback(ctx: typer.Context) -> None:
|
|
36
36
|
"""CLI 启动时加载 axi.json 中配置的原生工具模块。"""
|
|
37
|
-
from axi.providers.
|
|
37
|
+
from axi.providers.native import load_native_tool_modules
|
|
38
38
|
|
|
39
39
|
load_native_tool_modules()
|
|
40
40
|
if ctx.invoked_subcommand is None:
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"""MCP Provider:读取 axi.json,连接 MCP server,注册工具。"""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import importlib.util
|
|
5
4
|
import json
|
|
6
5
|
import logging
|
|
7
6
|
import os
|
|
8
7
|
import threading
|
|
9
8
|
from contextlib import AsyncExitStack
|
|
10
|
-
from pathlib import Path
|
|
11
9
|
from typing import Any, Self
|
|
12
10
|
|
|
13
11
|
from mcp import ClientSession, StdioServerParameters
|
|
@@ -15,11 +13,7 @@ from mcp.client.stdio import stdio_client
|
|
|
15
13
|
from mcp.client.streamable_http import streamable_http_client
|
|
16
14
|
from pydantic import Field, model_validator
|
|
17
15
|
|
|
18
|
-
from axi.config import
|
|
19
|
-
MCPServerConfig as MCPServerBaseConfig,
|
|
20
|
-
NativeToolEntry,
|
|
21
|
-
app_config,
|
|
22
|
-
)
|
|
16
|
+
from axi.config import MCPServerConfig as MCPServerBaseConfig, app_config
|
|
23
17
|
from axi.models import RunResult, ToolMeta, ToolSource
|
|
24
18
|
|
|
25
19
|
logger = logging.getLogger(__name__)
|
|
@@ -220,59 +214,6 @@ def run_async(coro: Any) -> Any:
|
|
|
220
214
|
return future.result()
|
|
221
215
|
|
|
222
216
|
|
|
223
|
-
def _import_from_file(file_path: str) -> None:
|
|
224
|
-
"""通过文件路径 import 模块,触发 @tool 注册。"""
|
|
225
|
-
path = Path(file_path).resolve()
|
|
226
|
-
if not path.exists():
|
|
227
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
|
228
|
-
module_name = path.stem
|
|
229
|
-
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
230
|
-
if spec is None or spec.loader is None:
|
|
231
|
-
raise ImportError(f"Cannot load module from: {file_path}")
|
|
232
|
-
module = importlib.util.module_from_spec(spec)
|
|
233
|
-
spec.loader.exec_module(module)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def _resolve_server_name(entry: NativeToolEntry) -> str:
|
|
237
|
-
"""推导 native tool 的 server 名称。"""
|
|
238
|
-
if entry.name is not None:
|
|
239
|
-
return entry.name
|
|
240
|
-
if entry.module.endswith(".py"):
|
|
241
|
-
return Path(entry.module).stem
|
|
242
|
-
return entry.module.rsplit(".", 1)[-1]
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def load_native_tool_modules() -> None:
|
|
246
|
-
"""从全局配置的 nativeTools 列表中加载模块,触发 @tool 注册。"""
|
|
247
|
-
entries = app_config.native_tools
|
|
248
|
-
if not entries:
|
|
249
|
-
return
|
|
250
|
-
|
|
251
|
-
from axi.cli import get_registry
|
|
252
|
-
|
|
253
|
-
registry = get_registry()
|
|
254
|
-
|
|
255
|
-
for entry in entries:
|
|
256
|
-
try:
|
|
257
|
-
server_name = _resolve_server_name(entry)
|
|
258
|
-
|
|
259
|
-
# 记录导入前已有的工具
|
|
260
|
-
before = set(registry.list_names())
|
|
261
|
-
|
|
262
|
-
if entry.module.endswith(".py"):
|
|
263
|
-
_import_from_file(entry.module)
|
|
264
|
-
else:
|
|
265
|
-
importlib.import_module(entry.module)
|
|
266
|
-
|
|
267
|
-
# 为新增的工具设置 server
|
|
268
|
-
new_names = [k for k in registry.list_names() if k not in before]
|
|
269
|
-
for name in new_names:
|
|
270
|
-
registry.set_server(name, server_name)
|
|
271
|
-
|
|
272
|
-
except Exception:
|
|
273
|
-
logger.exception("Failed to load native tool '%s'", entry)
|
|
274
|
-
|
|
275
|
-
|
|
276
217
|
def load_mcp_tools_sync() -> tuple[MCPProvider, list[ToolMeta]]:
|
|
277
218
|
"""同步包装:加载配置并连接所有 MCP server。"""
|
|
278
219
|
provider = MCPProvider()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""原生工具 Provider:通过 @tool 装饰器注册 Python 函数,
|
|
2
|
+
并负责从 axi.json 的 nativeTools 与 Python entry_points 加载工具模块。"""
|
|
3
|
+
|
|
4
|
+
import importlib
|
|
5
|
+
import importlib.metadata
|
|
6
|
+
import importlib.util
|
|
7
|
+
import inspect
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Callable, get_type_hints
|
|
11
|
+
|
|
12
|
+
from pydantic import create_model
|
|
13
|
+
|
|
14
|
+
from axi.config import NativeToolEntry, app_config
|
|
15
|
+
from axi.models import ToolMeta, ToolSource
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# 全局注册表,存储原生工具的函数引用
|
|
20
|
+
_native_functions: dict[str, Callable] = {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── 工具注册(@tool 装饰器的执行层)────────────────────────────
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _extract_input_schema(func: Callable) -> dict[str, Any]:
|
|
27
|
+
"""从函数签名和 type hints 提取 JSON Schema。
|
|
28
|
+
|
|
29
|
+
利用 Pydantic 的 create_model 动态构建模型,
|
|
30
|
+
自动支持 Literal、Optional、list[T]、嵌套 BaseModel、Annotated[..., Field()] 等。
|
|
31
|
+
"""
|
|
32
|
+
hints = get_type_hints(func, include_extras=True)
|
|
33
|
+
sig = inspect.signature(func)
|
|
34
|
+
fields: dict[str, Any] = {}
|
|
35
|
+
|
|
36
|
+
for name, param in sig.parameters.items():
|
|
37
|
+
hint = hints.get(name, Any)
|
|
38
|
+
if param.default is inspect.Parameter.empty:
|
|
39
|
+
fields[name] = (hint, ...)
|
|
40
|
+
else:
|
|
41
|
+
fields[name] = (hint, param.default)
|
|
42
|
+
|
|
43
|
+
model = create_model(func.__name__, **fields)
|
|
44
|
+
schema = model.model_json_schema()
|
|
45
|
+
|
|
46
|
+
# 移除 Pydantic 自动添加的 title 字段,保持输出紧凑
|
|
47
|
+
schema.pop("title", None)
|
|
48
|
+
for prop in schema.get("properties", {}).values():
|
|
49
|
+
prop.pop("title", None)
|
|
50
|
+
|
|
51
|
+
return schema
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def register_tool(
|
|
55
|
+
func: Callable,
|
|
56
|
+
name: str | None = None,
|
|
57
|
+
description: str | None = None,
|
|
58
|
+
output_example: Any | None = None,
|
|
59
|
+
) -> ToolMeta:
|
|
60
|
+
"""注册一个原生 Python 函数为 axi 工具。"""
|
|
61
|
+
tool_name = name or func.__name__
|
|
62
|
+
tool_desc = description or func.__doc__ or ""
|
|
63
|
+
|
|
64
|
+
meta = ToolMeta(
|
|
65
|
+
name=tool_name,
|
|
66
|
+
description=tool_desc,
|
|
67
|
+
input_schema=_extract_input_schema(func),
|
|
68
|
+
output_example=output_example,
|
|
69
|
+
source=ToolSource.NATIVE,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
_native_functions[tool_name] = func
|
|
73
|
+
return meta
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_native_function(name: str) -> Callable | None:
|
|
77
|
+
"""获取已注册的原生函数。"""
|
|
78
|
+
return _native_functions.get(name)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ── 模块发现与加载 ─────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
NATIVE_TOOLS_ENTRY_POINT_GROUP = "axi.native_tools"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _import_from_file(file_path: str) -> None:
|
|
87
|
+
"""通过文件路径 import 模块,触发 @tool 注册。"""
|
|
88
|
+
path = Path(file_path).resolve()
|
|
89
|
+
if not path.exists():
|
|
90
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
91
|
+
module_name = path.stem
|
|
92
|
+
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
93
|
+
if spec is None or spec.loader is None:
|
|
94
|
+
raise ImportError(f"Cannot load module from: {file_path}")
|
|
95
|
+
module = importlib.util.module_from_spec(spec)
|
|
96
|
+
spec.loader.exec_module(module)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _resolve_server_name(entry: NativeToolEntry) -> str:
|
|
100
|
+
"""推导 native tool 的 server 名称。"""
|
|
101
|
+
if entry.name is not None:
|
|
102
|
+
return entry.name
|
|
103
|
+
if entry.module.endswith(".py"):
|
|
104
|
+
return Path(entry.module).stem
|
|
105
|
+
return entry.module.rsplit(".", 1)[-1]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _load_native_entry(
|
|
109
|
+
registry: Any,
|
|
110
|
+
module: str,
|
|
111
|
+
server_name: str,
|
|
112
|
+
loaded: dict[str, str],
|
|
113
|
+
source: str,
|
|
114
|
+
) -> None:
|
|
115
|
+
"""加载单个原生工具模块并把新增工具归到 server_name 下。
|
|
116
|
+
|
|
117
|
+
``loaded`` 以 "模块路径 → server 名" 映射**仅记录成功加载**的模块:
|
|
118
|
+
- 模块路径重复 → 跳过(axi.json 与 entry_points 声明同一个模块是预期情况)
|
|
119
|
+
- 导入抛异常 → logger.exception 后 return,不占坑也不让一个坏包搞崩 CLI
|
|
120
|
+
- 加载成功但 server 名被别的模块占了 → logger.warning,继续挂到该 server 下
|
|
121
|
+
|
|
122
|
+
失败的模块不写入 ``loaded`` 可以避免对后续合法包误报 collision。
|
|
123
|
+
"""
|
|
124
|
+
if module in loaded:
|
|
125
|
+
return
|
|
126
|
+
try:
|
|
127
|
+
before = set(registry.list_names())
|
|
128
|
+
if module.endswith(".py"):
|
|
129
|
+
_import_from_file(module)
|
|
130
|
+
else:
|
|
131
|
+
importlib.import_module(module)
|
|
132
|
+
new_names = [n for n in registry.list_names() if n not in before]
|
|
133
|
+
except Exception:
|
|
134
|
+
logger.exception(
|
|
135
|
+
"Failed to load native tool module '%s' (from %s)", module, source
|
|
136
|
+
)
|
|
137
|
+
return
|
|
138
|
+
if server_name in loaded.values():
|
|
139
|
+
other = next(m for m, s in loaded.items() if s == server_name)
|
|
140
|
+
logger.warning(
|
|
141
|
+
"Server name collision: '%s' already claimed by module '%s'; "
|
|
142
|
+
"tools from '%s' (%s) will be merged under the same server",
|
|
143
|
+
server_name,
|
|
144
|
+
other,
|
|
145
|
+
module,
|
|
146
|
+
source,
|
|
147
|
+
)
|
|
148
|
+
loaded[module] = server_name
|
|
149
|
+
for name in new_names:
|
|
150
|
+
registry.set_server(name, server_name)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def load_native_tool_modules() -> None:
|
|
154
|
+
"""从 axi.json 的 nativeTools 与 Python entry_points 加载原生工具模块。
|
|
155
|
+
|
|
156
|
+
合并规则:
|
|
157
|
+
- axi.json 先遍历,entry_points 后遍历;按**模块路径**去重(同一个模块只加载一次)
|
|
158
|
+
- 因此重复声明时 axi.json 的 server 名赢
|
|
159
|
+
- 不同模块声明同一个 server 名时仅 log warning,两边工具都会注册到该 server
|
|
160
|
+
"""
|
|
161
|
+
from axi.cli import get_registry
|
|
162
|
+
|
|
163
|
+
registry = get_registry()
|
|
164
|
+
loaded: dict[str, str] = {}
|
|
165
|
+
|
|
166
|
+
# 来源 1:axi.json 的 nativeTools(显式配置,优先级更高)
|
|
167
|
+
for entry in app_config.native_tools:
|
|
168
|
+
_load_native_entry(
|
|
169
|
+
registry,
|
|
170
|
+
entry.module,
|
|
171
|
+
_resolve_server_name(entry),
|
|
172
|
+
loaded,
|
|
173
|
+
source="axi.json",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# 来源 2:Python entry_points group="axi.native_tools"
|
|
177
|
+
# 任何 pip 安装的包在 pyproject.toml 里声明就会被自动发现,
|
|
178
|
+
# 无需在 axi.json 里重复登记。
|
|
179
|
+
try:
|
|
180
|
+
eps = importlib.metadata.entry_points(group=NATIVE_TOOLS_ENTRY_POINT_GROUP)
|
|
181
|
+
except Exception:
|
|
182
|
+
logger.exception(
|
|
183
|
+
"Failed to query entry_points for %s", NATIVE_TOOLS_ENTRY_POINT_GROUP
|
|
184
|
+
)
|
|
185
|
+
eps = []
|
|
186
|
+
for ep in eps:
|
|
187
|
+
module_path = ep.value.split(":", 1)[0].strip()
|
|
188
|
+
_load_native_entry(
|
|
189
|
+
registry,
|
|
190
|
+
module_path,
|
|
191
|
+
ep.name,
|
|
192
|
+
loaded,
|
|
193
|
+
source=f"entry_points:{ep.name}",
|
|
194
|
+
)
|
axi_cli-0.0.3/README.md
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# axi
|
|
2
|
-
|
|
3
|
-
Agent eXecution Interface — AI Agent 与外部系统之间的统一工具层。
|
|
4
|
-
|
|
5
|
-
通过 CLI 作为万能适配器,将 MCP server 和 Python 函数统一为可搜索、可描述、可调用的命令。Agent 无需猜测命令格式或解析人类可读输出,所有交互都是结构化 JSON。
|
|
6
|
-
|
|
7
|
-
## 核心理念
|
|
8
|
-
|
|
9
|
-
几乎所有主流 Agent 框架都把 bash 作为基础能力。axi 在这个"万能通道"之上建一层 Agent-native 的工具层:
|
|
10
|
-
|
|
11
|
-
- **渐进式披露** — `search → describe → run`,按需获取信息,不吃 context window
|
|
12
|
-
- **Agent-first 输出** — 统一紧凑 JSON,无表格、无颜色、无进度条
|
|
13
|
-
- **双来源统一** — MCP server 和原生 Python 工具,对 Agent 来说是同一套接口
|
|
14
|
-
|
|
15
|
-
## 安装
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
# 需要 Python 3.12+
|
|
19
|
-
uv pip install -e .
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## 快速开始
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
# 搜索工具
|
|
26
|
-
axi search "web"
|
|
27
|
-
|
|
28
|
-
# 查看工具详情
|
|
29
|
-
axi describe jina/jina_search
|
|
30
|
-
|
|
31
|
-
# 执行工具
|
|
32
|
-
axi run jina/jina_search --query "hello world" --count 3
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## 配置
|
|
36
|
-
|
|
37
|
-
在项目根目录创建 `axi.json`(参考 `axi.json.example`):
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"mcpServers": {
|
|
42
|
-
"jina": {
|
|
43
|
-
"command": "npx",
|
|
44
|
-
"args": ["jina-mcp-tools"],
|
|
45
|
-
"env": { "JINA_API_KEY": "your-key" }
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
"nativeTools": [
|
|
49
|
-
{"module": "my_project.tools"},
|
|
50
|
-
{"module": "./scripts/tools.py", "name": "scripts"}
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
- **mcpServers** — MCP server 配置,key 名作为工具命名空间
|
|
56
|
-
- **nativeTools** — 原生 Python 工具,`module` 支持文件路径和模块路径,`name` 可选(省略则自动推导)
|
|
57
|
-
|
|
58
|
-
## CLI 命令
|
|
59
|
-
|
|
60
|
-
| 命令 | 说明 |
|
|
61
|
-
|------|------|
|
|
62
|
-
| `axi list [server]` | 列出所有 server 及工具 |
|
|
63
|
-
| `axi search <query>` | 混合搜索工具(BM25 + Embedding,支持 `--top-k`) |
|
|
64
|
-
| `axi grep <pattern>` | 正则表达式搜索工具(支持 `--top-k`) |
|
|
65
|
-
| `axi describe <tool>` | 查看工具完整 schema |
|
|
66
|
-
| `axi run <tool> --key value` | 执行工具(也支持 `-j '{...}'` 传 JSON) |
|
|
67
|
-
| `axi daemon start\|status\|stop` | 管理 daemon 进程 |
|
|
68
|
-
|
|
69
|
-
所有输出统一为紧凑 JSON。`axi run` 返回统一信封:
|
|
70
|
-
|
|
71
|
-
```json
|
|
72
|
-
{"status": "success", "data": "..."}
|
|
73
|
-
{"status": "error", "error": "错误信息"}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## 注册原生工具
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
from axi import tool
|
|
80
|
-
|
|
81
|
-
@tool(name="query_orders", description="按区域查询订单")
|
|
82
|
-
def query_orders(region: str, limit: int = 10) -> dict:
|
|
83
|
-
return {"orders": [...], "total": 42}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
装饰器自动从函数签名提取参数 schema。注册后同时获得 CLI 调用和 PTC 调用能力。
|
|
87
|
-
|
|
88
|
-
## PTC(Programmatic Tool Calling)
|
|
89
|
-
|
|
90
|
-
Agent 可以写 Python 代码批量调用工具,在本地做数据过滤和聚合,避免反复 LLM round-trip:
|
|
91
|
-
|
|
92
|
-
```python
|
|
93
|
-
from axi import tool
|
|
94
|
-
|
|
95
|
-
search = tool("jina/jina_search")
|
|
96
|
-
results = search(query="python async", count=5)
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Daemon 模式
|
|
100
|
-
|
|
101
|
-
MCP 工具通过后台 daemon 长连接执行,支持有状态 MCP server(如 browser MCP)。执行 `axi search/describe/run` 时自动启动 daemon,无需手动管理。
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
axi CLI ──(Unix socket)──> daemon ──(stdio)──> MCP server A
|
|
105
|
-
──(stdio)──> MCP server B
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## 技术栈
|
|
109
|
-
|
|
110
|
-
Python 3.12+ / [Typer](https://typer.tiangolo.com/) / [Pydantic](https://docs.pydantic.dev/) / [MCP SDK](https://github.com/modelcontextprotocol/python-sdk)
|
|
111
|
-
|
|
112
|
-
## License
|
|
113
|
-
|
|
114
|
-
MIT
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"""原生工具注册 Provider:通过 @tool 装饰器注册 Python 函数。"""
|
|
2
|
-
|
|
3
|
-
import inspect
|
|
4
|
-
from typing import Any, Callable, get_type_hints
|
|
5
|
-
|
|
6
|
-
from pydantic import create_model
|
|
7
|
-
|
|
8
|
-
from axi.models import ToolMeta, ToolSource
|
|
9
|
-
|
|
10
|
-
# 全局注册表,存储原生工具的函数引用
|
|
11
|
-
_native_functions: dict[str, Callable] = {}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _extract_input_schema(func: Callable) -> dict[str, Any]:
|
|
15
|
-
"""从函数签名和 type hints 提取 JSON Schema。
|
|
16
|
-
|
|
17
|
-
利用 Pydantic 的 create_model 动态构建模型,
|
|
18
|
-
自动支持 Literal、Optional、list[T]、嵌套 BaseModel、Annotated[..., Field()] 等。
|
|
19
|
-
"""
|
|
20
|
-
hints = get_type_hints(func, include_extras=True)
|
|
21
|
-
sig = inspect.signature(func)
|
|
22
|
-
fields: dict[str, Any] = {}
|
|
23
|
-
|
|
24
|
-
for name, param in sig.parameters.items():
|
|
25
|
-
hint = hints.get(name, Any)
|
|
26
|
-
if param.default is inspect.Parameter.empty:
|
|
27
|
-
fields[name] = (hint, ...)
|
|
28
|
-
else:
|
|
29
|
-
fields[name] = (hint, param.default)
|
|
30
|
-
|
|
31
|
-
model = create_model(func.__name__, **fields)
|
|
32
|
-
schema = model.model_json_schema()
|
|
33
|
-
|
|
34
|
-
# 移除 Pydantic 自动添加的 title 字段,保持输出紧凑
|
|
35
|
-
schema.pop("title", None)
|
|
36
|
-
for prop in schema.get("properties", {}).values():
|
|
37
|
-
prop.pop("title", None)
|
|
38
|
-
|
|
39
|
-
return schema
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def register_tool(
|
|
43
|
-
func: Callable,
|
|
44
|
-
name: str | None = None,
|
|
45
|
-
description: str | None = None,
|
|
46
|
-
output_example: Any | None = None,
|
|
47
|
-
) -> ToolMeta:
|
|
48
|
-
"""注册一个原生 Python 函数为 axi 工具。"""
|
|
49
|
-
tool_name = name or func.__name__
|
|
50
|
-
tool_desc = description or func.__doc__ or ""
|
|
51
|
-
|
|
52
|
-
meta = ToolMeta(
|
|
53
|
-
name=tool_name,
|
|
54
|
-
description=tool_desc,
|
|
55
|
-
input_schema=_extract_input_schema(func),
|
|
56
|
-
output_example=output_example,
|
|
57
|
-
source=ToolSource.NATIVE,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
_native_functions[tool_name] = func
|
|
61
|
-
return meta
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_native_function(name: str) -> Callable | None:
|
|
65
|
-
"""获取已注册的原生函数。"""
|
|
66
|
-
return _native_functions.get(name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|