my-llm-py 是一个 Python 异步 LLM 协议适配库,支持 OpenAI Chat、OpenAI Responses、Anthropic、Gemini 和 Ollama 五种协议自由组合。
用一种协议请求另一种协议的 API,按输入协议返回结果。标准协议直接转换,非标准协议通过配置兼容。
OpenAI Chat 格式输入 → 任意协议 API → OpenAI Chat 格式输出
消息、工具和流事件一并转换。更换上游 API,业务层继续使用原有协议收发数据。
主要功能与亮点
| 功能 | 说明 |
|---|---|
| 跨协议调用 | 标准协议直接转换,输入输出协议与上游 API 独立选择。 |
| 标准消息翻译 | 只翻译已识别的协议字段;工具参数、结果和 JSON Schema 等开放字典保留内部键值。 |
| 统一工具调用 | 转换工具定义、调用和结果;流式参数自动组装,响应成功结束后交付完整调用。工具由应用执行。 |
| 流式与完整回复 | 支持文本、可见思考增量;流结束后通过 stream.result 获取完整消息。 |
| 多模态转换 | 按协议与模型能力转换图片、音频、视频和文件等内容。 |
| 配置兼容 | 非标准协议通过配置兼容,适配服务与模型的参数、字段路径及思考内容等差异,配置可拓展。 |
| 独立格式转换 | 无需调用模型,即可转换已支持的消息、工具和完整流事件列表。 |
| 异步并发 | 复用官方 SDK 客户端,请求状态独立;支持取消、提前退出和资源清理。 |
| 协议扩展 | 实现四个抽象基类并注册,即可接入现有调用与转换流程。 |
输入接受标准字典或可转为字典的官方 SDK 对象,输出为所选输入协议的标准字典。
标准协议和 Profile 未声明的字段直接跳过,不读取、不校验;已识别开放字典内部数据保留。
Profile 用 Parameter 集中声明字段路径、缺失默认值和翻译阶段,Config 控制代码行为,均支持继承扩展。
字段 None 继承配置;default_value=None 是实际值,已有 None 不会被默认值覆盖;跨协议来回翻译不保证无损。
跨协议转换受目标协议的表达能力限制,详见兼容边界。
新增协议通过 MyCodec.register(codec) 在启动时注册,协议默认值随 Codec 声明;
无需修改公共入口或协议枚举,详见接入指南。
参数路径统一使用 $. 前缀,例如 $.reasoning.effort;非标准请求参数通过 SDK 的 extra_body 等正式扩展入口配置。
安装
需要 Python 3.12+。
pip install my-llm-py
# 或 uv add my-llm-py
快速开始
以下示例使用 OpenAI Chat 格式调用 Anthropic Messages API,返回 Chat assistant 消息。
设置 ANTHROPIC_API_KEY 和 LLM_MODEL 环境变量后运行,模型 ID 填写服务支持的值。
import asyncio
import os
from my_llm import Config, ModelProfile, MyLLM, Parameter, ProviderProfile
async def main():
async with MyLLM(
provider=ProviderProfile(
protocol="anthropic", # 实际请求的 API 协议
api_key=Config(name="API 密钥", default_value=os.environ["ANTHROPIC_API_KEY"]),
),
model=ModelProfile(
model=os.environ["LLM_MODEL"],
max_tokens=Parameter(
name="输出上限",
path="$.max_tokens",
default_value=4096,
phase="decode",
),
),
stdio_protocol="openai_chat", # 输入和输出均使用 Chat 协议
) as llm:
message = await llm.generate(messages=[{"role": "user", "content": "你好"}])
print(message["content"])
asyncio.run(main())
更换 provider.protocol 及对应连接、模型配置,即可切换上游 API;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(name="工具调用", default_value=True)。
独立格式转换
MyCodec 可离线转换协议格式,无需 API 密钥:
from my_llm import MyCodec
converted = MyCodec().convert(
[{"role": "user", "content": "你好"}],
input_format="openai_chat",
target_format="gemini",
)
print(converted["messages"])
# [{"role": "user", "parts": [{"text": "你好"}]}]
支持的协议
以下标识均可用于 ProviderProfile.protocol 和 stdio_protocol:
| 协议标识 | 对应 API |
|---|---|
openai_chat |
OpenAI Chat Completions 及兼容服务 |
openai_responses |
OpenAI Responses 及兼容服务 |
anthropic |
Anthropic Messages |
gemini |
Gemini GenerateContent |
ollama |
Ollama Chat |
同一实例的消息与工具使用同一种输入输出协议。
已通过端到端验证的协议组合
3×3 九条真实 API 路径验证 (2026-10-20)。
| 输入/输出协议 ↓ \ 上游 API → | Chat Completions | Responses | Anthropic Messages | Gemini Content | Ollama |
|---|---|---|---|---|---|
openai_chat |
✅ 已验证 | ✅ 已验证 | ✅ 已验证 | ➖ 离线验证 | ➖ 离线验证 |
openai_responses |
✅ 已验证 | ✅ 已验证 | ✅ 已验证 | ➖ 离线验证 | ➖ 离线验证 |
anthropic |
✅ 已验证 | ✅ 已验证 | ✅ 已验证 | ➖ 离线验证 | ➖ 离线验证 |
gemini |
➖ 离线验证 | ➖ 离线验证 | ➖ 离线验证 | ➖ 离线验证 | ➖ 离线验证 |
ollama |
➖ 离线验证 | ➖ 离线验证 | ➖ 离线验证 | ➖ 离线验证 | ➖ 离线验证 |
每条路径覆盖文本汇总、流式增量与汇总一致性,以及工具调用、参数组装和工具结果回传。 汇总模式也通过上游 SSE 执行,本轮未单独验证供应商的 HTTP 非流式响应。 实测中 Chat、Anthropic 的关闭思考参数未完全阻止返回思考块,部分跨协议输出会将其转为可见文本; 上述路径分别校验最终答案与完整流式文本一致性。
接入配置、复跑方法及验证边界见 SenseNova 端到端测试指南, 逐项结果见 JSON 报告。
安装最新的开发版本 (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 1.1.0
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 | |
|---|---|---|---|---|
| my_llm_py-1.1.0.tar.gz | 764.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| my_llm_py-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 872.3 kB
Release files / my_llm_py-1.1.0.tar.gz
| Download URL | my_llm_py-1.1.0.tar.gz |
|---|---|
| Size | 764.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
806e2692be9815343aee11007bc93756332ba586972ad826762eb14062858bf0
|
|
BLAKE2b-256 checksum How to use checksums |
a0778ee842b6622b434c5a034b06110ef1395f43fe71131aff1e0b44e7e2232a
|
| 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-1.1.0-py3-none-any.whl
| Download URL | my_llm_py-1.1.0-py3-none-any.whl |
|---|---|
| Size | 108.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e25656f9f9419cd5f345a3fd44d590512dc0952503066e6d166d075d59d27b01
|
|
BLAKE2b-256 checksum How to use checksums |
424db0db3174d161a7b27b95d7efad3642ae7d84ff8bba56ea09d8fbd2cf44e9
|
| 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 1.1.0
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.