sdpy-kit 0.7.5__tar.gz → 0.8.1__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.
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/PKG-INFO +48 -1
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/README.md +41 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/__init__.py +1 -1
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/__init__.py +22 -1
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/_internal/failover.py +52 -2
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/_internal/rerank_transport.py +2 -2
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/_internal/sdk_client.py +90 -10
- sdpy_kit-0.7.5/kit/ai/_internal/_service_base.py → sdpy_kit-0.8.1/kit/ai/_internal/service_base.py +1 -1
- {sdpy_kit-0.7.5/kit/ai → sdpy_kit-0.8.1/kit/ai/_internal}/types.py +1 -1
- sdpy_kit-0.8.1/kit/ai/agent/__init__.py +127 -0
- sdpy_kit-0.8.1/kit/ai/agent/approval.py +66 -0
- sdpy_kit-0.8.1/kit/ai/agent/errors.py +119 -0
- sdpy_kit-0.8.1/kit/ai/agent/loop.py +631 -0
- sdpy_kit-0.8.1/kit/ai/agent/mcp.py +268 -0
- sdpy_kit-0.8.1/kit/ai/agent/registry.py +199 -0
- sdpy_kit-0.8.1/kit/ai/agent/session.py +150 -0
- sdpy_kit-0.8.1/kit/ai/agent/skill.py +367 -0
- sdpy_kit-0.8.1/kit/ai/agent/types.py +157 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/chat_service.py +109 -4
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/embedding_service.py +1 -9
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/image_service.py +1 -18
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/messages.py +16 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/model_client.py +1 -1
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/rerank_service.py +2 -2
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/pyproject.toml +11 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/.gitignore +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/_internal/__init__.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/_internal/pool.py +0 -0
- /sdpy_kit-0.7.5/kit/ai/_internal/_retry_after.py → /sdpy_kit-0.8.1/kit/ai/_internal/retry_after.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/decision/__init__.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/decision/decima.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/decision/laya.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/common_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/crawl/__init__.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/crawl/article_io.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/crawl/article_models.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/crawl/deepseek_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/crawl/web_article_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/crawl/wx_article_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/audit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/base_db_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/crud.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/engine.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/pg_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/query.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/session.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/sqlite_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/docs/__init__.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/docs/anydoc_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/docs/docx_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/docs/marker_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/docs/md2doc_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/docs/mineru_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/docs/rc_docparser_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/file_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/license_kit.py +0 -0
- {sdpy_kit-0.7.5 → sdpy_kit-0.8.1}/kit/redis_kit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,6 +22,12 @@ Requires-Dist: pydantic-settings>=2.16.1
|
|
|
22
22
|
Requires-Dist: pydantic>=2.14.1
|
|
23
23
|
Requires-Dist: sqlalchemy~=2.0.54
|
|
24
24
|
Requires-Dist: sqlmodel>=0.0.48
|
|
25
|
+
Provides-Extra: agent
|
|
26
|
+
Requires-Dist: fastmcp-slim[client]<5,>=4.1.7; extra == 'agent'
|
|
27
|
+
Requires-Dist: httpx>=0.28.1; extra == 'agent'
|
|
28
|
+
Requires-Dist: openai<4,>=3.44; extra == 'agent'
|
|
29
|
+
Requires-Dist: python-dotenv>=1.2.4; extra == 'agent'
|
|
30
|
+
Requires-Dist: pyyaml>=6.0.3; extra == 'agent'
|
|
25
31
|
Provides-Extra: ai
|
|
26
32
|
Requires-Dist: httpx>=0.28.1; extra == 'ai'
|
|
27
33
|
Requires-Dist: openai<4,>=3.44; extra == 'ai'
|
|
@@ -97,6 +103,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
97
103
|
| `kit.config` | `get_settings` / `get_default_logger` | 配置单例与统一日志 | 无 |
|
|
98
104
|
| `kit.db` | `SQLiteKit` / `PgKit` / `MySqlKit` / `CRUDBase` | 统一 CRUD、原生 SQL、原生表操作、事务、表管理 | `pg` / `mysql` / `sqliteai` |
|
|
99
105
|
| `kit.ai` | `AI`(同步门面)/ 4 个原生异步 Service | 对话 / 向量化 / 重排序 / 文生图 | `ai` |
|
|
106
|
+
| `kit.ai.agent` | `Agent` / `ToolRegistry` / `SkillRegistry` / `McpServerConfig` / `Session` | MCP client(HTTP / stdio)+ Skill 包加载 + 模型驱动工具循环 | `agent` |
|
|
100
107
|
| `kit.redis_kit` | `RedisKit` | Redis string / hash / list / set / zset | `redis` |
|
|
101
108
|
| `kit.file_kit` | `FileKit` | 文件与目录读写、移动、遍历 | 无 |
|
|
102
109
|
| `kit.common_kit` | `CommonKit` | 雪花 ID、时间戳 ID、时间格式化 | 无 |
|
|
@@ -140,8 +147,48 @@ uv add sdpy-kit
|
|
|
140
147
|
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / multi_choice / score / noul;支持多字段 / 批量 / 长文本) | `pip install "sdpy-kit[laya]"` |
|
|
141
148
|
| `decima` | DecimaKit 决策模型(decima-ai + onnxruntime,**无 torch**;选项乱序不变、置信度可门控;磁盘约 137 MB) | `pip install "sdpy-kit[decima]"` |
|
|
142
149
|
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
150
|
+
| `agent` | Agent 能力:MCP client(fastmcp-slim)+ Skill 加载 + 工具循环(含 ai 三项依赖与 pyyaml) | `pip install "sdpy-kit[agent]"` |
|
|
143
151
|
| `recommended` | 常用能力集合(DB / 缓存 / AI / 文档 / 抓取);不含重依赖(laya / decima)与 Marker(0.7.0 起暂停发布) | `pip install "sdpy-kit[recommended]"` |
|
|
144
152
|
|
|
153
|
+
## Agent 快速开始(extra `agent`)
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import asyncio
|
|
157
|
+
|
|
158
|
+
from kit.ai.agent import Agent, McpServerConfig, SkillRegistry
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def main() -> None:
|
|
162
|
+
skills = SkillRegistry()
|
|
163
|
+
skills.load("./skills") # 一级子目录含 SKILL.md 即注册
|
|
164
|
+
|
|
165
|
+
agent = Agent(
|
|
166
|
+
skills=skills,
|
|
167
|
+
servers=[
|
|
168
|
+
McpServerConfig(
|
|
169
|
+
name="filesystem",
|
|
170
|
+
transport="stdio",
|
|
171
|
+
command="npx",
|
|
172
|
+
args=["-y", "@modelcontextprotocol/server-filesystem", "./data"],
|
|
173
|
+
)
|
|
174
|
+
],
|
|
175
|
+
system_prompt="你是一个谨慎的文档助手。",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
async with agent: # 建连一次,可多次 run()
|
|
179
|
+
result = await agent.run("总结 data 目录里的文件")
|
|
180
|
+
print(result.text)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
asyncio.run(main())
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
- 模型档案沿用 `kit.ai`(`config/ai_models.json`),首轮可 failover,成功后锁定档案;
|
|
187
|
+
- Skill 只读工具:`read_skill` / `list_skill_files` / `read_skill_file`(路径越界拒绝);
|
|
188
|
+
- MCP 工具默认放行,skill 脚本默认拒绝;可用 `DenyAllApproval` 整体收紧;
|
|
189
|
+
- `timeout` / `max_turns` / `tool_timeout` 控制总预算与轮次;
|
|
190
|
+
- 完整说明见 `docs/agents/modules/agent-kit.md`(仓库内)。
|
|
191
|
+
|
|
145
192
|
## 配置(.env)
|
|
146
193
|
|
|
147
194
|
kit 通过 pydantic-settings 读取项目根目录 `.env`;**同名真实环境变量优先于 `.env`**。全部变量都有默认值,按需覆盖即可。
|
|
@@ -15,6 +15,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
15
15
|
| `kit.config` | `get_settings` / `get_default_logger` | 配置单例与统一日志 | 无 |
|
|
16
16
|
| `kit.db` | `SQLiteKit` / `PgKit` / `MySqlKit` / `CRUDBase` | 统一 CRUD、原生 SQL、原生表操作、事务、表管理 | `pg` / `mysql` / `sqliteai` |
|
|
17
17
|
| `kit.ai` | `AI`(同步门面)/ 4 个原生异步 Service | 对话 / 向量化 / 重排序 / 文生图 | `ai` |
|
|
18
|
+
| `kit.ai.agent` | `Agent` / `ToolRegistry` / `SkillRegistry` / `McpServerConfig` / `Session` | MCP client(HTTP / stdio)+ Skill 包加载 + 模型驱动工具循环 | `agent` |
|
|
18
19
|
| `kit.redis_kit` | `RedisKit` | Redis string / hash / list / set / zset | `redis` |
|
|
19
20
|
| `kit.file_kit` | `FileKit` | 文件与目录读写、移动、遍历 | 无 |
|
|
20
21
|
| `kit.common_kit` | `CommonKit` | 雪花 ID、时间戳 ID、时间格式化 | 无 |
|
|
@@ -58,8 +59,48 @@ uv add sdpy-kit
|
|
|
58
59
|
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / multi_choice / score / noul;支持多字段 / 批量 / 长文本) | `pip install "sdpy-kit[laya]"` |
|
|
59
60
|
| `decima` | DecimaKit 决策模型(decima-ai + onnxruntime,**无 torch**;选项乱序不变、置信度可门控;磁盘约 137 MB) | `pip install "sdpy-kit[decima]"` |
|
|
60
61
|
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
62
|
+
| `agent` | Agent 能力:MCP client(fastmcp-slim)+ Skill 加载 + 工具循环(含 ai 三项依赖与 pyyaml) | `pip install "sdpy-kit[agent]"` |
|
|
61
63
|
| `recommended` | 常用能力集合(DB / 缓存 / AI / 文档 / 抓取);不含重依赖(laya / decima)与 Marker(0.7.0 起暂停发布) | `pip install "sdpy-kit[recommended]"` |
|
|
62
64
|
|
|
65
|
+
## Agent 快速开始(extra `agent`)
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import asyncio
|
|
69
|
+
|
|
70
|
+
from kit.ai.agent import Agent, McpServerConfig, SkillRegistry
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def main() -> None:
|
|
74
|
+
skills = SkillRegistry()
|
|
75
|
+
skills.load("./skills") # 一级子目录含 SKILL.md 即注册
|
|
76
|
+
|
|
77
|
+
agent = Agent(
|
|
78
|
+
skills=skills,
|
|
79
|
+
servers=[
|
|
80
|
+
McpServerConfig(
|
|
81
|
+
name="filesystem",
|
|
82
|
+
transport="stdio",
|
|
83
|
+
command="npx",
|
|
84
|
+
args=["-y", "@modelcontextprotocol/server-filesystem", "./data"],
|
|
85
|
+
)
|
|
86
|
+
],
|
|
87
|
+
system_prompt="你是一个谨慎的文档助手。",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async with agent: # 建连一次,可多次 run()
|
|
91
|
+
result = await agent.run("总结 data 目录里的文件")
|
|
92
|
+
print(result.text)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- 模型档案沿用 `kit.ai`(`config/ai_models.json`),首轮可 failover,成功后锁定档案;
|
|
99
|
+
- Skill 只读工具:`read_skill` / `list_skill_files` / `read_skill_file`(路径越界拒绝);
|
|
100
|
+
- MCP 工具默认放行,skill 脚本默认拒绝;可用 `DenyAllApproval` 整体收紧;
|
|
101
|
+
- `timeout` / `max_turns` / `tool_timeout` 控制总预算与轮次;
|
|
102
|
+
- 完整说明见 `docs/agents/modules/agent-kit.md`(仓库内)。
|
|
103
|
+
|
|
63
104
|
## 配置(.env)
|
|
64
105
|
|
|
65
106
|
kit 通过 pydantic-settings 读取项目根目录 `.env`;**同名真实环境变量优先于 `.env`**。全部变量都有默认值,按需覆盖即可。
|
|
@@ -32,6 +32,9 @@ Author: Clark Chang
|
|
|
32
32
|
同步门面与决策引擎:
|
|
33
33
|
- AI / run_sync / reload_config / shutdown / ReloadResult
|
|
34
34
|
- DecimaKit / LayaKit(extra ``decima`` / ``laya``,不进 ``__all__``)
|
|
35
|
+
|
|
36
|
+
Agent(extra ``agent``,不进 ``__all__``):
|
|
37
|
+
- Agent / ToolRegistry / Session / McpServerConfig
|
|
35
38
|
"""
|
|
36
39
|
|
|
37
40
|
from importlib import import_module
|
|
@@ -73,6 +76,12 @@ _EXPORTS: dict[str, str] = {
|
|
|
73
76
|
"DecimaError": "kit.ai.decision",
|
|
74
77
|
"LayaKit": "kit.ai.decision",
|
|
75
78
|
"LayaError": "kit.ai.decision",
|
|
79
|
+
# Agent 能力(extra ``agent``)只支持显式导入,不进 __all__:
|
|
80
|
+
# ``from kit.ai import *`` 不应在基础安装下触发 fastmcp。
|
|
81
|
+
"Agent": "kit.ai.agent",
|
|
82
|
+
"ToolRegistry": "kit.ai.agent",
|
|
83
|
+
"Session": "kit.ai.agent",
|
|
84
|
+
"McpServerConfig": "kit.ai.agent",
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
|
|
@@ -86,4 +95,16 @@ def __getattr__(name: str) -> Any:
|
|
|
86
95
|
|
|
87
96
|
|
|
88
97
|
def __dir__() -> list[str]:
|
|
89
|
-
return sorted(
|
|
98
|
+
return sorted(
|
|
99
|
+
set(__all__)
|
|
100
|
+
| {
|
|
101
|
+
"DecimaError",
|
|
102
|
+
"DecimaKit",
|
|
103
|
+
"LayaError",
|
|
104
|
+
"LayaKit",
|
|
105
|
+
"Agent",
|
|
106
|
+
"ToolRegistry",
|
|
107
|
+
"Session",
|
|
108
|
+
"McpServerConfig",
|
|
109
|
+
}
|
|
110
|
+
)
|
|
@@ -34,14 +34,22 @@ Author: Clark Chang
|
|
|
34
34
|
|
|
35
35
|
import asyncio
|
|
36
36
|
import time
|
|
37
|
-
from collections.abc import
|
|
37
|
+
from collections.abc import (
|
|
38
|
+
AsyncGenerator,
|
|
39
|
+
AsyncIterator,
|
|
40
|
+
Awaitable,
|
|
41
|
+
Callable,
|
|
42
|
+
Mapping,
|
|
43
|
+
Sequence,
|
|
44
|
+
)
|
|
38
45
|
from typing import Any, Final, TypeVar, cast
|
|
39
46
|
|
|
40
47
|
from kit.ai._internal.pool import AIServicePool
|
|
41
|
-
from kit.ai.chat_service import ChatService, StreamStats
|
|
48
|
+
from kit.ai.chat_service import ChatService, StreamStats, ToolSpecLike
|
|
42
49
|
from kit.ai.embedding_service import EmbeddingService
|
|
43
50
|
from kit.ai.exceptions import AIAggregateError, AIError, AIRequestError, AITimeoutError
|
|
44
51
|
from kit.ai.image_service import ImageResult, ImageService
|
|
52
|
+
from kit.ai.messages import ChatReply
|
|
45
53
|
from kit.ai.model_builder import Service
|
|
46
54
|
from kit.ai.rerank_service import RerankHit, RerankService
|
|
47
55
|
|
|
@@ -68,6 +76,13 @@ class _FailoverBase:
|
|
|
68
76
|
def __init__(self, pool: AIServicePool, model_type: str) -> None:
|
|
69
77
|
self._pool: AIServicePool = pool
|
|
70
78
|
self._model_type: str = model_type
|
|
79
|
+
# 最近一次成功的档案名(工具循环据此锁定档案,避免中途换模型)
|
|
80
|
+
self._last_profile: str | None = None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def last_profile(self) -> str | None:
|
|
84
|
+
"""最近一次成功调用使用的档案名;从未成功时为 None。"""
|
|
85
|
+
return self._last_profile
|
|
71
86
|
|
|
72
87
|
def _aggregate_error(
|
|
73
88
|
self,
|
|
@@ -146,6 +161,7 @@ class _FailoverBase:
|
|
|
146
161
|
)
|
|
147
162
|
continue
|
|
148
163
|
self._pool.mark_success(self._model_type, name)
|
|
164
|
+
self._last_profile = name
|
|
149
165
|
return result
|
|
150
166
|
raise self._aggregate_error(
|
|
151
167
|
label, tried, errors, budget_exhausted=budget_exhausted
|
|
@@ -194,6 +210,40 @@ class FailoverChatService(_FailoverBase):
|
|
|
194
210
|
|
|
195
211
|
return await self._attempt("对话", _run, deadline=deadline)
|
|
196
212
|
|
|
213
|
+
async def create_with_tools(
|
|
214
|
+
self,
|
|
215
|
+
messages: list[dict[str, Any]],
|
|
216
|
+
tools: Sequence[ToolSpecLike | Mapping[str, Any]],
|
|
217
|
+
*,
|
|
218
|
+
tool_choice: str | dict[str, Any] | None = None,
|
|
219
|
+
temperature: float | None = None,
|
|
220
|
+
max_tokens: int | None = None,
|
|
221
|
+
thinking: bool | None = None,
|
|
222
|
+
stats: StreamStats | None = None,
|
|
223
|
+
deadline: float | None = None,
|
|
224
|
+
) -> ChatReply:
|
|
225
|
+
"""带工具定义的对话(链式故障切换)。
|
|
226
|
+
|
|
227
|
+
仅第 1 轮使用本方法:成功后调用方可读 :attr:`last_profile` 锁定档案,
|
|
228
|
+
后续轮次改用单档案 ``ChatService``,不再切换候选。
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
async def _run(service: Service) -> ChatReply:
|
|
232
|
+
if stats is not None:
|
|
233
|
+
stats.reset()
|
|
234
|
+
return await cast(ChatService, service).create_with_tools(
|
|
235
|
+
messages,
|
|
236
|
+
tools,
|
|
237
|
+
tool_choice=tool_choice,
|
|
238
|
+
temperature=temperature,
|
|
239
|
+
max_tokens=max_tokens,
|
|
240
|
+
thinking=thinking,
|
|
241
|
+
stats=stats,
|
|
242
|
+
deadline=deadline,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return await self._attempt("工具对话", _run, deadline=deadline)
|
|
246
|
+
|
|
197
247
|
async def create_once(
|
|
198
248
|
self,
|
|
199
249
|
prompt: str,
|
|
@@ -37,10 +37,10 @@ except ImportError as e:
|
|
|
37
37
|
"kit.ai 需要可选依赖,请安装:pip install sdpy-kit[ai]"
|
|
38
38
|
) from e
|
|
39
39
|
|
|
40
|
-
from kit.ai._internal.
|
|
40
|
+
from kit.ai._internal.retry_after import MIN_RETRY_BUDGET, parse_retry_after
|
|
41
|
+
from kit.ai._internal.types import JSONObject
|
|
41
42
|
from kit.ai.exceptions import AIRequestError, AIResponseError, AITimeoutError
|
|
42
43
|
from kit.ai.profiles import ModelProfile
|
|
43
|
-
from kit.ai.types import JSONObject
|
|
44
44
|
|
|
45
45
|
__all__ = ["RerankTransport"]
|
|
46
46
|
|
|
@@ -46,16 +46,17 @@ except ImportError as e:
|
|
|
46
46
|
"kit.ai 需要可选依赖,请安装:pip install sdpy-kit[ai]"
|
|
47
47
|
) from e
|
|
48
48
|
|
|
49
|
-
from kit.ai._internal.
|
|
50
|
-
from kit.ai._internal.
|
|
49
|
+
from kit.ai._internal.retry_after import MIN_RETRY_BUDGET, parse_retry_after
|
|
50
|
+
from kit.ai._internal.service_base import LoggingServiceBase
|
|
51
|
+
from kit.ai._internal.types import JSONObject
|
|
51
52
|
from kit.ai.exceptions import (
|
|
52
53
|
AIError,
|
|
53
54
|
AIRequestError,
|
|
54
55
|
AIResponseError,
|
|
55
56
|
AITimeoutError,
|
|
56
57
|
)
|
|
58
|
+
from kit.ai.messages import ChatReply, ToolCall
|
|
57
59
|
from kit.ai.profiles import ModelProfile, resolve_max_tokens
|
|
58
|
-
from kit.ai.types import JSONObject
|
|
59
60
|
from kit.config.log_config import get_default_logger
|
|
60
61
|
|
|
61
62
|
__all__ = ["SdkClient", "SdkServiceBase"]
|
|
@@ -101,6 +102,41 @@ def _reasoning_of(holder: Any) -> str:
|
|
|
101
102
|
return ""
|
|
102
103
|
|
|
103
104
|
|
|
105
|
+
def _parse_tool_calls(message: Any) -> list[ToolCall]:
|
|
106
|
+
"""把 SDK 的 ``message.tool_calls`` 收敛为 kit 的 ``ToolCall`` 列表。
|
|
107
|
+
|
|
108
|
+
``arguments`` 在 OpenAI 协议里是 JSON 字符串;部分兼容端点回 dict,
|
|
109
|
+
统一序列化为字符串(``ToolCall`` 的契约就是字符串)。
|
|
110
|
+
"""
|
|
111
|
+
raw = getattr(message, "tool_calls", None)
|
|
112
|
+
if raw is None:
|
|
113
|
+
raw = _extra_of(message, "tool_calls")
|
|
114
|
+
if not raw:
|
|
115
|
+
return []
|
|
116
|
+
if not isinstance(raw, list):
|
|
117
|
+
raise AIResponseError("tool_calls 类型异常")
|
|
118
|
+
calls: list[ToolCall] = []
|
|
119
|
+
for index, item in enumerate(raw):
|
|
120
|
+
function = getattr(item, "function", None)
|
|
121
|
+
name = getattr(function, "name", None)
|
|
122
|
+
arguments = getattr(function, "arguments", None)
|
|
123
|
+
call_id = getattr(item, "id", None)
|
|
124
|
+
if not isinstance(name, str) or not name:
|
|
125
|
+
raise AIResponseError(f"tool_calls[{index}] 缺少 function.name")
|
|
126
|
+
if arguments is None:
|
|
127
|
+
arguments = "{}"
|
|
128
|
+
elif not isinstance(arguments, str):
|
|
129
|
+
arguments = json.dumps(arguments, ensure_ascii=False)
|
|
130
|
+
calls.append(
|
|
131
|
+
ToolCall(
|
|
132
|
+
id=str(call_id or f"call_{index}"),
|
|
133
|
+
type="function",
|
|
134
|
+
function={"name": name, "arguments": arguments},
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
return calls
|
|
138
|
+
|
|
139
|
+
|
|
104
140
|
class SdkClient:
|
|
105
141
|
"""openai SDK 客户端(全异步,单实例复用)。
|
|
106
142
|
|
|
@@ -181,11 +217,13 @@ class SdkClient:
|
|
|
181
217
|
|
|
182
218
|
def _chat_params(
|
|
183
219
|
self,
|
|
184
|
-
messages: list[dict[str,
|
|
220
|
+
messages: list[dict[str, Any]],
|
|
185
221
|
extra_body: JSONObject | None,
|
|
186
222
|
response_format: JSONObject | None,
|
|
187
223
|
temperature: float | None,
|
|
188
224
|
max_tokens: int | None,
|
|
225
|
+
tools: list[dict[str, Any]] | None = None,
|
|
226
|
+
tool_choice: str | dict[str, Any] | None = None,
|
|
189
227
|
) -> dict[str, Any]:
|
|
190
228
|
params: dict[str, Any] = {
|
|
191
229
|
"model": self._profile.model,
|
|
@@ -198,23 +236,39 @@ class SdkClient:
|
|
|
198
236
|
params["max_tokens"] = effective_max_tokens
|
|
199
237
|
if response_format is not None:
|
|
200
238
|
params["response_format"] = response_format
|
|
239
|
+
if tools:
|
|
240
|
+
params["tools"] = tools
|
|
241
|
+
if tool_choice is not None:
|
|
242
|
+
params["tool_choice"] = tool_choice
|
|
201
243
|
if extra_body:
|
|
202
244
|
params["extra_body"] = extra_body
|
|
203
245
|
return params
|
|
204
246
|
|
|
205
|
-
async def
|
|
247
|
+
async def chat_raw(
|
|
206
248
|
self,
|
|
207
249
|
*,
|
|
208
|
-
messages: list[dict[str,
|
|
250
|
+
messages: list[dict[str, Any]],
|
|
251
|
+
tools: list[dict[str, Any]] | None = None,
|
|
252
|
+
tool_choice: str | dict[str, Any] | None = None,
|
|
209
253
|
extra_body: JSONObject | None = None,
|
|
210
254
|
response_format: JSONObject | None = None,
|
|
211
255
|
temperature: float | None = None,
|
|
212
256
|
max_tokens: int | None = None,
|
|
213
257
|
deadline: float | None = None,
|
|
214
|
-
) ->
|
|
215
|
-
"""
|
|
258
|
+
) -> ChatReply:
|
|
259
|
+
"""非流式对话,返回完整回复(含 ``tool_calls``)。
|
|
260
|
+
|
|
261
|
+
``tools`` 必须是已包装好的 OpenAI 结构(``type=function`` +
|
|
262
|
+
``function.{name,description,parameters}``),字段改名由服务层完成。
|
|
263
|
+
"""
|
|
216
264
|
params = self._chat_params(
|
|
217
|
-
messages,
|
|
265
|
+
messages,
|
|
266
|
+
extra_body,
|
|
267
|
+
response_format,
|
|
268
|
+
temperature,
|
|
269
|
+
max_tokens,
|
|
270
|
+
tools=tools,
|
|
271
|
+
tool_choice=tool_choice,
|
|
218
272
|
)
|
|
219
273
|
resp = await self._request(
|
|
220
274
|
lambda: self._client.chat.completions.create(
|
|
@@ -233,7 +287,33 @@ class SdkClient:
|
|
|
233
287
|
if content is not None and not isinstance(content, str):
|
|
234
288
|
raise AIResponseError(f"content 类型异常: {type(content).__name__}")
|
|
235
289
|
finish = getattr(choice, "finish_reason", None)
|
|
236
|
-
return
|
|
290
|
+
return ChatReply(
|
|
291
|
+
content=content,
|
|
292
|
+
reasoning=_reasoning_of(message),
|
|
293
|
+
finish_reason=finish if isinstance(finish, str) else None,
|
|
294
|
+
tool_calls=_parse_tool_calls(message),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
async def chat(
|
|
298
|
+
self,
|
|
299
|
+
*,
|
|
300
|
+
messages: list[dict[str, Any]],
|
|
301
|
+
extra_body: JSONObject | None = None,
|
|
302
|
+
response_format: JSONObject | None = None,
|
|
303
|
+
temperature: float | None = None,
|
|
304
|
+
max_tokens: int | None = None,
|
|
305
|
+
deadline: float | None = None,
|
|
306
|
+
) -> tuple[str | None, str, str | None]:
|
|
307
|
+
"""非流式对话,返回 ``(正文, 推理文本, finish_reason)``。"""
|
|
308
|
+
reply = await self.chat_raw(
|
|
309
|
+
messages=messages,
|
|
310
|
+
extra_body=extra_body,
|
|
311
|
+
response_format=response_format,
|
|
312
|
+
temperature=temperature,
|
|
313
|
+
max_tokens=max_tokens,
|
|
314
|
+
deadline=deadline,
|
|
315
|
+
)
|
|
316
|
+
return reply.content, reply.reasoning, reply.finish_reason
|
|
237
317
|
|
|
238
318
|
async def chat_stream(
|
|
239
319
|
self,
|
sdpy_kit-0.7.5/kit/ai/_internal/_service_base.py → sdpy_kit-0.8.1/kit/ai/_internal/service_base.py
RENAMED
|
@@ -31,9 +31,9 @@ from collections.abc import Iterator
|
|
|
31
31
|
from contextlib import contextmanager
|
|
32
32
|
from typing import ClassVar
|
|
33
33
|
|
|
34
|
+
from kit.ai._internal.types import JSONValue
|
|
34
35
|
from kit.ai.exceptions import AIError, AIResponseError
|
|
35
36
|
from kit.ai.profiles import ModelProfile
|
|
36
|
-
from kit.ai.types import JSONValue
|
|
37
37
|
from kit.config.log_config import get_default_logger
|
|
38
38
|
|
|
39
39
|
__all__ = ["LoggingServiceBase", "safe_get"]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2026-11-10
|
|
21
|
+
"""kit.ai.agent - MCP + Skill + 工具循环(PEP 562 延迟导出)。
|
|
22
|
+
|
|
23
|
+
``import kit.ai.agent`` 本身不加载任何可选依赖;访问 ``Agent`` /
|
|
24
|
+
``McpConnection`` 时才检查 fastmcp 是否安装,缺依赖报错会提示
|
|
25
|
+
``pip install sdpy-kit[agent]``。
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from importlib import import_module, util
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"Agent",
|
|
33
|
+
"AgentConfigError",
|
|
34
|
+
"AgentEmptyReplyError",
|
|
35
|
+
"AgentError",
|
|
36
|
+
"AgentModelError",
|
|
37
|
+
"AgentResult",
|
|
38
|
+
"AgentTimeoutError",
|
|
39
|
+
"AgentTruncatedError",
|
|
40
|
+
"ApprovalDeniedError",
|
|
41
|
+
"ApprovalHook",
|
|
42
|
+
"ApprovalRequest",
|
|
43
|
+
"DefaultApproval",
|
|
44
|
+
"DenyAllApproval",
|
|
45
|
+
"DuplicateSkillError",
|
|
46
|
+
"MaxTurnsExceeded",
|
|
47
|
+
"McpConnection",
|
|
48
|
+
"McpConnectionError",
|
|
49
|
+
"McpServerConfig",
|
|
50
|
+
"SkillError",
|
|
51
|
+
"SkillNotFoundError",
|
|
52
|
+
"SkillPackage",
|
|
53
|
+
"SkillRegistry",
|
|
54
|
+
"SkillScript",
|
|
55
|
+
"SkillValidationError",
|
|
56
|
+
"Session",
|
|
57
|
+
"ToolCallRecord",
|
|
58
|
+
"ToolExecutionError",
|
|
59
|
+
"ToolNotFoundError",
|
|
60
|
+
"ToolRegistry",
|
|
61
|
+
"ToolResult",
|
|
62
|
+
"ToolSpec",
|
|
63
|
+
"load_servers",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
# 符号 → (模块, 模块内属性名);模块内属性名缺省与符号同名
|
|
67
|
+
_EXPORTS: dict[str, tuple[str, str]] = {
|
|
68
|
+
"Agent": ("kit.ai.agent.loop", "Agent"),
|
|
69
|
+
"AgentResult": ("kit.ai.agent.types", "AgentResult"),
|
|
70
|
+
"ToolSpec": ("kit.ai.agent.types", "ToolSpec"),
|
|
71
|
+
"ToolResult": ("kit.ai.agent.types", "ToolResult"),
|
|
72
|
+
"ToolCallRecord": ("kit.ai.agent.types", "ToolCallRecord"),
|
|
73
|
+
"McpServerConfig": ("kit.ai.agent.types", "McpServerConfig"),
|
|
74
|
+
"load_servers": ("kit.ai.agent.types", "load_servers"),
|
|
75
|
+
"ToolRegistry": ("kit.ai.agent.registry", "ToolRegistry"),
|
|
76
|
+
"Session": ("kit.ai.agent.session", "Session"),
|
|
77
|
+
"ApprovalHook": ("kit.ai.agent.approval", "ApprovalHook"),
|
|
78
|
+
"ApprovalRequest": ("kit.ai.agent.approval", "ApprovalRequest"),
|
|
79
|
+
"DefaultApproval": ("kit.ai.agent.approval", "DefaultApproval"),
|
|
80
|
+
"DenyAllApproval": ("kit.ai.agent.approval", "DenyAllApproval"),
|
|
81
|
+
"SkillPackage": ("kit.ai.agent.skill", "SkillPackage"),
|
|
82
|
+
"SkillScript": ("kit.ai.agent.skill", "SkillScript"),
|
|
83
|
+
"SkillRegistry": ("kit.ai.agent.skill", "SkillRegistry"),
|
|
84
|
+
"McpConnection": ("kit.ai.agent.mcp", "McpConnection"),
|
|
85
|
+
"AgentError": ("kit.ai.agent.errors", "AgentError"),
|
|
86
|
+
"AgentConfigError": ("kit.ai.agent.errors", "AgentConfigError"),
|
|
87
|
+
"AgentModelError": ("kit.ai.agent.errors", "AgentModelError"),
|
|
88
|
+
"AgentTimeoutError": ("kit.ai.agent.errors", "AgentTimeoutError"),
|
|
89
|
+
"AgentEmptyReplyError": ("kit.ai.agent.errors", "AgentEmptyReplyError"),
|
|
90
|
+
"AgentTruncatedError": ("kit.ai.agent.errors", "AgentTruncatedError"),
|
|
91
|
+
"MaxTurnsExceeded": ("kit.ai.agent.errors", "MaxTurnsExceeded"),
|
|
92
|
+
"ApprovalDeniedError": ("kit.ai.agent.errors", "ApprovalDeniedError"),
|
|
93
|
+
"ToolNotFoundError": ("kit.ai.agent.errors", "ToolNotFoundError"),
|
|
94
|
+
"ToolExecutionError": ("kit.ai.agent.errors", "ToolExecutionError"),
|
|
95
|
+
"McpConnectionError": ("kit.ai.agent.errors", "McpConnectionError"),
|
|
96
|
+
"SkillError": ("kit.ai.agent.errors", "SkillError"),
|
|
97
|
+
"SkillValidationError": ("kit.ai.agent.errors", "SkillValidationError"),
|
|
98
|
+
"SkillNotFoundError": ("kit.ai.agent.errors", "SkillNotFoundError"),
|
|
99
|
+
"DuplicateSkillError": ("kit.ai.agent.errors", "DuplicateSkillError"),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# 需要 fastmcp 的符号:在导入前显式守卫,报错必须指向 [agent]
|
|
103
|
+
_GUARDED = frozenset({"Agent", "McpConnection"})
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _require_fastmcp(name: str) -> None:
|
|
107
|
+
if util.find_spec("fastmcp") is not None:
|
|
108
|
+
return
|
|
109
|
+
raise ImportError(
|
|
110
|
+
f"kit.ai.agent.{name} 需要可选依赖,请安装:pip install sdpy-kit[agent]"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def __getattr__(name: str) -> Any:
|
|
115
|
+
target = _EXPORTS.get(name)
|
|
116
|
+
if target is None:
|
|
117
|
+
raise AttributeError(f"module 'kit.ai.agent' has no attribute {name!r}")
|
|
118
|
+
if name in _GUARDED:
|
|
119
|
+
_require_fastmcp(name)
|
|
120
|
+
module, attribute = target
|
|
121
|
+
value = getattr(import_module(module), attribute)
|
|
122
|
+
globals()[name] = value
|
|
123
|
+
return value
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def __dir__() -> list[str]:
|
|
127
|
+
return sorted(__all__)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2026-11-10
|
|
21
|
+
"""工具调用审批 Hook。
|
|
22
|
+
|
|
23
|
+
默认策略:MCP 工具放行(用户显式配置 server 才引入,信任边界在配置层);
|
|
24
|
+
skill 脚本拒绝(``subprocess`` 执行任意代码,v1 不开放该工具)。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import Any, Literal, Protocol
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"ApprovalHook",
|
|
32
|
+
"ApprovalRequest",
|
|
33
|
+
"DefaultApproval",
|
|
34
|
+
"DenyAllApproval",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class ApprovalRequest:
|
|
40
|
+
"""一次待审批的工具调用。"""
|
|
41
|
+
|
|
42
|
+
kind: Literal["mcp_tool", "skill_script"]
|
|
43
|
+
target: str
|
|
44
|
+
arguments: dict[str, Any]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ApprovalHook(Protocol):
|
|
48
|
+
"""审批 Hook:返回 True 放行,False 拒绝。"""
|
|
49
|
+
|
|
50
|
+
async def __call__(self, request: ApprovalRequest) -> bool:
|
|
51
|
+
"""判断是否放行。"""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DefaultApproval:
|
|
56
|
+
"""默认审批:MCP 工具放行,skill 脚本拒绝。"""
|
|
57
|
+
|
|
58
|
+
async def __call__(self, request: ApprovalRequest) -> bool:
|
|
59
|
+
return request.kind == "mcp_tool"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class DenyAllApproval:
|
|
63
|
+
"""全部拒绝(最严格策略)。"""
|
|
64
|
+
|
|
65
|
+
async def __call__(self, request: ApprovalRequest) -> bool:
|
|
66
|
+
return False
|