sdpy-kit 0.1.0__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.
Files changed (41) hide show
  1. sdpy_kit-0.1.0/.gitignore +95 -0
  2. sdpy_kit-0.1.0/PKG-INFO +128 -0
  3. sdpy_kit-0.1.0/README.md +97 -0
  4. sdpy_kit-0.1.0/kit/__init__.py +68 -0
  5. sdpy_kit-0.1.0/kit/ai/__init__.py +47 -0
  6. sdpy_kit-0.1.0/kit/ai/base_service.py +94 -0
  7. sdpy_kit-0.1.0/kit/ai/chat_service.py +110 -0
  8. sdpy_kit-0.1.0/kit/ai/embedding_service.py +133 -0
  9. sdpy_kit-0.1.0/kit/ai/exceptions.py +57 -0
  10. sdpy_kit-0.1.0/kit/ai/failover.py +244 -0
  11. sdpy_kit-0.1.0/kit/ai/image_service.py +99 -0
  12. sdpy_kit-0.1.0/kit/ai/model_builder.py +119 -0
  13. sdpy_kit-0.1.0/kit/ai/model_client.py +214 -0
  14. sdpy_kit-0.1.0/kit/ai/pool.py +411 -0
  15. sdpy_kit-0.1.0/kit/ai/profiles.py +373 -0
  16. sdpy_kit-0.1.0/kit/ai/rerank_service.py +93 -0
  17. sdpy_kit-0.1.0/kit/ai/runner.py +580 -0
  18. sdpy_kit-0.1.0/kit/ai/types.py +28 -0
  19. sdpy_kit-0.1.0/kit/common_kit.py +221 -0
  20. sdpy_kit-0.1.0/kit/config/__init__.py +44 -0
  21. sdpy_kit-0.1.0/kit/config/kit_config.py +147 -0
  22. sdpy_kit-0.1.0/kit/config/log_config.py +134 -0
  23. sdpy_kit-0.1.0/kit/db/__init__.py +70 -0
  24. sdpy_kit-0.1.0/kit/db/base_db_kit.py +406 -0
  25. sdpy_kit-0.1.0/kit/db/crud.py +361 -0
  26. sdpy_kit-0.1.0/kit/db/engine.py +230 -0
  27. sdpy_kit-0.1.0/kit/db/exceptions.py +34 -0
  28. sdpy_kit-0.1.0/kit/db/models/__init__.py +33 -0
  29. sdpy_kit-0.1.0/kit/db/models/base.py +56 -0
  30. sdpy_kit-0.1.0/kit/db/mysql_kit.py +89 -0
  31. sdpy_kit-0.1.0/kit/db/pg_kit.py +87 -0
  32. sdpy_kit-0.1.0/kit/db/session.py +85 -0
  33. sdpy_kit-0.1.0/kit/db/sqlite_kit.py +284 -0
  34. sdpy_kit-0.1.0/kit/db/transaction.py +109 -0
  35. sdpy_kit-0.1.0/kit/doc_kit.py +1502 -0
  36. sdpy_kit-0.1.0/kit/file_kit.py +429 -0
  37. sdpy_kit-0.1.0/kit/mcp_kit.py +547 -0
  38. sdpy_kit-0.1.0/kit/mineru_kit.py +918 -0
  39. sdpy_kit-0.1.0/kit/redis_kit.py +392 -0
  40. sdpy_kit-0.1.0/kit/zvec_kit.py +671 -0
  41. sdpy_kit-0.1.0/pyproject.toml +49 -0
@@ -0,0 +1,95 @@
1
+ # 忽略匹配下列规则的Git 提交 V2.1.0
2
+ ### gradle ###
3
+ .gradle
4
+ /build/
5
+ !gradle/wrapper/gradle-wrapper.jar
6
+
7
+ ### STS ###
8
+ .settings/
9
+ .apt_generated
10
+ .classpath
11
+ .factorypath
12
+ .project
13
+ .settings
14
+ .springBeans
15
+ bin/
16
+
17
+ ### IntelliJ IDEA ###
18
+ .idea
19
+ *.iws
20
+ *.iml
21
+ *.ipr
22
+ *.lock
23
+ rebel.xml
24
+
25
+ ### NetBeans ###
26
+ nbproject/private/
27
+ build/
28
+ nbbuild/
29
+ nbdist/
30
+ .nb-gradle/
31
+
32
+ ### maven ###
33
+ target/
34
+ *.war
35
+ *.ear
36
+ *.zip
37
+ *.tar
38
+ *.tar.gz
39
+
40
+ ### logs ####
41
+ /logs/
42
+ *.log
43
+
44
+ ### temp ignore ###
45
+ *.cache
46
+ *.diff
47
+ *.patch
48
+ *.tmp
49
+ *.java~
50
+ *.properties~
51
+ *.xml~
52
+
53
+ ### system ignore ###
54
+ .DS_Store
55
+ Thumbs.db
56
+ Servers
57
+ .metadata
58
+ # 根目录 data/ 运行时数据(zvec 向量库、SQLite 测试库等,不入库)
59
+ data/
60
+ # AI 文生图 e2e 测试产物(生成的海报图片,不入库)
61
+ tests/image/
62
+ # AMiner 检索测试产物(md 报告,不入库)
63
+ tests/aminer/
64
+ # SQLite 测试库(运行时生成,不入库)
65
+ metadata_log.db
66
+ upload
67
+ gen_code
68
+
69
+ ### node ###
70
+ node_modules
71
+ __pycache__/
72
+ .vscode/
73
+ .venv/
74
+ .trae/
75
+ .codebuddy/
76
+ .qoder/
77
+
78
+ ### python dependencies ###
79
+ # 项目统一使用 uv + pyproject.toml 管理依赖,禁止 requirements.txt
80
+ requirements.txt
81
+ requirements-dev.txt
82
+ requirements*.txt
83
+
84
+ ### secrets ###
85
+ # 环境变量文件含 API 密钥等敏感信息,禁止入库
86
+ .env
87
+ .env.*
88
+ docs/current-task/
89
+ research/
90
+
91
+ ### sdpy-kit 发布工程 ###
92
+ # kit/ 为发布脚本从主工程同步的副本(构建时生成,不入库)
93
+ publish/sdpy-kit/kit/
94
+ publish/sdpy-kit/dist/
95
+ publish/sdpy-kit/*.egg-info/
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.5
2
+ Name: sdpy-kit
3
+ Version: 0.1.0
4
+ Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
5
+ Author-email: Clark <changhongyuan@126.com>
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Python: >=3.13
14
+ Requires-Dist: fastmcp>=4.0.3
15
+ Requires-Dist: firecrawl-anydoc>=0.2.4
16
+ Requires-Dist: httpx>=0.28.1
17
+ Requires-Dist: markdown-it-py>=3.0
18
+ Requires-Dist: pimd[pdfa]>=2.2.5
19
+ Requires-Dist: psycopg2-binary>=2.9.12
20
+ Requires-Dist: pydantic-settings>=2.0
21
+ Requires-Dist: pydantic>=2.0
22
+ Requires-Dist: pymysql>=1.2.0
23
+ Requires-Dist: python-dotenv>=1.2.3
24
+ Requires-Dist: redis>=5.0.0
25
+ Requires-Dist: requests>=2.34.0
26
+ Requires-Dist: sqlalchemy~=2.0.51
27
+ Requires-Dist: sqliteai-vector>=1.0.0
28
+ Requires-Dist: sqlmodel>=0.0.39
29
+ Requires-Dist: zvec>=0.6.0
30
+ Description-Content-Type: text/markdown
31
+
32
+ # sdpy-kit
33
+
34
+ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:统一数据库访问层(PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致)+ AI 多模型服务池(chat / embedding / rerank / image,多厂商候选链与故障转移)+ MCP 客户端 + Redis / 文件 / 文档 / 向量库 / MinerU 工具。
35
+
36
+ > 要求 Python 3.13+
37
+
38
+ ## 安装
39
+
40
+ ```bash
41
+ pip install sdpy-kit
42
+ # 或
43
+ uv add sdpy-kit
44
+ ```
45
+
46
+ ## 配置(.env)
47
+
48
+ kit 通过 pydantic-settings 读取项目根目录 `.env`(默认值见下,均可省略):
49
+
50
+ ```dotenv
51
+ # 日志
52
+ LOG_LEVEL=INFO
53
+ LOG_TO_CONSOLE=true
54
+ LOG_TO_FILE=false
55
+
56
+ # 数据库(三选一,切换只改 DEFAULT_DB_TYPE)
57
+ DEFAULT_DB_TYPE=sqlite # postgresql / mysql / sqlite
58
+ DB_HOST=localhost
59
+ DB_PORT=5432
60
+ DB_DATABASE=postgres
61
+ DB_USER=admin
62
+ DB_PASSWORD=
63
+ MYSQL_PORT=3306
64
+ MYSQL_DATABASE=mydb
65
+ MYSQL_USER=root
66
+ SQLITE_DB_PATH=./data/app.db
67
+
68
+ # Redis
69
+ REDIS_HOST=localhost
70
+ REDIS_PORT=6379
71
+ ```
72
+
73
+ ## AI / MCP 功能的档案文件
74
+
75
+ AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
76
+
77
+ - `config/ai_models.json` —— chat / embedding / rerank / image 四类型多厂商模型档案
78
+ - `config/mcp_servers.json` —— MCP 服务器档案
79
+
80
+ 文件缺失时,首次使用相应功能会报错,**报错信息中附带可直接复制的最小模板**;
81
+ 其中 `api_key` / `token_env` 字段填写环境变量名,真实密钥配置在 `.env`。
82
+
83
+ ## 快速开始
84
+
85
+ ### 日志与配置
86
+
87
+ ```python
88
+ from kit.config import get_settings, get_default_logger
89
+
90
+ settings = get_settings()
91
+ logger = get_default_logger(__name__)
92
+ logger.info("hello %s", settings.APP_NAME)
93
+ ```
94
+
95
+ ### 数据库(SQLiteKit / PgKit / MySqlKit API 完全一致,切换只改 import)
96
+
97
+ ```python
98
+ from sqlmodel import SQLModel, Field
99
+ from kit.db.sqlite_kit import SQLiteKit
100
+
101
+ class Item(SQLModel, table=True):
102
+ id: int | None = Field(default=None, primary_key=True)
103
+ name: str
104
+
105
+ SQLiteKit.connect()
106
+ SQLiteKit.create_tables([Item])
107
+ item = SQLiteKit.create(Item, {"name": "demo"})
108
+ ```
109
+
110
+ 约定:`SQLiteKit/PgKit/MySqlKit` 方法自动提交;`CRUDBase`(配合 `session_scope`)不自动提交;事务用 `kit.transactional`。
111
+
112
+ ## 模块总览
113
+
114
+ | 模块 | 内容 |
115
+ |------|------|
116
+ | `kit.db` | 统一数据库访问层:PgKit / MySqlKit / SQLiteKit、CRUDBase、transactional |
117
+ | `kit.ai` | AI 服务池:chat / embedding / rerank / image,多厂商候选链 + 故障转移 |
118
+ | `kit.mcp_kit` | 通用 MCP 客户端(streamable-http / sse,档案驱动多服务器) |
119
+ | `kit.redis_kit` | Redis 工具 |
120
+ | `kit.file_kit` | 文件工具 |
121
+ | `kit.doc_kit` | 文档转换(基于 firecrawl-anydoc / pimd) |
122
+ | `kit.zvec_kit` | zvec 本地向量库 |
123
+ | `kit.mineru_kit` | MinerU 文档精准解析 API |
124
+ | `kit.common_kit` | 通用工具 |
125
+
126
+ ## License
127
+
128
+ MIT
@@ -0,0 +1,97 @@
1
+ # sdpy-kit
2
+
3
+ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:统一数据库访问层(PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致)+ AI 多模型服务池(chat / embedding / rerank / image,多厂商候选链与故障转移)+ MCP 客户端 + Redis / 文件 / 文档 / 向量库 / MinerU 工具。
4
+
5
+ > 要求 Python 3.13+
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ pip install sdpy-kit
11
+ # 或
12
+ uv add sdpy-kit
13
+ ```
14
+
15
+ ## 配置(.env)
16
+
17
+ kit 通过 pydantic-settings 读取项目根目录 `.env`(默认值见下,均可省略):
18
+
19
+ ```dotenv
20
+ # 日志
21
+ LOG_LEVEL=INFO
22
+ LOG_TO_CONSOLE=true
23
+ LOG_TO_FILE=false
24
+
25
+ # 数据库(三选一,切换只改 DEFAULT_DB_TYPE)
26
+ DEFAULT_DB_TYPE=sqlite # postgresql / mysql / sqlite
27
+ DB_HOST=localhost
28
+ DB_PORT=5432
29
+ DB_DATABASE=postgres
30
+ DB_USER=admin
31
+ DB_PASSWORD=
32
+ MYSQL_PORT=3306
33
+ MYSQL_DATABASE=mydb
34
+ MYSQL_USER=root
35
+ SQLITE_DB_PATH=./data/app.db
36
+
37
+ # Redis
38
+ REDIS_HOST=localhost
39
+ REDIS_PORT=6379
40
+ ```
41
+
42
+ ## AI / MCP 功能的档案文件
43
+
44
+ AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
45
+
46
+ - `config/ai_models.json` —— chat / embedding / rerank / image 四类型多厂商模型档案
47
+ - `config/mcp_servers.json` —— MCP 服务器档案
48
+
49
+ 文件缺失时,首次使用相应功能会报错,**报错信息中附带可直接复制的最小模板**;
50
+ 其中 `api_key` / `token_env` 字段填写环境变量名,真实密钥配置在 `.env`。
51
+
52
+ ## 快速开始
53
+
54
+ ### 日志与配置
55
+
56
+ ```python
57
+ from kit.config import get_settings, get_default_logger
58
+
59
+ settings = get_settings()
60
+ logger = get_default_logger(__name__)
61
+ logger.info("hello %s", settings.APP_NAME)
62
+ ```
63
+
64
+ ### 数据库(SQLiteKit / PgKit / MySqlKit API 完全一致,切换只改 import)
65
+
66
+ ```python
67
+ from sqlmodel import SQLModel, Field
68
+ from kit.db.sqlite_kit import SQLiteKit
69
+
70
+ class Item(SQLModel, table=True):
71
+ id: int | None = Field(default=None, primary_key=True)
72
+ name: str
73
+
74
+ SQLiteKit.connect()
75
+ SQLiteKit.create_tables([Item])
76
+ item = SQLiteKit.create(Item, {"name": "demo"})
77
+ ```
78
+
79
+ 约定:`SQLiteKit/PgKit/MySqlKit` 方法自动提交;`CRUDBase`(配合 `session_scope`)不自动提交;事务用 `kit.transactional`。
80
+
81
+ ## 模块总览
82
+
83
+ | 模块 | 内容 |
84
+ |------|------|
85
+ | `kit.db` | 统一数据库访问层:PgKit / MySqlKit / SQLiteKit、CRUDBase、transactional |
86
+ | `kit.ai` | AI 服务池:chat / embedding / rerank / image,多厂商候选链 + 故障转移 |
87
+ | `kit.mcp_kit` | 通用 MCP 客户端(streamable-http / sse,档案驱动多服务器) |
88
+ | `kit.redis_kit` | Redis 工具 |
89
+ | `kit.file_kit` | 文件工具 |
90
+ | `kit.doc_kit` | 文档转换(基于 firecrawl-anydoc / pimd) |
91
+ | `kit.zvec_kit` | zvec 本地向量库 |
92
+ | `kit.mineru_kit` | MinerU 文档精准解析 API |
93
+ | `kit.common_kit` | 通用工具 |
94
+
95
+ ## License
96
+
97
+ MIT
@@ -0,0 +1,68 @@
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-08-23
21
+ """Kit 模块——统一数据库访问层 + 工具集
22
+
23
+ 数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
+ """
25
+
26
+ __version__ = "0.1.0"
27
+
28
+ from kit.db import (
29
+ MYSQL,
30
+ POSTGRESQL,
31
+ SQLITE,
32
+ CRUDBase,
33
+ DBError,
34
+ NotFoundError,
35
+ close_all_engines,
36
+ close_engine,
37
+ create_tables,
38
+ get_engine,
39
+ get_session,
40
+ list_tables,
41
+ session_scope,
42
+ table_exists,
43
+ transactional,
44
+ )
45
+
46
+ __all__ = [
47
+ # 引擎管理
48
+ "get_engine",
49
+ "create_tables",
50
+ "list_tables",
51
+ "table_exists",
52
+ "close_all_engines",
53
+ "close_engine",
54
+ # 常量
55
+ "POSTGRESQL",
56
+ "MYSQL",
57
+ "SQLITE",
58
+ # 会话
59
+ "session_scope",
60
+ "get_session",
61
+ # 异常
62
+ "DBError",
63
+ "NotFoundError",
64
+ # CRUD
65
+ "CRUDBase",
66
+ # 事务
67
+ "transactional",
68
+ ]
@@ -0,0 +1,47 @@
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-08-21
21
+ """kit/ai - AI 模型服务模块(纯异步,Python 3.13+)
22
+
23
+ 提供开箱即用的各类模型基础服务:
24
+ - ChatService: 对话模型
25
+ - EmbeddingService: 文本嵌入向量
26
+ - RerankService: 重排序
27
+ - ImageService: 文生图
28
+ """
29
+
30
+ from kit.ai.chat_service import ChatRequest, ChatService
31
+ from kit.ai.embedding_service import EmbeddingRequest, EmbeddingService
32
+ from kit.ai.image_service import ImageRequest, ImageService
33
+ from kit.ai.model_builder import ModelBuilder
34
+ from kit.ai.rerank_service import RerankHit, RerankRequest, RerankService
35
+
36
+ __all__ = [
37
+ "ChatRequest",
38
+ "ChatService",
39
+ "EmbeddingRequest",
40
+ "EmbeddingService",
41
+ "ImageRequest",
42
+ "ImageService",
43
+ "ModelBuilder",
44
+ "RerankHit",
45
+ "RerankRequest",
46
+ "RerankService",
47
+ ]
@@ -0,0 +1,94 @@
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
+ from abc import ABC, abstractmethod
21
+ from typing import Self, override
22
+
23
+ from kit.ai.exceptions import AIResponseError
24
+ from kit.ai.model_client import ModelClient
25
+ from kit.ai.profiles import ModelProfile
26
+ from kit.ai.types import JSONObject, JSONValue
27
+
28
+ __all__ = ["BaseAIService", "safe_get"]
29
+
30
+
31
+ def safe_get(data: JSONValue, *path: str | int) -> JSONValue:
32
+ """按层级路径安全取值,支持 dict 键(str)与 list 索引(int)。
33
+
34
+ Raises:
35
+ AIResponseError: 路径任一环节缺失或类型不符。
36
+ """
37
+ cur: JSONValue = data
38
+ for key in path:
39
+ if isinstance(key, int):
40
+ if not isinstance(cur, list) or key < 0 or key >= len(cur):
41
+ raise AIResponseError(f"响应缺少索引 {path}: {str(data)[:200]}")
42
+ cur = cur[key]
43
+ else:
44
+ if not isinstance(cur, dict) or key not in cur:
45
+ raise AIResponseError(
46
+ f"响应缺少字段 {'.'.join(map(str, path))}: {str(data)[:200]}"
47
+ )
48
+ cur = cur[key]
49
+ return cur
50
+
51
+
52
+ class BaseAIService[RequestT, ResponseT](ABC):
53
+ """AI 服务基类(模板方法 + 泛型)。
54
+
55
+ 子类实现同步的 ``_build_payload``(请求模型→JSON 请求体)与
56
+ ``_parse_response``(JSON 响应体→业务结果),公共的「构造请求、
57
+ 发送、解析」由 ``_call`` 统一编排。请求类型 ``RequestT`` 与结果
58
+ 类型 ``ResponseT`` 由泛型绑定,保证覆写签名一致、类型自洽。
59
+ """
60
+
61
+ def __init__(self, profile: ModelProfile) -> None:
62
+ self._profile: ModelProfile = profile
63
+ self._client: ModelClient = ModelClient(profile)
64
+
65
+ @property
66
+ def profile(self) -> ModelProfile:
67
+ return self._profile
68
+
69
+ @abstractmethod
70
+ def _build_payload(self, request: RequestT) -> JSONObject:
71
+ """将请求模型构造为 JSON 请求体。"""
72
+
73
+ @abstractmethod
74
+ def _parse_response(self, data: JSONObject) -> ResponseT:
75
+ """将 JSON 响应体解析为业务结果。"""
76
+
77
+ async def _call(self, request: RequestT) -> ResponseT:
78
+ """统一编排:构造请求体 → 发送 → 解析响应。"""
79
+ payload = self._build_payload(request)
80
+ data = await self._client.post_json(payload)
81
+ return self._parse_response(data)
82
+
83
+ async def close(self) -> None:
84
+ await self._client.close()
85
+
86
+ async def __aenter__(self) -> Self:
87
+ return self
88
+
89
+ async def __aexit__(self, *exc_info: object) -> None:
90
+ await self.close()
91
+
92
+ @override
93
+ def __str__(self) -> str:
94
+ return f"{type(self).__name__}({self._profile.name})"
@@ -0,0 +1,110 @@
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
+ from collections.abc import AsyncIterator
21
+ from dataclasses import dataclass
22
+ from typing import cast, override
23
+
24
+ from kit.ai.base_service import BaseAIService, safe_get
25
+ from kit.ai.exceptions import AIResponseError
26
+ from kit.ai.types import JSONObject
27
+ from kit.config.log_config import get_default_logger
28
+
29
+ __all__ = ["ChatRequest", "ChatService"]
30
+
31
+ logger = get_default_logger(__name__)
32
+
33
+ Message = dict[str, str]
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class ChatRequest:
38
+ """对话请求模型。"""
39
+
40
+ messages: list[Message]
41
+ temperature: float | None = None
42
+ max_tokens: int | None = None
43
+ stream: bool = False
44
+
45
+
46
+ class ChatService(BaseAIService[ChatRequest, str]):
47
+ """对话类模型服务(全异步)。"""
48
+
49
+ @override
50
+ def _build_payload(self, request: ChatRequest) -> JSONObject:
51
+ payload: dict[str, object] = {
52
+ "model": self._profile.model,
53
+ "messages": request.messages,
54
+ "stream": request.stream,
55
+ }
56
+ if request.temperature is not None:
57
+ payload["temperature"] = request.temperature
58
+ if request.max_tokens is not None:
59
+ payload["max_tokens"] = request.max_tokens
60
+ return cast(JSONObject, payload)
61
+
62
+ @override
63
+ def _parse_response(self, data: JSONObject) -> str:
64
+ content = safe_get(data, "choices", 0, "message", "content")
65
+ if not isinstance(content, str):
66
+ raise AIResponseError(f"content 类型异常: {type(content).__name__}")
67
+ return content
68
+
69
+ async def ask(
70
+ self,
71
+ messages: list[Message],
72
+ *,
73
+ temperature: float | None = None,
74
+ max_tokens: int | None = None,
75
+ ) -> str:
76
+ request = ChatRequest(messages, temperature=temperature, max_tokens=max_tokens)
77
+ return await self._call(request)
78
+
79
+ async def complete(
80
+ self,
81
+ prompt: str,
82
+ *,
83
+ temperature: float | None = None,
84
+ max_tokens: int | None = None,
85
+ ) -> str:
86
+ messages: list[Message] = [{"role": "user", "content": prompt}]
87
+ return await self.ask(messages, temperature=temperature, max_tokens=max_tokens)
88
+
89
+ async def stream(
90
+ self,
91
+ messages: list[Message],
92
+ *,
93
+ temperature: float | None = None,
94
+ max_tokens: int | None = None,
95
+ ) -> AsyncIterator[str]:
96
+ request = ChatRequest(
97
+ messages, temperature=temperature, max_tokens=max_tokens, stream=True
98
+ )
99
+ payload = self._build_payload(request)
100
+ async for parsed in self._client.stream_json(payload):
101
+ if parsed is None:
102
+ continue
103
+ try:
104
+ delta = safe_get(parsed, "choices", 0, "delta", "content")
105
+ except AIResponseError:
106
+ # 流式结束帧等无 delta 的正常帧,跳过
107
+ logger.debug("流式帧缺少 delta.content,跳过")
108
+ continue
109
+ if isinstance(delta, str) and delta:
110
+ yield delta