my-llm-py 是一个 Python 异步 LLM 协议适配库,支持 OpenAI Chat、OpenAI Responses、Anthropic 三种协议自由组合。
用一种协议请求另一种协议的 API,按输入协议返回结果。标准协议直接转换,非标准协议通过配置兼容。
OpenAI Chat 格式输入 → 任意协议 API → OpenAI Chat 格式输出
消息、工具和流事件一并转换。更换上游 API,业务层继续使用原有协议收发数据。
主要功能与亮点
| 功能 | 说明 |
|---|---|
| 跨协议调用 | 输入输出协议与上游 API 独立选择,消息、工具和流事件一并转换。 |
| 标准输入输出 | 接受标准字典或官方 SDK 对象,按所选输入协议返回标准消息与事件。 |
| 统一工具调用 | 转换工具定义、调用和结果;流式参数自动组装,成功结束后交付完整调用,由应用执行。 |
| 流式与完整回复 | 支持文本、可见思考增量,流结束后通过 stream.result 获取完整消息。 |
| 多模态转换 | 支持文本、用户图片、工具结果图片、思维链及 usage;转换受目标协议能力限制。 |
| 语义损失处理 | 已支持内容无法表达时默认抛 SemanticLossError,可选 ignore 或双向 hook 补偿。 |
| Profile 配置兼容 | 用 InputParameter / OutputParameter 配置字段路径与默认值,Config 控制行为;适配服务与模型差异。 |
| 中间模型直连 | 显式 stdio_protocol="middleware",直接用中间模型收发消息、工具与流事件。 |
| 增量消息缓存 | 缓存已翻译的增量历史,连续重发相同批次不重复追加;开启后同实例串行使用,见缓存约定。 |
| 异步与资源管理 | 关闭消息缓存时支持同实例并发;支持取消、提前退出和资源清理,见架构职责。 |
| 独立格式转换 | 无需调用模型,即可转换已支持的消息、工具和完整流事件列表。 |
| 协议扩展 | 实现四个抽象基类,通过 MyCodec.register(CodecFactory) 注册;无需修改公共入口,见接入指南。 |
| 字段保留边界 | 只翻译已识别字段;工具参数、结果和 JSON Schema 等开放字典保留内部键值;audio、file、用户 metadata 等未支持字段静默丢弃。 |
安装
需要 Python 3.12+。
pip install 'my-llm-py[all]'
# 或 uv add 'my-llm-py[all]'
推荐使用 [all] 安装全部 SDK。也可以按需选择,支持用逗号组合多个 extra:
pip install 'my-llm-py[openai]'
pip install 'my-llm-py[anthropic]'
pip install 'my-llm-py[openai,anthropic]'
pip install my-llm-py 仅安装核心依赖。每种输入、输出及上游协议都需要对应 SDK;
例如 Chat 输入调用 Anthropic API 需要 [openai,anthropic]。
中间模型 stdio 只需要安装上游对应 SDK。
未安装的协议不会注册,显式选择时会提示安装命令。
项目开发环境执行 uv sync 默认安装开发工具和全部 SDK。
仅安装指定 SDK 可使用 uv sync --no-default-groups --extra openai。
快速开始
以下示例使用 OpenAI Chat 格式调用 Anthropic Messages API,返回 Chat assistant 消息。
设置 ANTHROPIC_API_KEY 和 LLM_MODEL 环境变量后运行,模型 ID 填写服务支持的值。
import asyncio
import os
from my_llm import Attribute, Config, ModelProfile, MyLLM, InputParameter, ProviderProfile
async def main():
async with MyLLM(
provider=ProviderProfile(
protocol=Config(default_value="anthropic"), # 实际请求的 API 协议
api_key=InputParameter(
path="$.api_key",
default_value=os.environ["ANTHROPIC_API_KEY"],
),
),
model=ModelProfile(
model=Attribute(default_value=os.environ["LLM_MODEL"]),
max_tokens=InputParameter(
path="$.max_tokens",
default_value=4096,
),
),
stdio_protocol="openai_chat", # 输入和输出均使用 Chat 协议
) as llm:
message = await llm.generate(messages=[{"role": "user", "content": "你好"}])
print(message["content"])
asyncio.run(main())
通过 provider.protocol 配置默认上游 API,model.protocol 可覆盖它;stdio_protocol 决定业务层使用的格式。
建议像示例一样显式指定 stdio_protocol。省略时仅在消息与工具整批唯一匹配一种协议时自动绑定;
零匹配或多种协议都匹配时抛出 MessageValidationError。普通 role/content 文本存在协议歧义,需要显式指定。
流式调用
在上例的 async with MyLLM(stdio_protocol="openai_chat", ...) 内调用下面的函数,流事件也按 Chat (openai_chat) 协议返回:
async def print_answer(llm):
stream = await llm.generate(
messages=[{"role": "user", "content": "你好"}],
stream=True,
)
async with stream:
async for event in stream:
for choice in event.get("choices", []):
print(choice["delta"].get("content", ""), end="", flush=True)
return stream.result
使用 async with 管理实例和流。传入工具前,在 ModelProfile 中设置
supports_function_calling=Config(default_value=True)。
设置 enable_message_cache=True 可缓存已翻译的增量消息;连续重发相同批次不会重复追加历史。
启用后,同一 MyLLM 实例不支持并发:先完成或关闭前一条流,再调用下一次生成。
不同实例可以并发使用。用法和重试约定见增量消息缓存。
独立格式转换
MyCodec 可离线转换协议格式,无需 API 密钥:
from my_llm import MyCodec
converted = MyCodec().convert(
[{"role": "user", "content": "你好"}],
input_format="openai_chat",
target_format="anthropic",
)
print(converted["messages"])
# [{"role": "user", "content": [{"type": "text", "text": "你好"}]}]
支持的协议
以下标识均可用于 ProviderProfile.protocol、ModelProfile.protocol 和 stdio_protocol:
| 协议标识 | 对应 API |
|---|---|
openai_chat |
OpenAI Chat Completions 及兼容服务 |
openai_responses |
OpenAI Responses 及兼容服务 |
anthropic |
Anthropic Messages |
同一实例的消息与工具使用同一种输入输出协议。
已通过端到端验证的协议组合
3×4 十二条真实 API 路径验证(2026-10-25),补跑后 36 个场景全部通过。
| 输入/输出协议 ↓ \ 上游 API → | Chat Completions | Responses | Anthropic Messages |
|---|---|---|---|
openai_chat |
✅ 已验证 | ✅ 已验证 | ✅ 已验证 |
openai_responses |
✅ 已验证 | ✅ 已验证 | ✅ 已验证 |
anthropic |
✅ 已验证 | ✅ 已验证 | ✅ 已验证 |
middleware |
✅ 已验证 | ✅ 已验证 | ✅ 已验证 |
每条路径覆盖文本汇总、流式增量与汇总一致性,以及工具调用、参数组装和工具结果回传。
汇总模式也通过上游 SSE 执行,本轮未单独验证供应商的 HTTP 非流式响应。
实测中 Chat、Anthropic 的关闭思考参数未完全阻止返回思考块。脚本默认 ignore,
目标无法表达的思维或签名按损失策略处理,思维不混入正文;正文和完整流式正文要求精确匹配。
接入配置、复跑方法、失败说明及补跑结果见 SenseNova 端到端测试指南。
安装最新的开发版本 (main 分支)
git clone https://github.com/JJJJSTIYYYY/my-llm.git
cd ./my-llm
uv sync --group dev --locked
uv run pytest -q
uv run ruff check src tests examples
uv run ruff format --check src tests examples
uv run python examples/local_demo.py
uv build
文档
项目采用 Apache License 2.0。
Metadata
Release files for my-llm-py 2.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| my_llm_py-2.0.1.tar.gz | 833.5 kB | Without provenance | Details for my_llm_py-2.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| my_llm_py-2.0.1-py3-none-any.whl | Python 3 | none | any | Details for my_llm_py-2.0.1-py3-none-any.whl |
Total release size: 993.8 kB
Release files / my_llm_py-2.0.1.tar.gz
| Download URL | my_llm_py-2.0.1.tar.gz |
|---|---|
| Size | 833.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d1873155313a9b389d313e236fd8182f485f14f4e59165c67c140c9ecf340d0e
|
|
BLAKE2b-256 checksum How to use checksums |
1e2898a836ae6b069b13e72bbd93dff9bbf547f7a28ab0325a1fb7347da16dba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|
Release files / my_llm_py-2.0.1-py3-none-any.whl
| Download URL | my_llm_py-2.0.1-py3-none-any.whl |
|---|---|
| Size | 160.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
779a6701514fd6069fe533cf00f714d6b1e7c98092ade7de87cafa6c43d60ad1
|
|
BLAKE2b-256 checksum How to use checksums |
5e96da20e289a2f0115ba93465654b94554a0a3b1f827e8c0589edb5fe749c55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|
Security for my-llm-py 2.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.