init(all):一个让codex调用deepseek模型的项目
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.egg-info/
|
||||||
|
.venv/
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# IDE / Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Runtime
|
||||||
|
proxy_debug.log
|
||||||
|
debug_request_body.txt
|
||||||
|
|
||||||
|
# Docker (避免层级嵌套)
|
||||||
|
docker/
|
||||||
|
Dockerfile
|
||||||
|
docker-compose*.yml
|
||||||
|
|
||||||
|
# Build / Dist
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# ==== codex_proxy 配置文件 ====
|
||||||
|
# 复制此文件为 .env 并填入你的配置
|
||||||
|
|
||||||
|
# 必填:DeepSeek API Key(从 https://platform.deepseek.com/api_keys 获取)
|
||||||
|
DEEPSEEK_API_KEY=*********
|
||||||
|
|
||||||
|
# 本服务的API Key,用于代理请求
|
||||||
|
MY_API_KEY=sk-1234567890
|
||||||
|
|
||||||
|
# 默认模型
|
||||||
|
MODEL_DEFAULT = deepseek-v4-flash
|
||||||
|
|
||||||
|
## codex模型对应,为空则使用默认模型
|
||||||
|
MODEL_GTP5_5 = deepseek-v4-pro
|
||||||
|
MODEL_GTP5_4 = deepseek-v4-flash
|
||||||
|
MODEl_GPT5_4_MINI = deepseek-v4-flash
|
||||||
|
MODEL_GTP5_3_CODEX = deepseek-v4-flash
|
||||||
|
MODEL_GTP5_2 = deepseek-v4-flash
|
||||||
|
|
||||||
|
# 可选:DeepSeek API 地址(默认官方地址,一般无需修改)
|
||||||
|
# DEEPSEEK_URL=https://api.deepseek.com/v1/chat/completions
|
||||||
|
|
||||||
|
# 可选:调试日志,设为 1 开启
|
||||||
|
# DEEPSEEK_DEBUG=1
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# API Key
|
||||||
|
.env
|
||||||
|
|
||||||
|
# 调试日志
|
||||||
|
proxy_debug.log
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
|
||||||
|
# 虚拟环境
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
env/
|
||||||
|
/.claude
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
# ============================================================
|
||||||
|
# Codex2DeepSeek — 跨平台多阶段构建
|
||||||
|
# ============================================================
|
||||||
|
# 构建:
|
||||||
|
# docker build -t codex2deepseek:latest .
|
||||||
|
# docker buildx build --platform linux/amd64,linux/arm64 -t codex2deepseek:latest .
|
||||||
|
#
|
||||||
|
# 运行:
|
||||||
|
# docker compose -f docker/docker-compose.yml up -d
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# ---- 构建阶段 ----
|
||||||
|
# 使用与运行时相同的 slim 基镜像,避免 musl/glibc 不兼容问题
|
||||||
|
FROM python:3.12-slim AS builder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# 安装 uv
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
||||||
|
|
||||||
|
# 利用 Docker 层缓存:先复制依赖声明
|
||||||
|
COPY pyproject.toml uv.lock ./
|
||||||
|
RUN uv sync --frozen --no-dev --no-install-project
|
||||||
|
|
||||||
|
# 复制源码
|
||||||
|
COPY app/ ./app/
|
||||||
|
COPY routers/ ./routers/
|
||||||
|
COPY services/ ./services/
|
||||||
|
COPY main.py ./
|
||||||
|
|
||||||
|
# 重新 sync 以链接本地包
|
||||||
|
RUN uv sync --frozen --no-dev
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 运行时阶段 ----
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 安装 ca-certificates 确保 HTTPS 请求正常
|
||||||
|
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 从 builder 复制已安装的 venv 和源码
|
||||||
|
COPY --from=builder /build/.venv /app/.venv
|
||||||
|
COPY --from=builder /build/main.py /app/main.py
|
||||||
|
COPY --from=builder /build/app/ /app/app/
|
||||||
|
COPY --from=builder /build/routers/ /app/routers/
|
||||||
|
COPY --from=builder /build/services/ /app/services/
|
||||||
|
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH" \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
|
EXPOSE 12345
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD python -c "import socket; socket.create_connection(('localhost', 12345), timeout=5).close()" || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "main.py"]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 PineKings
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
# Codex2DeepSeek
|
||||||
|
|
||||||
|
OpenAI Responses API ↔ DeepSeek Chat API 流式转发代理。
|
||||||
|
|
||||||
|
将 OpenAI Codex 等客户端发出的 Responses API 请求,实时转换为 DeepSeek Chat Completions 格式,实现 SSE 流式透传。
|
||||||
|
|
||||||
|
本项目配合 `CC Switch`使用,可实现 Codex IDE/CLI 与 DeepSeek Chat 的对接中转。
|
||||||
|
|
||||||
|
> 妈的,使用Codex,想用DeepSeek接入,找了半天工具,就没发现什么非常好用的工具,协议都不匹配,找到 [Nigel211/codex_deepseek_proxy](https://github.com/Nigel211/codex_deepseek_proxy.git) 但是单文件模式不习惯,使用flask框架也不喜欢,同时在使用中发现模型的对应问题,很头大,没工夫去慢慢研究了,所以重构了fastAPI的版本,后续有空的话会增加其他各路国产模型的适配。
|
||||||
|
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- **协议转换** — 将 OpenAI Responses API 请求(`input`/`instructions`/`tools` 等字段)自动映射为 DeepSeek Chat Completions 格式
|
||||||
|
- **流式透传** — 基于 SSE (Server-Sent Events) 实时转发 DeepSeek 响应,完整保留 `reasoning_content`(思考链)
|
||||||
|
- **工具调用** — 支持 Function Calling 的双向格式转换与消息重排,兼容并行工具调用
|
||||||
|
- **模型映射** — 通过环境变量灵活配置客户端模型名到 DeepSeek 模型的对应关系
|
||||||
|
- **身份验证** — 支持 Authorization Bearer Token 鉴权
|
||||||
|
- **轻量部署** — 仅依赖 FastAPI + Uvicorn + Requests
|
||||||
|
|
||||||
|
## 流程
|
||||||
|
|
||||||
|
```
|
||||||
|
Codex IDE/CLI Proxy Server DeepSeek API
|
||||||
|
───────────── ───────────── ────────────
|
||||||
|
Responses API ────→ 格式转换 (Responses→Chat) ────→ /v1/chat/completions
|
||||||
|
SSE Stream ←──── 格式转换 (Chat→Responses) ←──── SSE Stream
|
||||||
|
```
|
||||||
|
|
||||||
|
### 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
├── main.py # 应用入口:CLI 参数、FastAPI 初始化、启动
|
||||||
|
├── pyproject.toml # 项目配置与依赖声明
|
||||||
|
├── .env # 环境变量(API Key、模型映射等)
|
||||||
|
├── app/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── config.py # 配置管理:.env 加载、API Key 交互式输入、模型映射表
|
||||||
|
├── routers/
|
||||||
|
│ ├── __init__.py # 路由统一注册
|
||||||
|
│ ├── proxy.py # API 端点:/responses, /v1/responses, /v1/chat/completions
|
||||||
|
│ ├── lifespan.py # 应用生命周期管理
|
||||||
|
│ └── middleware.py # 中间件:CORS、Auth 鉴权、模型名称解析
|
||||||
|
└── services/
|
||||||
|
├── __init__.py
|
||||||
|
├── converter.py # 协议转换:Responses API → Chat Completions
|
||||||
|
└── stream.py # SSE 流式生成器
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速启动
|
||||||
|
|
||||||
|
> 前置条件:Python 3.8+、uv(uv 未安装请自行百度uv安装)
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/PineKings/Code2DeepSeek.git
|
||||||
|
cd Code2DeepSeek
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置
|
||||||
|
|
||||||
|
创建 `.env` 文件:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# DeepSeek API Key(必填)
|
||||||
|
DEEPSEEK_API_KEY=sk-your-deepseek-key
|
||||||
|
|
||||||
|
# 本服务鉴权密钥(必填,客户端请求需携带)
|
||||||
|
MY_API_KEY=sk-your-proxy-key
|
||||||
|
|
||||||
|
# 默认模型(请求中未指定模型时使用)
|
||||||
|
MODEL_DEFAULT=deepseek-v4-flash
|
||||||
|
|
||||||
|
# 模型映射:客户端模型名 → DeepSeek 模型名(留空则使用 MODEL_DEFAULT)
|
||||||
|
MODEL_GTP5_5=deepseek-v4-pro
|
||||||
|
MODEL_GTP5_4=deepseek-v4-flash
|
||||||
|
MODEl_GPT5_4_MINI=deepseek-v4-flash
|
||||||
|
MODEL_GTP5_3_CODEX=deepseek-v4-flash
|
||||||
|
MODEL_GTP5_2=deepseek-v4-flash
|
||||||
|
```
|
||||||
|
|
||||||
|
首次启动时若未配置 `DEEPSEEK_API_KEY` 或 `MY_API_KEY`,程序会交互式提示输入并自动写入 `.env`,该方式docker启动不可用❌。
|
||||||
|
|
||||||
|
### 3. 运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `--port` | `12345` | 服务端口 |
|
||||||
|
| `--host` | `0.0.0.0` | 绑定地址 |
|
||||||
|
| `--debug` | — | 启用调试日志 |
|
||||||
|
|
||||||
|
|
||||||
|
### 4. Docker 方式构建后部署
|
||||||
|
|
||||||
|
确保已安装 Docker 和 Docker Compose(Docker Desktop 已内置 compose)。
|
||||||
|
|
||||||
|
#### 构建镜像
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 从 Dockerfile 构建(根目录)
|
||||||
|
docker build -t codex2deepseek:latest .
|
||||||
|
|
||||||
|
# 跨平台构建
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 -t codex2deepseek:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 docker-compose.yml 后台启动
|
||||||
|
docker compose -f docker/docker-compose.yml up -d
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker compose -f docker/docker-compose.yml logs -f
|
||||||
|
|
||||||
|
# 重启
|
||||||
|
docker compose -f docker/docker-compose.yml restart
|
||||||
|
|
||||||
|
# 停止并删除容器
|
||||||
|
docker compose -f docker/docker-compose.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
服务将在 `http://127.0.0.1:12345` 监听。
|
||||||
|
|
||||||
|
> ⚠️ `.env` 文件**必须**在启动前已配置好 `DEEPSEEK_API_KEY` 和 `MY_API_KEY`。Docker 模式下不支持交互式输入,请确认 `.env` 文件内容完整。
|
||||||
|
|
||||||
|
#### 单独使用 Docker(不依赖 Compose)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建
|
||||||
|
docker build -t codex2deepseek:latest .
|
||||||
|
|
||||||
|
# 运行
|
||||||
|
docker run -d -p 127.0.0.1:12345:12345 \
|
||||||
|
--name codex2deepseek \
|
||||||
|
--env-file .env \
|
||||||
|
--restart unless-stopped \
|
||||||
|
codex2deepseek:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 文件说明
|
||||||
|
|
||||||
|
```
|
||||||
|
├── Dockerfile # 多阶段构建,builder 使用 python:3.12-slim + uv
|
||||||
|
├── docker/
|
||||||
|
│ ├── docker-compose.yml # Compose 编排文件(构建 + 启动)
|
||||||
|
│ └── .env.example # 环境变量模板
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Dockerfile` 位于项目根目录,使用 python:3.12-slim 作为构建和运行时基镜像,避免 musl/glibc 不兼容问题
|
||||||
|
- `docker/docker-compose.yml` 位于 `docker/` 目录下,通过 `context: ..` 引用项目根目录的 `Dockerfile` 和 `.env`
|
||||||
|
|
||||||
|
### 5. Docker 部署
|
||||||
|
#### 拉取镜像
|
||||||
|
```bash
|
||||||
|
docker pull pinekings/codex2deepseek:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 创建.env文件
|
||||||
|
```bash
|
||||||
|
touch .env
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 编辑.env文件(`vi .env`)
|
||||||
|
```bash
|
||||||
|
# ==== codex_proxy 配置文件 ====
|
||||||
|
# 复制此文件为 .env 并填入你的配置
|
||||||
|
|
||||||
|
# 必填:DeepSeek API Key(从 https://platform.deepseek.com/api_keys 获取)
|
||||||
|
DEEPSEEK_API_KEY=*********
|
||||||
|
|
||||||
|
# 本服务的API Key,用于代理请求
|
||||||
|
MY_API_KEY=sk-1234567890
|
||||||
|
|
||||||
|
# 默认模型
|
||||||
|
MODEL_DEFAULT = deepseek-v4-flash
|
||||||
|
|
||||||
|
## codex模型对应,为空则使用默认模型
|
||||||
|
MODEL_GTP5_5 = deepseek-v4-pro
|
||||||
|
MODEL_GTP5_4 = deepseek-v4-flash
|
||||||
|
MODEl_GPT5_4_MINI = deepseek-v4-flash
|
||||||
|
MODEL_GTP5_3_CODEX = deepseek-v4-flash
|
||||||
|
MODEL_GTP5_2 = deepseek-v4-flash
|
||||||
|
|
||||||
|
# 可选:DeepSeek API 地址(默认官方地址,一般无需修改)
|
||||||
|
# DEEPSEEK_URL=https://api.deepseek.com/v1/chat/completions
|
||||||
|
|
||||||
|
# 可选:调试日志,设为 1 开启
|
||||||
|
# DEEPSEEK_DEBUG=1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 运行容器
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name codex2deepseek \
|
||||||
|
-p 12345:12345 \
|
||||||
|
--env-file ./.env \
|
||||||
|
pinekings/codex2deepseek:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用法
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
本项目无法直接实现codex的对接,需要配合CCSwitch等工具实现对接。
|
||||||
|
|
||||||
|
打开ccswitch,点击添加新供应商,选择自定义,填写代理地址和端口即可。
|
||||||
|
|
||||||
|
```
|
||||||
|
供应商名称: DeepSeek
|
||||||
|
API Key: sk-your-proxy-key
|
||||||
|
API 请求地址:http://127.0.0.1:12345(本地跑这个服务就本地,远程就改成你的ip和端口号)
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 点击“获取模型列表”按钮,报错“未找到可用的模型列表端点,请检查 Base URL 或确认供应商是否开放该接口”非常正常!因为本项目就没有中转模型列表获取的接口!
|
||||||
|
- 测试是否对接成功,在供应商列表,然后点击“测试模型”,如果返回了类似这样`DeepSeek 运行正常 (4ms)`的信息就说明成功了
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## 模型映射
|
||||||
|
|
||||||
|
通过环境变量为每个 Codex 模型名指定对应的 DeepSeek 模型:
|
||||||
|
|
||||||
|
| 环境变量 | 默认值 | 客户端请求 model |
|
||||||
|
|---------|--------|-----------------|
|
||||||
|
| `MODEL_DEFAULT` | `deepseek-v4-flash` | 未指定或未匹配时使用 |
|
||||||
|
| `MODEL_GTP5_5` | `MODEL_DEFAULT` | `gpt-5.5` |
|
||||||
|
| `MODEL_GTP5_4` | `MODEL_DEFAULT` | `gpt-5.4` |
|
||||||
|
| `MODEl_GPT5_4_MINI` | `MODEL_DEFAULT` | `gpt-5.4-mini` |
|
||||||
|
| `MODEL_GTP5_3_CODEX` | `MODEL_DEFAULT` | `gpt-5.3-codex` |
|
||||||
|
| `MODEL_GTP5_2` | `MODEL_DEFAULT` | `gpt-5.2` |
|
||||||
|
|
||||||
|
模型解析优先级:`中间件解析值 > 请求体中的 model > MODEL_DEFAULT > 代码默认值`
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
| 变量 | 必填 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `DEEPSEEK_API_KEY` | 是 | — | DeepSeek API 密钥 |
|
||||||
|
| `MY_API_KEY` | 是 | — | 本服务鉴权密钥 |
|
||||||
|
| `DEEPSEEK_URL` | 否 | `https://api.deepseek.com/v1/chat/completions` | DeepSeek API 地址 |
|
||||||
|
| `DEFAULT_MODEL` | 否 | `deepseek-v4-flash` | 无模型指定时的默认模型(旧配置) |
|
||||||
|
| `MODEL_DEFAULT` | 否 | `deepseek-v4-flash` | 模型映射默认值 |
|
||||||
|
| `DEEPSEEK_DEBUG` | 否 | `0` | 设为 `1` 启用调试日志到 `proxy_debug.log` |
|
||||||
|
| `MODEL_GTP5_5` | 否 | `MODEL_DEFAULT` | `gpt-5.5` 映射目标 |
|
||||||
|
| `MODEL_GTP5_4` | 否 | `MODEL_DEFAULT` | `gpt-5.4` 映射目标 |
|
||||||
|
| `MODEl_GPT5_4_MINI` | 否 | `MODEL_DEFAULT` | `gpt-5.4-mini` 映射目标 |
|
||||||
|
| `MODEL_GTP5_3_CODEX` | 否 | `MODEL_DEFAULT` | `gpt-5.3-codex` 映射目标 |
|
||||||
|
| `MODEL_GTP5_2` | 否 | `MODEL_DEFAULT` | `gpt-5.2` 映射目标 |
|
||||||
|
|
||||||
|
## Tool Calling 兼容性
|
||||||
|
|
||||||
|
代理处理了 DeepSeek Chat Completions API 与 OpenAI Responses API 之间的以下差异:
|
||||||
|
|
||||||
|
| 问题 | 处理方式 |
|
||||||
|
|------|----------|
|
||||||
|
| 工具定义格式不同 | 将 Responses API 扁平格式转为 Chat API 嵌套 `function` 格式,清除 `strict` 和 `additionalProperties` 字段 |
|
||||||
|
| 并行工具调用 | 将 Codex 的多个连续 `function_call` 项合并为单个 assistant 消息(含多个 `tool_calls`) |
|
||||||
|
| 消息顺序 | DeepSeek 要求 tool 消息紧跟对应的 assistant 消息;代理自动重排插入的 system 消息 |
|
||||||
|
| 思考模式 | 默认禁用(`thinking: {type: "disabled"}`),避免 `reasoning_content` 回传兼容问题 |
|
||||||
|
| `reasoning_content` | 仅在流结束事件中随 `item.done` 回传,避免破坏 Responses API 事件结构 |
|
||||||
|
|
||||||
|
|
||||||
|
## 归属权
|
||||||
|
|
||||||
|
本项目重构自 [Nigel211/codex_deepseek_proxy](https://github.com/Nigel211/codex_deepseek_proxy.git),该项目以MIT协议开源,本项目亦同。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""codex_deepseek_proxy 配置模块"""
|
||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
"""
|
||||||
|
配置管理模块
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- .env 文件加载(不覆盖已有系统环境变量)
|
||||||
|
- DEEPSEEK_API_KEY 交互式输入与持久化
|
||||||
|
- 导出全局配置常量
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import getpass
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def load_dotenv():
|
||||||
|
"""加载 .env 文件到 os.environ(不覆盖已有的系统环境变量)"""
|
||||||
|
env_file = os.path.join(BASE_DIR, ".env")
|
||||||
|
if not os.path.exists(env_file):
|
||||||
|
return
|
||||||
|
with open(env_file, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, _, val = line.partition("=")
|
||||||
|
key, val = key.strip(), val.strip().strip("\"'")
|
||||||
|
if key and key not in os.environ:
|
||||||
|
os.environ[key] = val
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_api_key() -> str:
|
||||||
|
"""确保 DEEPSEEK_API_KEY 已设置:系统环境变量 > .env > 交互输入
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
API Key 字符串,若用户取消输入则退出进程。
|
||||||
|
"""
|
||||||
|
key = os.environ.get("DEEPSEEK_API_KEY", "").strip()
|
||||||
|
if key:
|
||||||
|
return key
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(" 未检测到 DEEPSEEK_API_KEY")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
print(" 从 https://platform.deepseek.com/api_keys 获取 API Key")
|
||||||
|
print()
|
||||||
|
print(" 你也可以设置系统环境变量 DEEPSEEK_API_KEY 后重启")
|
||||||
|
print()
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = getpass.getpass(" 请输入你的 DeepSeek API Key: ").strip()
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
key = ""
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
print()
|
||||||
|
print(" ERROR: 未输入 API Key,程序退出。")
|
||||||
|
print()
|
||||||
|
print(" 支持以下方式设置 API Key(按优先级排列):")
|
||||||
|
print(" 1. 系统环境变量: DEEPSEEK_API_KEY=sk-your-key")
|
||||||
|
print(" 2. 脚本同目录 .env 文件: DEEPSEEK_API_KEY=sk-your-key")
|
||||||
|
print()
|
||||||
|
input(" 按 Enter 退出...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
env_file = os.path.join(BASE_DIR, ".env")
|
||||||
|
existing = {}
|
||||||
|
if os.path.exists(env_file):
|
||||||
|
with open(env_file, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition("=")
|
||||||
|
existing[k.strip()] = line
|
||||||
|
existing["DEEPSEEK_API_KEY"] = f"DEEPSEEK_API_KEY={key}"
|
||||||
|
|
||||||
|
with open(env_file, "w", encoding="utf-8") as f:
|
||||||
|
for line in existing.values():
|
||||||
|
f.write(line + "\n")
|
||||||
|
if "DEFAULT_MODEL" not in existing:
|
||||||
|
f.write("DEFAULT_MODEL=deepseek-v4-pro\n")
|
||||||
|
|
||||||
|
os.environ["DEEPSEEK_API_KEY"] = key
|
||||||
|
print()
|
||||||
|
print(f" API Key 已保存到: {env_file}")
|
||||||
|
print()
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
# ===================== 模块导入时自动加载 =====================
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# ===================== 全局配置常量 =====================
|
||||||
|
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "").strip()
|
||||||
|
DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "deepseek-v4-flash").strip()
|
||||||
|
DEEPSEEK_URL = os.environ.get(
|
||||||
|
"DEEPSEEK_URL", "https://api.deepseek.com/v1/chat/completions"
|
||||||
|
).strip()
|
||||||
|
DEEPSEEK_DEBUG = os.environ.get("DEEPSEEK_DEBUG", "0").strip() in (
|
||||||
|
"1",
|
||||||
|
"true",
|
||||||
|
"True",
|
||||||
|
"yes",
|
||||||
|
)
|
||||||
|
DEBUG_LOG = os.path.join(BASE_DIR, "proxy_debug.log")
|
||||||
|
|
||||||
|
# ===================== 模型映射表 =====================
|
||||||
|
# 将客户端请求中的模型名称映射为 DeepSeek 实际模型名称
|
||||||
|
# 默认模型名称(可被环境变量覆盖),当请求中未指定模型或指定的模型不在 MODEL_MAP 中时使用
|
||||||
|
MODEL_DEFAULT = os.environ.get("MODEL_DEFAULT", "deepseek-v4-flash")
|
||||||
|
|
||||||
|
## codex模型对应,为空则使用默认模型
|
||||||
|
MODEL_GTP5_5 = os.environ.get("MODEL_GTP5_5", "").strip() or MODEL_DEFAULT
|
||||||
|
MODEL_GTP5_4 = os.environ.get("MODEL_GTP5_4", "").strip() or MODEL_DEFAULT
|
||||||
|
MODEl_GPT5_4_MINI = os.environ.get("MODEl_GPT5_4_MINI", "").strip() or MODEL_DEFAULT
|
||||||
|
MODEL_GTP5_3_CODEX = os.environ.get("MODEL_GTP5_3_CODEX", "").strip() or MODEL_DEFAULT
|
||||||
|
MODEL_GTP5_2 = os.environ.get("MODEL_GTP5_2", "").strip() or MODEL_DEFAULT
|
||||||
|
|
||||||
|
MODEL_MAP = {
|
||||||
|
"gpt-5.5": MODEL_GTP5_5,
|
||||||
|
"gpt-5.4": MODEL_GTP5_4,
|
||||||
|
"gpt-5.4-mini": MODEl_GPT5_4_MINI,
|
||||||
|
"gpt-5.3-codex": MODEL_GTP5_3_CODEX,
|
||||||
|
"gpt-5.2": MODEL_GTP5_2,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def ensure_my_api_key() -> str:
|
||||||
|
"""确保 MY_API_KEY 已设置:系统环境变量 > .env > 交互输入
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
授权密钥字符串,若用户取消输入则退出进程。
|
||||||
|
"""
|
||||||
|
key = os.environ.get("MY_API_KEY", "").strip()
|
||||||
|
if key:
|
||||||
|
return key
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(" 未检测到 MY_API_KEY(授权密钥)")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
print(" MY_API_KEY 用于验证请求来源是否合法,请设置一个自定义密钥。")
|
||||||
|
print()
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = getpass.getpass(" 请输入你的 MY_API_KEY: ").strip()
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
key = ""
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
print()
|
||||||
|
print(" ERROR: 未输入 MY_API_KEY,程序退出。")
|
||||||
|
print()
|
||||||
|
print(" 你可以在 .env 文件中设置: MY_API_KEY=你的密钥")
|
||||||
|
print()
|
||||||
|
input(" 按 Enter 退出...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
env_file = os.path.join(BASE_DIR, ".env")
|
||||||
|
existing = {}
|
||||||
|
if os.path.exists(env_file):
|
||||||
|
with open(env_file, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition("=")
|
||||||
|
existing[k.strip()] = line
|
||||||
|
existing["MY_API_KEY"] = f"MY_API_KEY={key}"
|
||||||
|
|
||||||
|
with open(env_file, "w", encoding="utf-8") as f:
|
||||||
|
for line in existing.values():
|
||||||
|
f.write(line + "\n")
|
||||||
|
|
||||||
|
os.environ["MY_API_KEY"] = key
|
||||||
|
# 更新模块级变量,使后续 import 能拿到最新值
|
||||||
|
globals()["MY_API_KEY"] = key
|
||||||
|
print()
|
||||||
|
print(f" MY_API_KEY 已保存到: {env_file}")
|
||||||
|
print()
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
# ===================== 全局配置常量 =====================
|
||||||
|
# 简单防护,使用固定API Key 验证请求来源是否合法
|
||||||
|
MY_API_KEY = os.environ.get("MY_API_KEY", "").strip()
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# ==== codex_proxy 配置文件 ====
|
||||||
|
# 复制此文件为 .env 并填入你的配置
|
||||||
|
|
||||||
|
# 必填:DeepSeek API Key(从 https://platform.deepseek.com/api_keys 获取)
|
||||||
|
DEEPSEEK_API_KEY=*********
|
||||||
|
|
||||||
|
# 本服务的API Key,用于代理请求
|
||||||
|
MY_API_KEY=sk-1234567890
|
||||||
|
|
||||||
|
# 默认模型
|
||||||
|
MODEL_DEFAULT = deepseek-v4-flash
|
||||||
|
|
||||||
|
## codex模型对应,为空则使用默认模型
|
||||||
|
MODEL_GTP5_5 = deepseek-v4-pro
|
||||||
|
MODEL_GTP5_4 = deepseek-v4-flash
|
||||||
|
MODEl_GPT5_4_MINI = deepseek-v4-flash
|
||||||
|
MODEL_GTP5_3_CODEX = deepseek-v4-flash
|
||||||
|
MODEL_GTP5_2 = deepseek-v4-flash
|
||||||
|
|
||||||
|
# 可选:DeepSeek API 地址(默认官方地址,一般无需修改)
|
||||||
|
# DEEPSEEK_URL=https://api.deepseek.com/v1/chat/completions
|
||||||
|
|
||||||
|
# 可选:调试日志,设为 1 开启
|
||||||
|
# DEEPSEEK_DEBUG=1
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# ============================================================
|
||||||
|
# Codex DeepSeek Proxy — Docker Compose 编排
|
||||||
|
# ============================================================
|
||||||
|
# 使用:
|
||||||
|
# docker compose -f docker/docker-compose.yml up -d # 启动
|
||||||
|
# docker compose -f docker/docker-compose.yml logs -f # 日志
|
||||||
|
# docker compose -f docker/docker-compose.yml down # 停止
|
||||||
|
# docker compose -f docker/docker-compose.yml restart # 重启
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
services:
|
||||||
|
proxy:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
image: codex2deepseek:latest
|
||||||
|
container_name: codex2deepseek
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:12345:12345"
|
||||||
|
env_file:
|
||||||
|
- ../.env
|
||||||
|
restart: unless-stopped
|
||||||
|
user: "1000:1000"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
memory: 128M
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import socket; socket.create_connection(('localhost', 12345), timeout=5).close()"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
OpenAI Responses API ↔ DeepSeek Chat API 流式转发代理
|
||||||
|
启动:
|
||||||
|
python main.py --port 12345
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# ===================== 命令行参数 =====================
|
||||||
|
parser = argparse.ArgumentParser(description="Codex DeepSeek Proxy")
|
||||||
|
parser.add_argument(
|
||||||
|
"--port", type=int, default=12345, help="服务运行端口,默认 12345"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--host", type=str, default="0.0.0.0", help="绑定地址,默认 0.0.0.0"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
action="store_true",
|
||||||
|
help="启用调试日志(覆盖 DEEPSEEK_DEBUG 环境变量)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# ===================== 初始化 API Key =====================
|
||||||
|
# 必须在 FastAPI 应用创建前完成,因为 config 模块在 import 时加载 .env
|
||||||
|
from app.config import ensure_api_key, ensure_my_api_key
|
||||||
|
|
||||||
|
key = ensure_api_key()
|
||||||
|
if not key:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
my_key = ensure_my_api_key()
|
||||||
|
if not my_key:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# ===================== FastAPI 应用创建 =====================
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from routers import register_routes
|
||||||
|
from routers.lifespan import create_lifespan
|
||||||
|
from routers.middleware import setup_middleware
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Codex DeepSeek Proxy",
|
||||||
|
description="OpenAI Responses API ↔ DeepSeek Chat API 流式转发代理",
|
||||||
|
version="0.2.0",
|
||||||
|
lifespan=create_lifespan,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 配置中间件
|
||||||
|
setup_middleware(app)
|
||||||
|
|
||||||
|
# 注册路由
|
||||||
|
register_routes(app)
|
||||||
|
|
||||||
|
# ===================== 服务启动 =====================
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(
|
||||||
|
app,
|
||||||
|
host=args.host,
|
||||||
|
port=args.port,
|
||||||
|
access_log=True,
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[project]
|
||||||
|
name = "codex2deepseek"
|
||||||
|
version = "0.2.0"
|
||||||
|
description = "OpenAI Responses API ↔ DeepSeek Chat API 流式转发代理"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"fastapi>=0.136.0",
|
||||||
|
"uvicorn>=0.44.0",
|
||||||
|
"requests>=2.28",
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
"""路由注册入口
|
||||||
|
|
||||||
|
参照 PineSoundDesktop 项目结构,通过 register_routes() 统一注册所有路由。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .proxy import router
|
||||||
|
|
||||||
|
|
||||||
|
def register_routes(app):
|
||||||
|
"""注册所有 API 路由"""
|
||||||
|
app.include_router(router)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
"""
|
||||||
|
应用生命周期管理
|
||||||
|
|
||||||
|
通过 FastAPI lifespan 机制处理启动和关闭事件。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def create_lifespan(app):
|
||||||
|
"""应用生命周期上下文管理器"""
|
||||||
|
# ==================== 启动事件 ====================
|
||||||
|
print("codex_deepseek_proxy starting ...")
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
# ==================== 关闭事件 ====================
|
||||||
|
print("codex_deepseek_proxy shutting down ...")
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
中间件配置
|
||||||
|
|
||||||
|
参照 PineSoundDesktop 项目结构集中管理中间件。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from fastapi import Response
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from app.config import MODEL_MAP, DEFAULT_MODEL, MY_API_KEY
|
||||||
|
|
||||||
|
|
||||||
|
def setup_middleware(app):
|
||||||
|
"""配置所有中间件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app: FastAPI 应用实例
|
||||||
|
"""
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.middleware("http")
|
||||||
|
async def auth_check(request, call_next):
|
||||||
|
"""验证请求携带的 API Key 是否合法"""
|
||||||
|
# OPTIONS 预检请求跳过验证
|
||||||
|
if request.method == "OPTIONS":
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
|
auth = request.headers.get("Authorization", "")
|
||||||
|
if auth.startswith("Bearer "):
|
||||||
|
token = auth[7:]
|
||||||
|
else:
|
||||||
|
token = ""
|
||||||
|
|
||||||
|
if token != MY_API_KEY:
|
||||||
|
return Response(
|
||||||
|
content=json.dumps({
|
||||||
|
"error": {"message": "Invalid API Key", "type": "auth_error"},
|
||||||
|
}),
|
||||||
|
status_code=401,
|
||||||
|
media_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
|
@app.middleware("http")
|
||||||
|
async def resolve_model(request, call_next):
|
||||||
|
"""拦截请求中的模型名称,按 MODEL_MAP 进行替换"""
|
||||||
|
if request.method == "POST":
|
||||||
|
body = await request.body()
|
||||||
|
if body:
|
||||||
|
try:
|
||||||
|
data = json.loads(body)
|
||||||
|
original = data.get("model")
|
||||||
|
if original:
|
||||||
|
resolved = MODEL_MAP.get(original, DEFAULT_MODEL)
|
||||||
|
request.state.resolved_model = resolved
|
||||||
|
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||||
|
pass
|
||||||
|
response = await call_next(request)
|
||||||
|
return response
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"""
|
||||||
|
代理路由模块
|
||||||
|
|
||||||
|
提供 OpenAI Responses API ↔ DeepSeek Chat API 的流式转发端点。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
|
||||||
|
from app.config import MODEL_DEFAULT, DEEPSEEK_DEBUG
|
||||||
|
from services.converter import extract_messages
|
||||||
|
from services.stream import create_sse_generator, _log_debug
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
async def _handle_proxy(request: Request):
|
||||||
|
"""处理 /responses 系列请求的核心逻辑"""
|
||||||
|
req_data = await request.json()
|
||||||
|
messages, tools, tool_choice = extract_messages(req_data)
|
||||||
|
# 中间件 resolve_model 已解析的模型优先级最高
|
||||||
|
effective_model = (
|
||||||
|
getattr(request.state, "resolved_model", None)
|
||||||
|
or req_data.get("model")
|
||||||
|
or MODEL_DEFAULT
|
||||||
|
)
|
||||||
|
response_id = f"resp_{uuid.uuid4().hex[:12]}"
|
||||||
|
|
||||||
|
if DEEPSEEK_DEBUG:
|
||||||
|
_log_debug(req_data, messages, tools, tool_choice, request.url.path)
|
||||||
|
|
||||||
|
stream_gen = create_sse_generator(
|
||||||
|
messages, tools, tool_choice, effective_model, response_id, debug_path=request.url.path
|
||||||
|
)
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
stream_gen(),
|
||||||
|
media_type="text/event-stream",
|
||||||
|
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/responses")
|
||||||
|
async def route_responses(request: Request):
|
||||||
|
"""OpenAI Responses API 格式的流式代理端点"""
|
||||||
|
return await _handle_proxy(request)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v1/responses")
|
||||||
|
async def route_v1_responses(request: Request):
|
||||||
|
"""OpenAI Responses API (v1) 格式的流式代理端点"""
|
||||||
|
return await _handle_proxy(request)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v1/chat/completions")
|
||||||
|
async def route_v1_chat(request: Request):
|
||||||
|
"""Chat Completions API 格式的流式代理端点(兼容模式)"""
|
||||||
|
return await _handle_proxy(request)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""代理业务逻辑服务模块"""
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
"""
|
||||||
|
消息格式转换模块
|
||||||
|
|
||||||
|
将 OpenAI Responses API 请求格式转换为 DeepSeek Chat Completions API 格式。
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_schema(obj):
|
||||||
|
"""递归清除 JSON Schema 中 DeepSeek 不支持的字段"""
|
||||||
|
if not isinstance(obj, dict):
|
||||||
|
return obj
|
||||||
|
cleaned = {}
|
||||||
|
for k, v in obj.items():
|
||||||
|
if k in ("additionalProperties", "strict"):
|
||||||
|
continue
|
||||||
|
if isinstance(v, dict):
|
||||||
|
cleaned[k] = _clean_schema(v)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
cleaned[k] = [_clean_schema(i) if isinstance(i, dict) else i for i in v]
|
||||||
|
else:
|
||||||
|
cleaned[k] = v
|
||||||
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_tools(tools: list) -> list:
|
||||||
|
"""将工具定义从 Responses API 格式转换为 Chat Completions API 格式"""
|
||||||
|
result = []
|
||||||
|
for tool in tools:
|
||||||
|
if not isinstance(tool, dict):
|
||||||
|
continue
|
||||||
|
if tool.get("type") != "function":
|
||||||
|
continue
|
||||||
|
func = {
|
||||||
|
"name": tool.get("name", ""),
|
||||||
|
"description": tool.get("description", ""),
|
||||||
|
}
|
||||||
|
if "parameters" in tool:
|
||||||
|
func["parameters"] = _clean_schema(tool["parameters"])
|
||||||
|
result.append({"type": "function", "function": func})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_tool_choice(tc):
|
||||||
|
"""将 tool_choice 从 Responses API 格式转换为 Chat Completions 格式"""
|
||||||
|
if tc is None:
|
||||||
|
return "auto"
|
||||||
|
if isinstance(tc, str):
|
||||||
|
return tc
|
||||||
|
if isinstance(tc, dict) and tc.get("type") == "function":
|
||||||
|
return {"type": "function", "function": {"name": tc.get("name", "")}}
|
||||||
|
return "auto"
|
||||||
|
|
||||||
|
|
||||||
|
def extract_messages(data: dict):
|
||||||
|
"""
|
||||||
|
从 Responses API 请求中提取 messages 列表、tools 列表和 tool_choice。
|
||||||
|
|
||||||
|
支持两种输入格式:
|
||||||
|
- Responses API(input/instructions 字段)
|
||||||
|
- Chat Completions API(messages 字段)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(messages, tools, tool_choice)
|
||||||
|
"""
|
||||||
|
ROLE_MAP = {"developer": "system"}
|
||||||
|
raw_tools = data.get("tools", [])
|
||||||
|
tools = _convert_tools(raw_tools)
|
||||||
|
tool_choice = _convert_tool_choice(data.get("tool_choice"))
|
||||||
|
|
||||||
|
if "input" not in data:
|
||||||
|
if "messages" in data:
|
||||||
|
return data["messages"], tools, tool_choice
|
||||||
|
return [], tools, tool_choice
|
||||||
|
|
||||||
|
inp = data["input"]
|
||||||
|
if isinstance(inp, str):
|
||||||
|
messages = []
|
||||||
|
if "instructions" in data and data["instructions"]:
|
||||||
|
messages.append({"role": "system", "content": data["instructions"]})
|
||||||
|
messages.append({"role": "user", "content": inp})
|
||||||
|
return messages, tools, tool_choice
|
||||||
|
|
||||||
|
if not isinstance(inp, list):
|
||||||
|
return [], tools, tool_choice
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
if "instructions" in data and data["instructions"]:
|
||||||
|
messages.append({"role": "system", "content": data["instructions"]})
|
||||||
|
|
||||||
|
pending_tool_calls = []
|
||||||
|
pending_reasoning = ""
|
||||||
|
|
||||||
|
def _flush_tool_calls():
|
||||||
|
nonlocal pending_tool_calls, pending_reasoning
|
||||||
|
if pending_tool_calls:
|
||||||
|
msg = {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "",
|
||||||
|
"tool_calls": pending_tool_calls,
|
||||||
|
}
|
||||||
|
if pending_reasoning:
|
||||||
|
msg["reasoning_content"] = pending_reasoning
|
||||||
|
messages.append(msg)
|
||||||
|
pending_tool_calls = []
|
||||||
|
pending_reasoning = ""
|
||||||
|
|
||||||
|
for item in inp:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
item_type = item.get("type")
|
||||||
|
|
||||||
|
if item_type == "message":
|
||||||
|
_flush_tool_calls()
|
||||||
|
role = item.get("role", "user")
|
||||||
|
role = ROLE_MAP.get(role, role)
|
||||||
|
content = item.get("content", "")
|
||||||
|
if isinstance(content, list):
|
||||||
|
texts = []
|
||||||
|
tool_calls = []
|
||||||
|
for c in content:
|
||||||
|
if not isinstance(c, dict):
|
||||||
|
continue
|
||||||
|
c_type = c.get("type")
|
||||||
|
if c_type in ("text", "input_text", "output_text"):
|
||||||
|
t = c.get("text", "")
|
||||||
|
if t.strip():
|
||||||
|
texts.append(t)
|
||||||
|
elif c_type == "tool_call":
|
||||||
|
tool_calls.append({
|
||||||
|
"id": c.get("id", ""),
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": c.get("name", ""),
|
||||||
|
"arguments": c.get("arguments", ""),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
text_content = "\n".join(texts)
|
||||||
|
if tool_calls:
|
||||||
|
msg = {"role": role, "content": text_content or ""}
|
||||||
|
msg["tool_calls"] = tool_calls
|
||||||
|
if item.get("reasoning_content"):
|
||||||
|
msg["reasoning_content"] = item["reasoning_content"]
|
||||||
|
messages.append(msg)
|
||||||
|
elif text_content:
|
||||||
|
msg = {"role": role, "content": text_content}
|
||||||
|
if item.get("reasoning_content"):
|
||||||
|
msg["reasoning_content"] = item["reasoning_content"]
|
||||||
|
messages.append(msg)
|
||||||
|
elif isinstance(content, str) and content.strip():
|
||||||
|
msg = {"role": role, "content": content.strip()}
|
||||||
|
if item.get("reasoning_content"):
|
||||||
|
msg["reasoning_content"] = item["reasoning_content"]
|
||||||
|
messages.append(msg)
|
||||||
|
|
||||||
|
elif item_type == "function_call":
|
||||||
|
pending_tool_calls.append({
|
||||||
|
"id": item.get("call_id", ""),
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": item.get("name", ""),
|
||||||
|
"arguments": item.get("arguments", ""),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if item.get("reasoning_content") and not pending_reasoning:
|
||||||
|
pending_reasoning = item["reasoning_content"]
|
||||||
|
|
||||||
|
elif item_type == "function_call_output":
|
||||||
|
_flush_tool_calls()
|
||||||
|
messages.append({
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": item.get("call_id", ""),
|
||||||
|
"content": item.get("output", ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
_flush_tool_calls()
|
||||||
|
|
||||||
|
# ---- 重排消息:确保 tool 消息紧跟对应的 assistant 消息 ----
|
||||||
|
reordered = []
|
||||||
|
i = 0
|
||||||
|
while i < len(messages):
|
||||||
|
msg = messages[i]
|
||||||
|
if msg.get("role") == "assistant" and msg.get("tool_calls"):
|
||||||
|
expected_ids = {tc["id"] for tc in msg["tool_calls"]}
|
||||||
|
tool_msgs = []
|
||||||
|
non_tool_msgs = []
|
||||||
|
j = i + 1
|
||||||
|
while j < len(messages) and expected_ids:
|
||||||
|
nxt = messages[j]
|
||||||
|
if nxt.get("role") == "tool" and nxt.get("tool_call_id") in expected_ids:
|
||||||
|
expected_ids.remove(nxt["tool_call_id"])
|
||||||
|
tool_msgs.append(nxt)
|
||||||
|
elif nxt.get("role") in ("system", "developer"):
|
||||||
|
non_tool_msgs.append(nxt)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
j += 1
|
||||||
|
reordered.extend(non_tool_msgs)
|
||||||
|
reordered.append(msg)
|
||||||
|
reordered.extend(tool_msgs)
|
||||||
|
i = j
|
||||||
|
else:
|
||||||
|
reordered.append(msg)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return reordered, tools, tool_choice
|
||||||
@@ -0,0 +1,551 @@
|
|||||||
|
"""
|
||||||
|
SSE 流式生成模块
|
||||||
|
|
||||||
|
将 DeepSeek Chat Completions API 的流式响应转换为
|
||||||
|
OpenAI Responses API 格式的 SSE 事件流。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from app.config import DEEPSEEK_API_KEY, DEEPSEEK_URL, DEEPSEEK_DEBUG, DEBUG_LOG
|
||||||
|
|
||||||
|
|
||||||
|
def _log_debug(req_data, messages, tools, tool_choice, debug_path):
|
||||||
|
"""记录调试日志到文件"""
|
||||||
|
with open(DEBUG_LOG, "a", encoding="utf-8") as f:
|
||||||
|
f.write(f"\n--- [{datetime.now()}] PATH={debug_path} ---\n")
|
||||||
|
f.write(f"Request body:\n{json.dumps(req_data, indent=2, ensure_ascii=False)}\n")
|
||||||
|
f.write(f"Messages:\n{json.dumps(messages, indent=2, ensure_ascii=False)}\n")
|
||||||
|
if tools:
|
||||||
|
f.write(f"Tools count: {len(tools)}\n")
|
||||||
|
f.write(f"Tool choice: {tool_choice}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _log_debug_error(payload, messages, tools, err_msg, status_code, body):
|
||||||
|
"""记录错误调试日志"""
|
||||||
|
with open(DEBUG_LOG, "a", encoding="utf-8") as f:
|
||||||
|
f.write(f"ERROR: {err_msg}\n")
|
||||||
|
f.write(f"Payload sent (tools={len(tools)}, msgs={len(messages)}):\n")
|
||||||
|
payload_copy = dict(payload)
|
||||||
|
payload_copy.pop("messages", None)
|
||||||
|
payload_copy.pop("tools", None)
|
||||||
|
f.write(json.dumps(payload_copy, indent=2, ensure_ascii=False) + "\n")
|
||||||
|
f.write(f"Messages ({len(messages)}):\n")
|
||||||
|
f.write(json.dumps(messages, indent=2, ensure_ascii=False)[:3000] + "\n")
|
||||||
|
f.write(f"Tools ({len(tools)}):\n")
|
||||||
|
tools_str = json.dumps(tools, indent=2, ensure_ascii=False)
|
||||||
|
f.write(tools_str[:5000] + ("...(truncated)" if len(tools_str) > 5000 else "") + "\n")
|
||||||
|
total_size = len(json.dumps(payload, ensure_ascii=False))
|
||||||
|
f.write(f"Total payload size: {total_size} bytes ({total_size/1024:.1f} KB)\n")
|
||||||
|
|
||||||
|
|
||||||
|
def create_sse_generator(messages, tools, tool_choice, effective_model, response_id, debug_path=""):
|
||||||
|
"""创建 SSE 流式事件生成器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
messages: 转换后的 Chat Completions 格式消息列表
|
||||||
|
tools: 转换后的工具定义列表
|
||||||
|
tool_choice: 工具选择策略
|
||||||
|
effective_model: 使用的模型名称
|
||||||
|
response_id: 响应 ID
|
||||||
|
debug_path: 请求路径(用于调试日志)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
生成 SSE 事件字符串的生成器函数
|
||||||
|
"""
|
||||||
|
|
||||||
|
def generate():
|
||||||
|
if not messages:
|
||||||
|
yield "event: response.completed\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.completed",
|
||||||
|
"response": {
|
||||||
|
"id": response_id,
|
||||||
|
"object": "response",
|
||||||
|
"status": "completed",
|
||||||
|
"model": effective_model,
|
||||||
|
"output": [],
|
||||||
|
"usage": {
|
||||||
|
"input_tokens": 0,
|
||||||
|
"output_tokens": 0,
|
||||||
|
"total_tokens": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# response.created
|
||||||
|
yield "event: response.created\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.created",
|
||||||
|
"response": {
|
||||||
|
"id": response_id,
|
||||||
|
"object": "response",
|
||||||
|
"status": "in_progress",
|
||||||
|
"model": effective_model,
|
||||||
|
"output": [],
|
||||||
|
"usage": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# response.in_progress
|
||||||
|
yield "event: response.in_progress\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.in_progress",
|
||||||
|
"response": {
|
||||||
|
"id": response_id,
|
||||||
|
"object": "response",
|
||||||
|
"status": "in_progress",
|
||||||
|
"model": effective_model,
|
||||||
|
"output": [],
|
||||||
|
"usage": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建 DeepSeek 请求
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {DEEPSEEK_API_KEY}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"model": effective_model,
|
||||||
|
"messages": messages,
|
||||||
|
"stream": True,
|
||||||
|
"stream_options": {"include_usage": True},
|
||||||
|
"thinking": {"type": "disabled"},
|
||||||
|
}
|
||||||
|
if tools:
|
||||||
|
payload["tools"] = tools
|
||||||
|
if tool_choice != "auto":
|
||||||
|
payload["tool_choice"] = tool_choice
|
||||||
|
|
||||||
|
# 状态跟踪
|
||||||
|
text_item_id = f"item_{uuid.uuid4().hex[:12]}"
|
||||||
|
full_text = ""
|
||||||
|
full_reasoning = ""
|
||||||
|
has_text = False
|
||||||
|
text_started = False
|
||||||
|
|
||||||
|
# 工具调用累积: index → {id, name, arguments, item_id, started}
|
||||||
|
tool_calls_acc = {}
|
||||||
|
|
||||||
|
input_tokens = 0
|
||||||
|
output_tokens = 0
|
||||||
|
seq = 0
|
||||||
|
|
||||||
|
upstream = None
|
||||||
|
try:
|
||||||
|
upstream = requests.post(
|
||||||
|
DEEPSEEK_URL,
|
||||||
|
headers=headers,
|
||||||
|
json=payload,
|
||||||
|
stream=True,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
upstream.raise_for_status()
|
||||||
|
for line in upstream.iter_lines():
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
line = line.decode("utf-8")
|
||||||
|
if not line.startswith("data: "):
|
||||||
|
continue
|
||||||
|
raw = line[6:].strip()
|
||||||
|
if raw == "[DONE]":
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
chunk = json.loads(raw)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
usage = chunk.get("usage")
|
||||||
|
if usage:
|
||||||
|
input_tokens = usage.get("prompt_tokens", 0)
|
||||||
|
output_tokens = usage.get("completion_tokens", 0)
|
||||||
|
|
||||||
|
if "error" in chunk:
|
||||||
|
err = chunk["error"]
|
||||||
|
raise Exception(
|
||||||
|
f"DeepSeek API error: {err.get('message', str(err))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "choices" not in chunk or not chunk["choices"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
delta = chunk["choices"][0].get("delta", {})
|
||||||
|
|
||||||
|
# ---- 捕获 reasoning_content ----
|
||||||
|
reasoning_delta = delta.get("reasoning_content", "")
|
||||||
|
if reasoning_delta:
|
||||||
|
full_reasoning += reasoning_delta
|
||||||
|
|
||||||
|
# ---- 处理文本内容 ----
|
||||||
|
content = delta.get("content", "")
|
||||||
|
if content:
|
||||||
|
if not text_started:
|
||||||
|
text_started = True
|
||||||
|
has_text = True
|
||||||
|
yield "event: response.output_item.added\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.output_item.added",
|
||||||
|
"output_index": 0,
|
||||||
|
"item": {
|
||||||
|
"id": text_item_id,
|
||||||
|
"type": "message",
|
||||||
|
"status": "in_progress",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
yield "event: response.content_part.added\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.content_part.added",
|
||||||
|
"item_id": text_item_id,
|
||||||
|
"output_index": 0,
|
||||||
|
"content_index": 0,
|
||||||
|
"part": {"type": "text", "text": ""},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
full_text += content
|
||||||
|
seq += 1
|
||||||
|
yield "event: response.output_text.delta\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.output_text.delta",
|
||||||
|
"delta": content,
|
||||||
|
"item_id": text_item_id,
|
||||||
|
"output_index": 0,
|
||||||
|
"content_index": 0,
|
||||||
|
"sequence_number": seq,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---- 处理工具调用 ----
|
||||||
|
for tc in delta.get("tool_calls", []):
|
||||||
|
idx = tc.get("index", 0)
|
||||||
|
if idx not in tool_calls_acc:
|
||||||
|
item_id = f"item_{uuid.uuid4().hex[:12]}"
|
||||||
|
tool_calls_acc[idx] = {
|
||||||
|
"id": "",
|
||||||
|
"name": "",
|
||||||
|
"arguments": "",
|
||||||
|
"item_id": item_id,
|
||||||
|
"started": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
acc = tool_calls_acc[idx]
|
||||||
|
if tc.get("id"):
|
||||||
|
acc["id"] = tc["id"]
|
||||||
|
func = tc.get("function", {})
|
||||||
|
if func.get("name"):
|
||||||
|
acc["name"] = func["name"]
|
||||||
|
|
||||||
|
args_delta = func.get("arguments", "")
|
||||||
|
if args_delta:
|
||||||
|
acc["arguments"] += args_delta
|
||||||
|
out_idx = (
|
||||||
|
1 if has_text else 0
|
||||||
|
) + sorted(tool_calls_acc.keys()).index(idx)
|
||||||
|
|
||||||
|
if not acc["started"]:
|
||||||
|
acc["started"] = True
|
||||||
|
yield "event: response.output_item.added\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.output_item.added",
|
||||||
|
"output_index": out_idx,
|
||||||
|
"item": {
|
||||||
|
"id": acc["item_id"],
|
||||||
|
"type": "function_call",
|
||||||
|
"status": "in_progress",
|
||||||
|
"call_id": acc["id"],
|
||||||
|
"name": acc["name"],
|
||||||
|
"arguments": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
yield "event: response.function_call_arguments.delta\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.function_call_arguments.delta",
|
||||||
|
"item_id": acc["item_id"],
|
||||||
|
"output_index": out_idx,
|
||||||
|
"delta": args_delta,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ===== 流结束后发出完成事件 =====
|
||||||
|
|
||||||
|
# 文本完成
|
||||||
|
if has_text:
|
||||||
|
yield "event: response.output_text.done\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.output_text.done",
|
||||||
|
"text": full_text,
|
||||||
|
"item_id": text_item_id,
|
||||||
|
"output_index": 0,
|
||||||
|
"content_index": 0,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
yield "event: response.content_part.done\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.content_part.done",
|
||||||
|
"item_id": text_item_id,
|
||||||
|
"output_index": 0,
|
||||||
|
"content_index": 0,
|
||||||
|
"part": {"type": "text", "text": full_text},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
text_output_item = {
|
||||||
|
"id": text_item_id,
|
||||||
|
"type": "message",
|
||||||
|
"status": "completed",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{"type": "text", "text": full_text}],
|
||||||
|
}
|
||||||
|
if full_reasoning:
|
||||||
|
text_output_item["reasoning_content"] = full_reasoning
|
||||||
|
yield "event: response.output_item.done\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.output_item.done",
|
||||||
|
"output_index": 0,
|
||||||
|
"item": text_output_item,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 工具调用完成
|
||||||
|
output_items = []
|
||||||
|
if has_text:
|
||||||
|
output_items.append({
|
||||||
|
"id": text_item_id,
|
||||||
|
"type": "message",
|
||||||
|
"status": "completed",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [{"type": "text", "text": full_text}],
|
||||||
|
**({"reasoning_content": full_reasoning} if full_reasoning else {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
for idx in sorted(tool_calls_acc.keys()):
|
||||||
|
acc = tool_calls_acc[idx]
|
||||||
|
out_idx = (1 if has_text else 0) + sorted(tool_calls_acc.keys()).index(
|
||||||
|
idx
|
||||||
|
)
|
||||||
|
|
||||||
|
yield "event: response.function_call_arguments.done\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.function_call_arguments.done",
|
||||||
|
"item_id": acc["item_id"],
|
||||||
|
"output_index": out_idx,
|
||||||
|
"arguments": acc["arguments"],
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
func_item = {
|
||||||
|
"id": acc["item_id"],
|
||||||
|
"type": "function_call",
|
||||||
|
"status": "completed",
|
||||||
|
"call_id": acc["id"],
|
||||||
|
"name": acc["name"],
|
||||||
|
"arguments": acc["arguments"],
|
||||||
|
}
|
||||||
|
if full_reasoning:
|
||||||
|
func_item["reasoning_content"] = full_reasoning
|
||||||
|
yield "event: response.output_item.done\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.output_item.done",
|
||||||
|
"output_index": out_idx,
|
||||||
|
"item": func_item,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
output_items.append({
|
||||||
|
"id": acc["item_id"],
|
||||||
|
"type": "function_call",
|
||||||
|
"status": "completed",
|
||||||
|
"call_id": acc["id"],
|
||||||
|
"name": acc["name"],
|
||||||
|
"arguments": acc["arguments"],
|
||||||
|
**({"reasoning_content": full_reasoning} if full_reasoning else {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
# response.completed
|
||||||
|
yield "event: response.completed\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.completed",
|
||||||
|
"response": {
|
||||||
|
"id": response_id,
|
||||||
|
"object": "response",
|
||||||
|
"status": "completed",
|
||||||
|
"model": effective_model,
|
||||||
|
"output": output_items,
|
||||||
|
"usage": {
|
||||||
|
"input_tokens": input_tokens
|
||||||
|
or max(1, len(json.dumps(messages)) // 4),
|
||||||
|
"output_tokens": output_tokens
|
||||||
|
or max(1, len(full_text) // 4),
|
||||||
|
"total_tokens": (input_tokens + output_tokens)
|
||||||
|
or max(
|
||||||
|
1,
|
||||||
|
len(json.dumps(messages)) // 4 + len(full_text) // 4,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
body = ""
|
||||||
|
try:
|
||||||
|
if upstream is not None:
|
||||||
|
body = upstream.text[:2000]
|
||||||
|
except Exception:
|
||||||
|
body = "(unable to read error body)"
|
||||||
|
err_msg = f"DeepSeek API {e.response.status_code}: {body}"
|
||||||
|
if DEEPSEEK_DEBUG:
|
||||||
|
_log_debug_error(payload, messages, tools, err_msg, e.response.status_code, body)
|
||||||
|
yield "event: response.failed\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.failed",
|
||||||
|
"response": {
|
||||||
|
"id": response_id,
|
||||||
|
"object": "response",
|
||||||
|
"status": "failed",
|
||||||
|
"model": effective_model,
|
||||||
|
"error": {
|
||||||
|
"message": err_msg,
|
||||||
|
"type": "upstream_error",
|
||||||
|
},
|
||||||
|
"output": [],
|
||||||
|
"usage": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
yield "event: response.failed\n"
|
||||||
|
yield (
|
||||||
|
"data: "
|
||||||
|
+ json.dumps(
|
||||||
|
{
|
||||||
|
"type": "response.failed",
|
||||||
|
"response": {
|
||||||
|
"id": response_id,
|
||||||
|
"object": "response",
|
||||||
|
"status": "failed",
|
||||||
|
"model": effective_model,
|
||||||
|
"error": {
|
||||||
|
"message": str(e),
|
||||||
|
"type": "upstream_error",
|
||||||
|
},
|
||||||
|
"output": [],
|
||||||
|
"usage": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if upstream is not None:
|
||||||
|
try:
|
||||||
|
upstream.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return generate
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-doc"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.13.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2026.5.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "3.4.7"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codex2deepseek"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "fastapi" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "uvicorn" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "fastapi", specifier = ">=0.136.0" },
|
||||||
|
{ name = "requests", specifier = ">=2.28" },
|
||||||
|
{ name = "uvicorn", specifier = ">=0.44.0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi"
|
||||||
|
version = "0.136.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-doc" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "starlette" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.16"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.13.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.46.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.34.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "charset-normalizer" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "starlette"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/95/66/4d20cdf39a8d6a51e663b7038e3b828ff211d3891a43a713fe7e4643f3a8/starlette-1.1.0.tar.gz", hash = "sha256:e83c7fe0ddecd8719c5b840080325aec0260acec86e9832899e377b91d65e90f", size = 2660060, upload-time = "2026-05-23T16:55:41.376Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/79/920b8e0a8b20f793e8d64855095cb8febabf6175b8550b6f7a547d813891/starlette-1.1.0-py3-none-any.whl", hash = "sha256:7f0dfd38e428aad5cb6f9f667f0ca1d2d8ca3f3385dccac8305f79ec98458382", size = 72899, upload-time = "2026-05-23T16:55:39.201Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspection"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uvicorn"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "h11" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/f6544ba992ddb9a6077343a576f9844f7f8f06ab819aefd00206e9255f18/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37", size = 91074, upload-time = "2026-05-24T12:08:41.925Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/be/72532be3da7acc5fdfbccdb95215cd04f995a0886532a5b423f929cda4cc/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad", size = 71410, upload-time = "2026-05-24T12:08:40.258Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user