Compare commits
170 Commits
feat/moder
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c429ad0c | ||
|
|
71ccf230e9 | ||
|
|
2efb1268b7 | ||
|
|
5ab8171552 | ||
|
|
c0c9973e23 | ||
|
|
4abd553302 | ||
|
|
6f977450c9 | ||
|
|
cb99a83364 | ||
|
|
716876fd92 | ||
|
|
74aca298a7 | ||
|
|
6bcacaf94a | ||
|
|
88d2fd2c86 | ||
|
|
1aa28430e0 | ||
|
|
2e2fa36896 | ||
|
|
ac79d6c0d0 | ||
|
|
eaf440a6a9 | ||
|
|
fe348b070e | ||
|
|
ab83f05d63 | ||
|
|
cef9e0bc52 | ||
|
|
039814b281 | ||
|
|
ea2f08e443 | ||
|
|
75fe89ac2e | ||
|
|
5deecc67ce | ||
|
|
b53eb61fbf | ||
|
|
fe4e8271bf | ||
|
|
0d2b39ddd7 | ||
|
|
2789f191d4 | ||
| f5a9ce2c12 | |||
| 79a99f2dba | |||
| 335a06d5ea | |||
| a70933c44c | |||
| 7dd0886193 | |||
| 89928a68e7 | |||
| 37c8d4127e | |||
| c55b4c7a83 | |||
| 9b00d02365 | |||
| 72f2e81291 | |||
| f0b29759cd | |||
| f18d370354 | |||
| 336f614041 | |||
| 3b25b9cfb4 | |||
| 6bc04897ab | |||
| dd8f2d23f6 | |||
| c90cd88dd4 | |||
| c15cb0416e | |||
| 647e63eb04 | |||
| 39af416625 | |||
| 34627054b1 | |||
| 8ee2eccc55 | |||
| 2792fc7bda | |||
| 4d455da18c | |||
| 52312b0a06 | |||
| 982517a1c8 | |||
| 767539fabd | |||
| 3423d5752f | |||
| 0f2d84bd00 | |||
| 3947fb3587 | |||
| bbe067e2b9 | |||
| 242839d0bb | |||
| 4d69d54e20 | |||
| 63c8a42215 | |||
| 1096e85720 | |||
| 6fd1f2ee5c | |||
| de695424d3 | |||
| e29cdadf18 | |||
| f5ded344f6 | |||
| f1d02f9d16 | |||
| ca1abb008a | |||
| dfb0794ee2 | |||
| 892f0c5002 | |||
| 6123c45c10 | |||
| 08a409c00f | |||
| a1c3eb4b25 | |||
| d4e455ba9a | |||
| 2ebe811c34 | |||
| 6f8c14c329 | |||
| eee648038a | |||
| 1d12d42e80 | |||
| 6876edae62 | |||
| 186f64d544 | |||
| 134bd1ca68 | |||
| 9212cf8afb | |||
| faba862336 | |||
| 3a0a8d4c86 | |||
| 308e91c61c | |||
| 9377cfabb8 | |||
| e6958f277b | |||
| bb4900f997 | |||
| 6bfa0cb27c | |||
| 90c93dbe07 | |||
| ffb10827bb | |||
| df8aafe1d8 | |||
| ae02a7e88c | |||
| fb7fa8c082 | |||
| 3743dec00d | |||
| 311b0aec6f | |||
| cab7843f95 | |||
| d4c079d11e | |||
| 151fb14b25 | |||
| 565e9cd8a4 | |||
| 2b121077c6 | |||
| 6cafd70b34 | |||
| 76ddfaabc7 | |||
| c3abbf9bfe | |||
| a0f38df113 | |||
| 450c9009a5 | |||
| 87040915ee | |||
| bab415ba83 | |||
| e186e74b63 | |||
| 8e9ab5008c | |||
| be3c939955 | |||
| fed36ff079 | |||
| 063e158989 | |||
| dac3ebb5a7 | |||
| 2f75784ea6 | |||
| 2b30a3f0dc | |||
| cfa355a7a5 | |||
| a228095220 | |||
| 93e3f17a67 | |||
| 9019f6c48e | |||
| c345238eaa | |||
| d4406a60fd | |||
| 5c021b81cb | |||
| 57d77dc819 | |||
| 2f2841c16c | |||
| 37c6814b2d | |||
| 314da7ae44 | |||
| dc007a30a9 | |||
| ce5cfc4463 | |||
| 022269040f | |||
| 1dc7df71ef | |||
| 45458159d4 | |||
| a7099d37f3 | |||
| fa99d04595 | |||
| 5d52d02319 | |||
| 08bebcd257 | |||
| f32f49fb85 | |||
| 62dce1d3d7 | |||
| 5ec5946a90 | |||
| e494c88977 | |||
| 7e4069f3b6 | |||
| 3ba1c50eb6 | |||
| d84cc1d859 | |||
| 283b7d498c | |||
| 42f3a41b06 | |||
| 93ec47f198 | |||
| cef4859574 | |||
| 4cc818b98b | |||
| adb0bafc0a | |||
| 44d94dace5 | |||
| c65cf35a85 | |||
| eed21ce6a5 | |||
| d6e4221a7b | |||
| cb5efd5550 | |||
| 71626468e2 | |||
| 534e4fe8e0 | |||
| 65d5020fc7 | |||
| d44c2dae74 | |||
| 59d3c406ab | |||
| a4e3411584 | |||
| 9aa917bce5 | |||
| 04913dbe42 | |||
| ca51e168dc | |||
| e9a20a091f | |||
| 265702b894 | |||
| f151ad2c30 | |||
| 146ebb2b4a | |||
| b558059dc8 | |||
| 9364989be3 | |||
| fd6d17e3c2 |
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
__pycache__/
|
||||||
|
# CRUD definition directories (auto-generated by Sage platform)
|
||||||
|
wwwroot/llm/
|
||||||
|
wwwroot/llm_api_map/
|
||||||
|
wwwroot/llmcatelog_list/
|
||||||
|
wwwroot/llmusage/
|
||||||
|
wwwroot/llmusage_accounting_failed/
|
||||||
|
!wwwroot/llmusage_accounting_failed/recover_usages.dspy
|
||||||
|
wwwroot/llmusage_history/
|
||||||
|
build/
|
||||||
68
README.md
68
README.md
@ -278,6 +278,74 @@ tasks = await get_today_asynctask_list(userid)
|
|||||||
await query_task_status(request, luid, onetime=False)
|
await query_task_status(request, luid, onetime=False)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 历史推理记录查询
|
||||||
|
|
||||||
|
`GET /llmage/api/get_inference_history.dspy`
|
||||||
|
|
||||||
|
跨表(llmusage + llmusage_history)分页查询当前用户的推理历史,按时间倒序返回,默认每页 10 条。自动通过 FileStorage 读取 ioinfo 文件内容,返回实际输入输出。
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| page | int | 否 | 页码,默认 1 |
|
||||||
|
| pagerows | int | 否 | 每页条数,默认 10 |
|
||||||
|
| llmcatelogid | str | 否 | 按模型分类 ID 过滤,仅返回该分类下模型的记录 |
|
||||||
|
|
||||||
|
**返回字段**:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| success | 是否成功 |
|
||||||
|
| total | 两表合计总记录数 |
|
||||||
|
| page | 当前页码 |
|
||||||
|
| page_size | 每页条数(默认 10,可通过 pagerows 参数指定) |
|
||||||
|
| rows | 记录列表 |
|
||||||
|
|
||||||
|
**rows 中每条记录**:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| id | 记录 ID |
|
||||||
|
| llmid | 模型 ID |
|
||||||
|
| use_date | 使用日期 |
|
||||||
|
| use_time | 使用时间(排序依据) |
|
||||||
|
| userid | 用户 ID |
|
||||||
|
| usages | token 用量(JSON 对象) |
|
||||||
|
| status | 调用状态(ok/failed 等) |
|
||||||
|
| ioinfo | 原始 webpath |
|
||||||
|
| io_content | 解析后的输入输出内容,包含 input 和 output;读取失败时为 null |
|
||||||
|
| amount | 费用金额 |
|
||||||
|
| userorgid | 组织 ID |
|
||||||
|
| accounting_status | 记账状态 |
|
||||||
|
|
||||||
|
**返回示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"id": "abc123",
|
||||||
|
"llmid": "model001",
|
||||||
|
"use_date": "2026-06-05",
|
||||||
|
"use_time": "2026-06-05 12:30:00",
|
||||||
|
"userid": "user001",
|
||||||
|
"usages": {"total_tokens": 1000, "prompt_tokens": 800, "completion_tokens": 200},
|
||||||
|
"status": "ok",
|
||||||
|
"io_content": {"input": [...], "output": [...]},
|
||||||
|
"amount": 0.05,
|
||||||
|
"accounting_status": "accounted"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 156,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**权限**:logined(所有已登录用户),仅返回当前登录用户自己的记录。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 前端页面
|
## 前端页面
|
||||||
|
|||||||
801
docs/API.md
Normal file
801
docs/API.md
Normal file
@ -0,0 +1,801 @@
|
|||||||
|
# llmage API 文档
|
||||||
|
|
||||||
|
Base Path: `/llmage/v1`
|
||||||
|
|
||||||
|
所有 API 端点需要 Bearer Token 认证(`logined` 权限)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /v1/chat/completions
|
||||||
|
|
||||||
|
文本生成接口,兼容 OpenAI 格式。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `model` | string | 模型名称,如 `"qwen3-max"` |
|
||||||
|
| `messages` 或 `prompt` | array / string | 对话消息数组或文本提示 |
|
||||||
|
|
||||||
|
### 可选参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `catelogid` | string | 目录类型ID,默认 `"t2t"`,也支持中文名(向后兼容) |
|
||||||
|
| `stream` | boolean | 是否启用流式输出 |
|
||||||
|
| `off_peak` | boolean | 是否使用非高峰时段 |
|
||||||
|
| `transno` | string | 交易流水号(不传则自动生成) |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "qwen3-max",
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": "Hello"}
|
||||||
|
],
|
||||||
|
"stream": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
**非流式响应:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "luid_xxx",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"model": "qwen3-max",
|
||||||
|
"choices": [{
|
||||||
|
"index": 0,
|
||||||
|
"message": {"role": "assistant", "content": "Hi there!"},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}],
|
||||||
|
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**流式响应 (SSE):**
|
||||||
|
|
||||||
|
```
|
||||||
|
data: {"choices": [{"delta": {"content": "Hi"}, "index": 0}]}
|
||||||
|
data: {"choices": [{"delta": {"content": " there!"}, "index": 0}]}
|
||||||
|
data: [DONE]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误响应
|
||||||
|
|
||||||
|
| 状态码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 400 | 缺少必填参数或模型不存在 |
|
||||||
|
| 403 | 未登录 |
|
||||||
|
| 429 | 账户余额不足 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /v1/video/generations
|
||||||
|
|
||||||
|
视频生成接口。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `model` | string | 模型名称,如 `"keling-2.1"` |
|
||||||
|
| `catelogid` | string | 目录类型ID,如 `"t2v"` / `"i2v"` / `"r2v"` |
|
||||||
|
| `prompt` | string | 生成提示词 |
|
||||||
|
|
||||||
|
### 可选参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `image_url` | string | 图生视频时提供参考图 URL |
|
||||||
|
| `duration` | string | 视频时长,如 `"5s"` |
|
||||||
|
| `resolution` | string | 分辨率,如 `"1080p"` |
|
||||||
|
| `n` | integer | 生成数量 |
|
||||||
|
| `transno` | string | 交易流水号 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "keling-2.1",
|
||||||
|
"catelogid": "t2v",
|
||||||
|
"prompt": "A beautiful sunset over the ocean",
|
||||||
|
"duration": "5s",
|
||||||
|
"resolution": "1080p"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
视频生成通常为异步任务,提交后返回任务信息:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "luid_xxx",
|
||||||
|
"object": "video.generation",
|
||||||
|
"model": "keling-2.1",
|
||||||
|
"status": "submitted",
|
||||||
|
"taskid": "task_xxx",
|
||||||
|
"created": 1716912000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
通过 `/v1/tasks?taskid=xxx` 查询任务状态。
|
||||||
|
|
||||||
|
### 各模型输入参数明细
|
||||||
|
|
||||||
|
> 以下为各平台/模型的具体输入参数。调用时通过 `model` + `catelogid` 自动路由到对应供应商。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Vidu 平台
|
||||||
|
|
||||||
|
##### T2V - 文生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 可选值 |
|
||||||
|
|--------|------|------|--------|------|--------|
|
||||||
|
| `model` | string | 是 | `viduq3-pro` | 模型名称 | `viduq3-turbo`, `viduq3-pro` |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 | - |
|
||||||
|
| `off_peak` | string | 否 | `N` | 错峰执行 | `Y`, `N` |
|
||||||
|
| `duration` | integer | 否 | `10` | 视频长度(1-16秒) | 1-16 |
|
||||||
|
| `ratio` | string | 否 | `16:9` | 长宽比 | `16:9`, `9:16`, `4:3`, `3:4`, `1:1` |
|
||||||
|
| `resolution` | string | 否 | `1080p` | 分辨率 | `540p`, `720p`, `1080p` |
|
||||||
|
|
||||||
|
##### I2V - 图生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 可选值 |
|
||||||
|
|--------|------|------|--------|------|--------|
|
||||||
|
| `model` | string | 是 | `viduq3-pro` | 模型名称 | `viduq3-pro`, `viduq3-turbo` |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 | - |
|
||||||
|
| `image_file` | image | 是 | - | 首帧图片 | - |
|
||||||
|
| `off_peak` | string | 否 | `N` | 错峰执行 | `Y`, `N` |
|
||||||
|
| `duration` | integer | 否 | `10` | 视频长度(1-16秒) | 1-16 |
|
||||||
|
| `ratio` | string | 否 | `16:9` | 长宽比 | `16:9`, `9:16`, `4:3`, `3:4`, `1:1` |
|
||||||
|
| `resolution` | string | 否 | `1080p` | 分辨率 | `540p`, `720p`, `1080p` |
|
||||||
|
|
||||||
|
##### 2I2V - 首尾帧生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| `model` | string | 否 | `viduq2` | 模型名称 |
|
||||||
|
| `payload` | string | 是 | `2i2v` | 固定值 |
|
||||||
|
| `off_peak` | boolean | 否 | `false` | 错峰模式 |
|
||||||
|
| `images` | array | 是 | - | 两张图片URL `[首帧, 尾帧]` |
|
||||||
|
| `duration` | integer | 否 | `10` | 视频时长 |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 |
|
||||||
|
| `audio` | boolean | 否 | `true` | 音频直出 |
|
||||||
|
| `seed` | integer | 否 | `12345` | 随机种子 |
|
||||||
|
| `aspect_ratio` | string | 否 | `16:9` | 画面比例 |
|
||||||
|
| `resolution` | string | 否 | `1080p` | 分辨率 |
|
||||||
|
|
||||||
|
##### Ref2V - 参考生视频 v2(主体模式)
|
||||||
|
|
||||||
|
> 使用主体(图片/视频/文字)生成视频,支持 viduq3-turbo/q3/q2-pro/q2/q1/2.0
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| `model` | string | 是 | 模型名称 |
|
||||||
|
| `subjects` | array | 是 | 主体列表(最多7个图片/文字主体,每个主体最多3张图) |
|
||||||
|
| `prompt` | string | 是 | 提示词 |
|
||||||
|
| `audio` | boolean | 否 | 音视频直出 |
|
||||||
|
| `audio_type` | string | 否 | 音频类型 |
|
||||||
|
| `duration` | integer | 否 | 视频时长 |
|
||||||
|
| `seed` | integer | 否 | 随机种子 |
|
||||||
|
| `aspect_ratio` | string | 否 | 画面比例 |
|
||||||
|
| `resolution` | string | 否 | 分辨率 |
|
||||||
|
| `movement_amplitude` | string | 否 | 运动幅度 |
|
||||||
|
| `off_peak` | boolean | 否 | 错峰模式 |
|
||||||
|
| `auto_subjects` | boolean | 否 | 智能主体 |
|
||||||
|
|
||||||
|
##### Ref2V - 参考生视频 v2(非主体模式)
|
||||||
|
|
||||||
|
> 直接上传图片参考生成视频,支持 viduq3-mix/q3-turbo/q3/q2-pro/q2/q1/2.0
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| `model` | string | 是 | 模型名称 |
|
||||||
|
| `images` | array | 是 | 参考图片URL列表(1-7张) |
|
||||||
|
| `videos` | array | 否 | 参考视频URL列表(仅viduq2-pro) |
|
||||||
|
| `prompt` | string | 是 | 提示词 |
|
||||||
|
| `audio` | boolean | 否 | 音视频直出 |
|
||||||
|
| `bgm` | boolean | 否 | 背景音乐 |
|
||||||
|
| `duration` | integer | 否 | 视频时长 |
|
||||||
|
| `seed` | integer | 否 | 随机种子 |
|
||||||
|
| `aspect_ratio` | string | 否 | 画面比例 |
|
||||||
|
| `resolution` | string | 否 | 分辨率 |
|
||||||
|
| `off_peak` | boolean | 否 | 错峰模式 |
|
||||||
|
|
||||||
|
##### Ref2V - 参考生视频 v1
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 可选值 |
|
||||||
|
|--------|------|------|--------|------|--------|
|
||||||
|
| `model` | string | 是 | `viduq2-pro` | 模型名称 | `viduq2`, `viduq1`, `vidu2.0` |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 | - |
|
||||||
|
| `off_peak` | string | 否 | `N` | 错峰执行 | `Y`, `N` |
|
||||||
|
| `duration` | integer | 否 | `10` | 视频长度 | - |
|
||||||
|
| `ratio` | string | 否 | `16:9` | 长宽比 | `16:9`, `9:16`, `4:3`, `3:4`, `1:1` |
|
||||||
|
| `resolution` | string | 否 | `1080p` | 分辨率 | `540p`, `720p`, `1080p` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Seedance 平台(火山方舟)
|
||||||
|
|
||||||
|
##### T2V - 文生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 可选值 |
|
||||||
|
|--------|------|------|--------|------|--------|
|
||||||
|
| `model` | string | 是 | `doubao-seedance-2-0-260128` | 模型名称 | `doubao-seedance-2-0-260128`, `doubao-seedance-2-0-fast-260128` |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 | - |
|
||||||
|
| `resolution` | string | 否 | `720p` | 尺寸 | `480p`, `720p`, `1080p` |
|
||||||
|
| `duration` | integer | 否 | `8` | 视频长度 | - |
|
||||||
|
| `ratio` | string | 否 | `1:1` | 宽高比 | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `21:9`, `9:21` |
|
||||||
|
|
||||||
|
##### TI2V - 文图生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 可选值 |
|
||||||
|
|--------|------|------|--------|------|--------|
|
||||||
|
| `model` | string | 是 | `doubao-seedance-2-0-260128` | 模型名称 | `doubao-seedance-2-0-260128`, `doubao-seedance-2-0-fast-260128` |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 | - |
|
||||||
|
| `image1_file` | image | 是 | - | 首帧图片 | - |
|
||||||
|
| `image2_file` | image | 否 | - | 尾帧图片 | - |
|
||||||
|
| `resolution` | string | 否 | `720p` | 尺寸 | `480p`, `720p`, `1080p` |
|
||||||
|
| `duration` | integer | 否 | `8` | 视频长度 | - |
|
||||||
|
| `ratio` | string | 否 | `1:1` | 宽高比 | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `21:9`, `9:21` |
|
||||||
|
|
||||||
|
##### Ref2V - 参考生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| `model` | string | 是 | - | 模型名称 |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 |
|
||||||
|
| `image_file` | image | 否 | - | 参考图片(支持数组,多张参考图) |
|
||||||
|
| `video_file` | video | 否 | - | 参考视频(支持数组) |
|
||||||
|
| `audio_file` | audio | 否 | - | 参考音频(支持数组) |
|
||||||
|
| `duration` | integer | 否 | `12` | 视频长度 |
|
||||||
|
| `resolution` | string | 否 | `720p` | 尺寸 |
|
||||||
|
| `ratio` | string | 否 | - | 宽高比 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 通义万象(DashScope)
|
||||||
|
|
||||||
|
##### T2V - 文生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| `model` | string | 是 | - | 模型名称(如 `wan2.6-t2v`) |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 |
|
||||||
|
| `negative_prompt` | string | 否 | - | 反向提示词 |
|
||||||
|
| `audio_file` | audio | 否 | - | 配音文件 |
|
||||||
|
| `size` | string | 否 | `1920*1080` | 视频尺寸 |
|
||||||
|
| `duration` | string | 否 | `15` | 视频时长 |
|
||||||
|
|
||||||
|
**size 可选值:** `832*480`, `480*832`, `624*624`, `1280*720`, `720*1280`, `960*960`, `1088*832`, `832*1088`, `1920*1080`, `1080*1920`, `1440*1440`, `1632*1248`, `1248*1632`
|
||||||
|
|
||||||
|
**duration 可选值:** `5`, `10`, `15`
|
||||||
|
|
||||||
|
##### I2V - 图生视频
|
||||||
|
|
||||||
|
可用模型:`wan2.6-i2v`, `wan2.6-i2v-flash`
|
||||||
|
|
||||||
|
> 输入参数与 T2V 类似,额外需要首帧图片。
|
||||||
|
|
||||||
|
##### 2I2V - 首尾帧生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| `model` | string | 是 | - | 模型名称 |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 |
|
||||||
|
| `negative_prompt` | string | 否 | - | 反向提示词 |
|
||||||
|
| `image1_file` | image | 是 | - | 首帧图片 |
|
||||||
|
| `image2_file` | image | 是 | - | 尾帧图片 |
|
||||||
|
| `resolution` | string | 否 | `1080P` | 分辨率 |
|
||||||
|
| `duration` | integer | - | `5` | 固定5秒 |
|
||||||
|
|
||||||
|
##### Ref2V - 角色参考生视频
|
||||||
|
|
||||||
|
> 参考输入视频中的角色形象和音色,搭配提示词生成保持角色一致性的视频。可以输入1-3个人物视频,每个视频一个角色。
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| `model` | string | 是 | - | 模型名称(如 `wan2.6-r2v`) |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 |
|
||||||
|
| `video1_file` | video | 是 | - | 角色一视频 |
|
||||||
|
| `video2_file` | video | 否 | - | 角色二视频 |
|
||||||
|
| `video3_file` | video | 否 | - | 角色三视频 |
|
||||||
|
| `size` | string | 否 | `1920*1080` | 视频尺寸 |
|
||||||
|
| `duration` | string | 否 | `10` | 视频时长 |
|
||||||
|
|
||||||
|
**size 可选值:** 同 T2V
|
||||||
|
|
||||||
|
**duration 可选值:** `10`, `15`
|
||||||
|
|
||||||
|
##### IA2V - 图像音频生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| `image_file` | image | 是 | 图像 |
|
||||||
|
| `audio_file` | audio | 是 | 音频 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 可灵(Kling)
|
||||||
|
|
||||||
|
##### T2V - 文生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 可选值 |
|
||||||
|
|--------|------|------|--------|------|--------|
|
||||||
|
| `model` | string | 是 | - | 模型名称 | `kling-v2-1-master`, `kling-v2-master`, `kling-v1-6`, `kling-v1` |
|
||||||
|
| `prompt` | string | 是 | - | 提示词 | - |
|
||||||
|
| `negative_prompt` | string | 否 | - | 反向提示词 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 海螺(Hailuo/MiniMax)
|
||||||
|
|
||||||
|
##### TI2V - 图生视频
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 可选值 |
|
||||||
|
|--------|------|------|--------|------|--------|
|
||||||
|
| `prompt` | string | 是 | - | 提示词 | - |
|
||||||
|
| `image_file` | image | 否 | - | 首帧图片 | - |
|
||||||
|
| `image_file1` | image | 否 | - | 尾帧图片 | - |
|
||||||
|
| `resolution` | string | 否 | `768P` | 尺寸 | `768P`, `1080P` |
|
||||||
|
| `duration` | integer | 否 | `6` | 视频长度 | `6`(6秒), `10`(10秒) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 快乐马(HappyHorse)
|
||||||
|
|
||||||
|
> 基于通义万象平台(tongyi-wan),输入参数与通义万象对应类型一致。
|
||||||
|
|
||||||
|
##### T2V - 文生视频
|
||||||
|
|
||||||
|
输入参数同通义万象 T2V。可用模型:`happyhorse-1.0-t2v`
|
||||||
|
|
||||||
|
##### I2V - 图生视频
|
||||||
|
|
||||||
|
输入参数同通义万象 I2V。可用模型:`happyhorse-1.0-i2v`
|
||||||
|
|
||||||
|
> **注意:** 图片参数名为 `image_file`(非 `image_url`),传入图片 URL。
|
||||||
|
|
||||||
|
##### Ref2V - 参考生视频
|
||||||
|
|
||||||
|
输入参数同通义万象 Ref2V,额外支持:
|
||||||
|
|
||||||
|
| 参数名 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `resolution` | 可选 `1080P`(默认), `720P` |
|
||||||
|
| `ratio` | 可选 `16:9`(默认), `9:16`, `3:4`, `4:3` |
|
||||||
|
|
||||||
|
可用模型:`happyhorse-1.0-r2v`(参考图像数量1-9张,支持多角色参考)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /v1/image/generations
|
||||||
|
|
||||||
|
图像生成接口。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `model` | string | 模型名称,如 `"jimeng-4.0"` |
|
||||||
|
| `catelogid` | string | 目录类型ID,如 `"t2i"` |
|
||||||
|
| `prompt` | string | 生成提示词 |
|
||||||
|
|
||||||
|
### 可选参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `image_url` | string | 图生图时提供参考图 URL |
|
||||||
|
| `size` | string | 尺寸,如 `"1024x1024"` |
|
||||||
|
| `n` | integer | 生成数量 |
|
||||||
|
| `style` | string | 风格参数 |
|
||||||
|
| `quality` | string | 质量参数 |
|
||||||
|
| `transno` | string | 交易流水号 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "jimeng-4.0",
|
||||||
|
"catelogid": "t2i",
|
||||||
|
"prompt": "A beautiful sunset over the ocean",
|
||||||
|
"size": "1024x1024",
|
||||||
|
"n": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
响应格式取决于上游模型配置(同步返回图像数据,异步返回任务信息):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "luid_xxx",
|
||||||
|
"object": "image.generation",
|
||||||
|
"model": "jimeng-4.0",
|
||||||
|
"status": "submitted",
|
||||||
|
"taskid": "task_xxx",
|
||||||
|
"created": 1716912000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /v1/music/generations
|
||||||
|
|
||||||
|
音乐生成接口。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `model` | string | 模型名称,如 `"music-2.6"`, `"music-2.5"` |
|
||||||
|
| `catelogid` | string | 目录类型ID,固定为 `"music_gen"` |
|
||||||
|
| `prompt` | string | 音乐风格描述(风格、情绪、场景),如 `"流行音乐, 开心, 适合阳光明媚的下午"` |
|
||||||
|
| `lyrics` | string | 歌词内容,使用 `\n` 分隔每行,可包含结构标签 |
|
||||||
|
|
||||||
|
### 歌词结构标签
|
||||||
|
|
||||||
|
歌词中可包含以下结构标签来优化生成的音乐结构:
|
||||||
|
- `[Intro]` - 前奏
|
||||||
|
- `[Verse]` - 主歌
|
||||||
|
- `[Pre Chorus]` - 预副歌
|
||||||
|
- `[Chorus]` - 副歌
|
||||||
|
- `[Bridge]` - 桥段
|
||||||
|
- `[Outro]` - 尾声
|
||||||
|
- `[Interlude]` - 间奏
|
||||||
|
- `[Hook]` - 记忆点
|
||||||
|
- `[Build Up]` - 情绪铺垫
|
||||||
|
- `[Solo]` - 独奏
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "music-2.6",
|
||||||
|
"catelogid": "music_gen",
|
||||||
|
"prompt": "Pop music, happy, suitable for a sunny day",
|
||||||
|
"lyrics": "[Intro]\n\n[Verse]\nWalking down the street\nFeeling the beat\n\n[Chorus]\nDancing in the sun\nHaving so much fun"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
MiniMax 音乐生成为同步接口,直接返回音频URL:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "luid_xxx",
|
||||||
|
"object": "music.generation",
|
||||||
|
"model": "music-2.6",
|
||||||
|
"status": "SUCCEEDED",
|
||||||
|
"audio": "https://...",
|
||||||
|
"created": 1716912000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可用模型
|
||||||
|
|
||||||
|
| 模型名称 | model 参数 | 说明 |
|
||||||
|
|---------|-----------|------|
|
||||||
|
| MiniMax Music 2.6 | `music-2.6` | 最新版本,音质最佳 |
|
||||||
|
| MiniMax Music 2.5 | `music-2.5` | 支持14种段落级结构标签,物理级高保真 |
|
||||||
|
|
||||||
|
### MiniMax Music 2.5 特性
|
||||||
|
|
||||||
|
Music 2.5 在「段落级强控制」与「物理级高保真」两大技术难题上实现突破:
|
||||||
|
- 开放全段落标签控制,精准支持14种结构变体
|
||||||
|
- 长度限制:歌词内容 [1, 3500] 个字符
|
||||||
|
- prompt 长度限制:[10, 300] 个字符
|
||||||
|
|
||||||
|
### MiniMax Music 2.0 特性(已过期)
|
||||||
|
|
||||||
|
Music 2.0 能根据文本描述和歌词直接生成包含人声的完整歌曲:
|
||||||
|
- prompt 长度限制:[10, 300] 个字符
|
||||||
|
- lyrics 长度限制:[10, 3000] 个字符
|
||||||
|
- 状态:已过期(expired_date: 2026-01-01)
|
||||||
|
|
||||||
|
### 错误响应
|
||||||
|
|
||||||
|
| 状态码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 400 | 缺少必填参数或模型不存在 |
|
||||||
|
| 403 | 未登录 |
|
||||||
|
| 429 | 账户余额不足 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /v1/audio/speech
|
||||||
|
|
||||||
|
文本转语音(TTS)接口。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `model` | string | 模型名称,如 `"speech-2.6-turbo"`, `"speech-2.6-hd"` |
|
||||||
|
| `catelogid` | string | 目录类型ID,固定为 `"tts"` |
|
||||||
|
| `prompt` | string | 需要合成的文本内容,最长 10,000 字符 |
|
||||||
|
|
||||||
|
### 可选参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `speaker` | string | 说话人/音色ID,如 `"female-tianmei"` |
|
||||||
|
| `speed` | float | 语速,默认 `1.0` |
|
||||||
|
| `emotion` | string | 情感,如 `"happy"`, `"sad"` |
|
||||||
|
| `transno` | string | 交易流水号 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "speech-2.6-turbo",
|
||||||
|
"catelogid": "tts",
|
||||||
|
"prompt": "你好,欢迎使用语音合成服务",
|
||||||
|
"speaker": "female-tianmei",
|
||||||
|
"speed": 1.0,
|
||||||
|
"emotion": "happy"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
MiniMax TTS 为流式接口,逐块返回音频数据(hex编码自动转base64):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "SUCCEEDED",
|
||||||
|
"audio": "base64_encoded_audio_data"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可用模型
|
||||||
|
|
||||||
|
| 模型名称 | model 参数 | 说明 |
|
||||||
|
|---------|-----------|------|
|
||||||
|
| MiniMax Speech 2.6 Turbo | `speech-2.6-turbo` | 极速版,更快更优惠,适用于语音聊天和数字人 |
|
||||||
|
| MiniMax Speech 2.6 HD | `speech-2.6-hd` | 高清版,超低延时,更高自然度 |
|
||||||
|
| MiniMax Speech 2.5 HD | `speech-2.5-hd-preview` | Preview版本 |
|
||||||
|
| F5-TTS 本地 | `f5tts` | 本地部署,零样本声音克隆,多语言支持 |
|
||||||
|
|
||||||
|
### 错误响应
|
||||||
|
|
||||||
|
| 状态码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 400 | 缺少必填参数或模型不存在 |
|
||||||
|
| 403 | 未登录 |
|
||||||
|
| 429 | 账户余额不足 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /v1/audio/transcriptions
|
||||||
|
|
||||||
|
语音识别(ASR)接口,将音频转为文本。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `model` | string | 模型名称,如 `"qwen3-asr-flash"`, `"parakeet-tdt-0.6b-v2"` |
|
||||||
|
| `catelogid` | string | 目录类型ID,固定为 `"asr"` |
|
||||||
|
| `audio_file` | string | 音频文件URL |
|
||||||
|
|
||||||
|
### 可选参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `language` | string | 语言代码(部分模型支持) |
|
||||||
|
| `transno` | string | 交易流水号 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "qwen3-asr-flash",
|
||||||
|
"catelogid": "asr",
|
||||||
|
"audio_file": "https://example.com/audio.wav"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"text": "识别出的文本内容",
|
||||||
|
"usage": {
|
||||||
|
"duration_seconds": 5.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可用模型
|
||||||
|
|
||||||
|
| 模型名称 | model 参数 | 说明 |
|
||||||
|
|---------|-----------|------|
|
||||||
|
| 通义千问 ASR | `qwen3-asr-flash` | 多语种识别、歌唱识别、情感识别、噪声拒识,0.00026元/秒 |
|
||||||
|
| Nvidia ASR | `parakeet-tdt-0.6b-v2` | 仅支持英文,6亿参数,支持标点/大小写/时间戳 |
|
||||||
|
|
||||||
|
### 通义千问 ASR 核心功能
|
||||||
|
|
||||||
|
- 多语种识别:涵盖普通话及多种方言(粤语、四川话等)
|
||||||
|
- 复杂环境适应:自动语种检测与智能非人声过滤
|
||||||
|
- 歌唱识别:伴随BGM下也能实现整首歌曲转写
|
||||||
|
- 上下文增强:通过配置上下文提高识别准确率
|
||||||
|
- 情感识别:支持惊讶、平静、愉快、悲伤、厌恶、愤怒、恐惧
|
||||||
|
|
||||||
|
### 错误响应
|
||||||
|
|
||||||
|
| 状态码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 400 | 缺少必填参数或模型不存在 |
|
||||||
|
| 403 | 未登录 |
|
||||||
|
| 429 | 账户余额不足 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GET /v1/tasks
|
||||||
|
|
||||||
|
查询异步任务状态。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `taskid` | string | 任务 ID |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /llmage/v1/tasks?taskid=task_xxx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": {
|
||||||
|
"status": "SUCCEEDED",
|
||||||
|
"output": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
任务状态值: `UNKNOWN` / `SUCCEEDED` / `FAILED`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GET /v1/models
|
||||||
|
|
||||||
|
列出可用模型列表。
|
||||||
|
|
||||||
|
### 可选参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `catelogid` | string | 按目录类型过滤 |
|
||||||
|
| `orderby` | string | 排序字段 |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /llmage/v1/models
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"object": "list",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "qwen3-max",
|
||||||
|
"object": "model",
|
||||||
|
"created": 1748044800,
|
||||||
|
"owned_by": "opencomputing.ai"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GET /v1/pricing
|
||||||
|
|
||||||
|
获取模型定价展示信息。
|
||||||
|
|
||||||
|
### 必填参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `model` | string | 模型名称,如 `"qwen3.7-max"` |
|
||||||
|
|
||||||
|
### 可选参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `catelogid` | string | 目录类型ID,默认 `"t2t"` |
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /llmage/v1/pricing?model=qwen3.7-max
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"data": {
|
||||||
|
"ppid": "pp_xxx",
|
||||||
|
"name": "qwen3.7-max",
|
||||||
|
"pricing_type": "per_use",
|
||||||
|
"display_text": "【通义千问 qwen3.7-max】定价:\n - 输入Token: 12 元/百万 [模型=qwen3.7-max]\n - 输出Token: 48 元/百万 [模型=qwen3.7-max]",
|
||||||
|
"items": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误响应
|
||||||
|
|
||||||
|
| 状态 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| error | 缺少 model 参数 |
|
||||||
|
| error | 模型不存在或无定价配置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 通用说明
|
||||||
|
|
||||||
|
### catelogid 目录类型ID对照表
|
||||||
|
|
||||||
|
| ID | 中文名 | 说明 |
|
||||||
|
|----|--------|------|
|
||||||
|
| `t2t` | 文生文 | 文本生成(默认) |
|
||||||
|
| `t2i` | 文生图 | 图像生成 |
|
||||||
|
| `t2v` | 文生视频 | 文本生成视频 |
|
||||||
|
| `i2v` | 图生视频 | 图像生成视频 |
|
||||||
|
| `r2v` | 参考生视频 | 参考图像生成视频 |
|
||||||
|
| `tts` | 语音合成 | 文本转语音 |
|
||||||
|
| `asr` | 语音识别 | 语音转文本 |
|
||||||
|
| `vision` | 图理解 | 图像理解 |
|
||||||
|
| `ai_search` | AI搜索 | AI搜索 |
|
||||||
|
| `digital_human` | 数字人 | 数字人 |
|
||||||
|
| `music_gen` | 音乐生成 | 音乐生成 |
|
||||||
|
| `text_cls` | 文本分类 | 文本分类 |
|
||||||
|
| `3d_gen` | 3D生成 | 3D模型生成 |
|
||||||
|
| `video_tool` | 视频工具 | 视频处理工具 |
|
||||||
|
| `translate` | 翻译 | 文本翻译 |
|
||||||
|
|
||||||
|
> 向后兼容:catelogid 参数同时支持新ID(如 `"t2v"`)和旧中文名(如 `"文生视频"`),推荐使用新ID。
|
||||||
|
|
||||||
|
### 参数统一
|
||||||
|
|
||||||
|
所有 v1 接口统一使用 `catelogid` 参数标识目录类型,替代原有的 `lctype` / `llmcatelogid`。
|
||||||
|
|
||||||
|
### 认证
|
||||||
|
|
||||||
|
所有接口需要 Bearer Token 认证,请求头中携带:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer ***
|
||||||
|
```
|
||||||
|
|
||||||
|
### 余额检查
|
||||||
|
|
||||||
|
每次请求都会自动调用 `checkCustomerBalance()` 进行余额检查:
|
||||||
|
- 如果模型属于用户所在组织(`llm.ownerid == userorgid`),则跳过余额检查
|
||||||
|
- 否则检查 tpac 余额或本地余额
|
||||||
|
- 余额不足时返回 429 状态码
|
||||||
|
|
||||||
|
### 计费
|
||||||
|
|
||||||
|
请求成功后自动创建 `llmusage` 记录,状态为 `created`。后台定时任务会定期执行计费流程。
|
||||||
118
docs/vendor-minimax.md
Normal file
118
docs/vendor-minimax.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# MiniMax 供应商接入记录
|
||||||
|
|
||||||
|
## 供应商信息
|
||||||
|
|
||||||
|
| 项目 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 供应商名称 | MiniMax (上海稀宇科技有限公司) |
|
||||||
|
| 平台网址 | https://platform.minimaxi.com |
|
||||||
|
| API文档 | https://platform.minimaxi.com/docs/api-reference/text-chat-openai |
|
||||||
|
| 定价页面 | https://platform.minimaxi.com/subscribe/token-plan?tab=api-enterprise |
|
||||||
|
| API基础URL | https://api.minimaxi.com/v1 (upapp.baseurl) |
|
||||||
|
| 系统upappid | minimax |
|
||||||
|
| 系统providerid | ww4e_kfX3Lh65Sdys0Vku |
|
||||||
|
| API认证方式 | Bearer Token (Authorization: Bearer *** |
|
||||||
|
|
||||||
|
## 已接入模型 (共11个, 截至2026-06-12)
|
||||||
|
|
||||||
|
### 文本生成 (t2t) — 定价项目: 5jmzupARABxkDFwUraFiQ
|
||||||
|
|
||||||
|
| 模型名称 | model | llm.id | 状态 | httpapi |
|
||||||
|
|----------|-------|--------|------|---------|
|
||||||
|
| **MiniMax M3** | MiniMax-M3 | mm3_MiniMax_M3 | 新增 | minimax_openai t2t |
|
||||||
|
| MiniMax M2.7 | minimax-m2.7 | oiLvLl75qNX9IQkWFm60i | 已有 | t2t |
|
||||||
|
| **MiniMax M2.7 Highspeed** | MiniMax-M2.7-highspeed | mm_m27_highspeed | 新增 | minimax_openai t2t |
|
||||||
|
|
||||||
|
### 视频生成 (i2v) — 定价项目: 0V89eilc_UQ2KiZIRJO8M
|
||||||
|
|
||||||
|
| 模型名称 | model | llm.id | 状态 |
|
||||||
|
|----------|-------|--------|------|
|
||||||
|
| MiniMax Hailuo 2.3 | MiniMax-Hailuo-2.3 | AU1f40HV3tqFjxcVWWpyR | 已有, 补充ppid |
|
||||||
|
| 海螺参考生视频 | S2V-01 | oks-VG9D8p2b0Agvs-LeQ | 已有, 补充ppid |
|
||||||
|
|
||||||
|
### 语音合成 (tts) — 定价项目: mm_tts_pricing (新增)
|
||||||
|
|
||||||
|
| 模型名称 | model | llm.id | 状态 |
|
||||||
|
|----------|-------|--------|------|
|
||||||
|
| speech-2.6-hd | speech-2.6-hd | q6rdMUsGD1z3S3NyZh_A_ | 已有, 补充ppid |
|
||||||
|
| speech-2.6-turbo | speech-2.6-turbo | CEYD4YWRxjCj4k_6bpzIM | 已有, 补充ppid |
|
||||||
|
| speech-2.5-hd-preview | speech-2.5-hd-preview | Si2g0XJ9ym3P5jlrdmcfB | 已有, 补充ppid |
|
||||||
|
|
||||||
|
### 音乐生成 (music_gen) — 定价项目: fQzkUeS6t6NBz_Fu4Fi77
|
||||||
|
|
||||||
|
| 模型名称 | model | llm.id | 状态 |
|
||||||
|
|----------|-------|--------|------|
|
||||||
|
| Music 2.6 | music-2.6 | dleFKyYSSllCl70etn7yU | 已有 |
|
||||||
|
| Music 2.5 | music-2.5 | tTREa9nNy3yIRxywQLjvT | 已有 |
|
||||||
|
| Music 2.0 | music-2.0 | ns7egG9aXi91wjI62yKfu | 已有, 补充ppid |
|
||||||
|
|
||||||
|
## 定价信息
|
||||||
|
|
||||||
|
### 文本模型 (元/百万tokens) — 5jmzupARABxkDFwUraFiQ
|
||||||
|
|
||||||
|
| 模型 | 输入 | 输出 | 缓存 | 备注 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| MiniMax-M3 (≤512K) | ¥2.1 | ¥8.4 | ¥0.42 | 永久五折 |
|
||||||
|
| MiniMax-M3 (512K~1M) | ¥4.2 | ¥16.8 | ¥0.84 | 永久五折 |
|
||||||
|
| MiniMax-M2.7 | ¥2.1 | ¥8.4 | - | 五折 |
|
||||||
|
| MiniMax-M2.7-highspeed | ¥4.2 | ¥16.8 | - | - |
|
||||||
|
| MiniMax-M2.5 | ¥2.1 | ¥8.4 | - | - |
|
||||||
|
| MiniMax-M2.5-highspeed | ¥4.2 | ¥16.8 | - | - |
|
||||||
|
| M2-her | ¥2.1 | ¥8.4 | - | - |
|
||||||
|
|
||||||
|
### TTS (元/万字符) — mm_tts_pricing
|
||||||
|
|
||||||
|
| 模型 | 单价 |
|
||||||
|
|------|------|
|
||||||
|
| speech-2.6-hd | ¥3.5 |
|
||||||
|
| speech-2.6-turbo | ¥2.0 |
|
||||||
|
| speech-2.5-hd-preview | ¥3.5 |
|
||||||
|
|
||||||
|
### 视频 (元/次) — 0V89eilc_UQ2KiZIRJO8M
|
||||||
|
|
||||||
|
| 模型 | 分辨率 | 时长 | 单价 |
|
||||||
|
|------|--------|------|------|
|
||||||
|
| Hailuo-2.3 | 768P | 6s | ¥2.00 |
|
||||||
|
| Hailuo-2.3 | 768P | 10s | ¥3.50 |
|
||||||
|
| Hailuo-2.3 | 1080P | 6s | ¥2.00 |
|
||||||
|
| Hailuo-2.3-Fast | 768P | 6s | ¥2.25 |
|
||||||
|
|
||||||
|
### 音乐 (元/次) — fQzkUeS6t6NBz_Fu4Fi77
|
||||||
|
|
||||||
|
| 模型 | 单价 |
|
||||||
|
|------|------|
|
||||||
|
| Music-2.6/2.5/2.0 | ¥1.0 |
|
||||||
|
|
||||||
|
## uapi配置 (uapi模块)
|
||||||
|
|
||||||
|
### minimax t2t (新增, id=mm_minimax_t2t)
|
||||||
|
- path: /chat/completions (upapp.baseurl拼接)
|
||||||
|
- 完整URL: https://api.minimaxi.com/v1/chat/completions
|
||||||
|
- ioid: Is8l4TGkcZcqFSjbbeIK2 (文本会话, 共享)
|
||||||
|
- stream: stream, chunk_match: data:
|
||||||
|
- headers: Bearer {{apikey}}, Content-Type: application/json
|
||||||
|
|
||||||
|
### minimax tm2t (新增, id=mm_minimax_tm2t)
|
||||||
|
- 多模态对话, 支持image_file/video_file/audio_file
|
||||||
|
- ioid: t-ujII59ku45tIPcdXu4O (文本媒体转文本, 共享)
|
||||||
|
- 与ali-qwen的tm2t模板相同(b64media2url处理)
|
||||||
|
|
||||||
|
## SQL文件
|
||||||
|
|
||||||
|
`scripts/minimax_m3_add.sql` — 包含11条SQL语句:
|
||||||
|
1. INSERT httpapi (minimax_openai t2t)
|
||||||
|
2. INSERT llm (MiniMax-M3)
|
||||||
|
3. INSERT llm (MiniMax-M2.7-highspeed)
|
||||||
|
4. INSERT llm_api_map (M3)
|
||||||
|
5. INSERT llm_api_map (M2.7-highspeed)
|
||||||
|
6. UPDATE llm_api_map ppid × 6 (视频/TTS/音乐)
|
||||||
|
7. INSERT pricing_program (mm_tts_pricing)
|
||||||
|
8. INSERT pricing_program_timing (TTS定价)
|
||||||
|
9. UPDATE 5jmzup timing (追加M3定价)
|
||||||
|
10. UPDATE 5jmzup spec (添加M3到模型选项)
|
||||||
|
|
||||||
|
## 变更记录
|
||||||
|
|
||||||
|
| 日期 | 操作 |
|
||||||
|
|------|------|
|
||||||
|
| 2026-06-12 | 新增M3+M2.7-highspeed, 补齐Hailuo/S2V/TTS/Music定价 |
|
||||||
127
i18n/en/msg.txt
Normal file
127
i18n/en/msg.txt
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
模型管理: Model Management
|
||||||
|
模型名称: Model Name
|
||||||
|
模型编码: Model Code
|
||||||
|
模型类型: Model Type
|
||||||
|
模型提供商: Model Provider
|
||||||
|
模型版本: Model Version
|
||||||
|
模型状态: Model Status
|
||||||
|
API接口: API Interface
|
||||||
|
API密钥: API Key
|
||||||
|
API地址: API Endpoint
|
||||||
|
最大Token: Max Tokens
|
||||||
|
输入价格: Input Price
|
||||||
|
输出价格: Output Price
|
||||||
|
折扣: Discount
|
||||||
|
启用: Enable
|
||||||
|
停用: Disable
|
||||||
|
已启用: Enabled
|
||||||
|
已停用: Disabled
|
||||||
|
新增模型: Add Model
|
||||||
|
编辑模型: Edit Model
|
||||||
|
删除模型: Delete Model
|
||||||
|
测试模型: Test Model
|
||||||
|
模型分组: Model Group
|
||||||
|
分组名称: Group Name
|
||||||
|
分组描述: Group Description
|
||||||
|
新增分组: Add Group
|
||||||
|
编辑分组: Edit Group
|
||||||
|
删除分组: Delete Group
|
||||||
|
使用统计: Usage Statistics
|
||||||
|
调用次数: Call Count
|
||||||
|
成功次数: Success Count
|
||||||
|
失败次数: Failure Count
|
||||||
|
Token用量: Token Usage
|
||||||
|
输入Token: Input Tokens
|
||||||
|
输出Token: Output Tokens
|
||||||
|
总Token: Total Tokens
|
||||||
|
费用统计: Cost Statistics
|
||||||
|
总费用: Total Cost
|
||||||
|
本月费用: Monthly Cost
|
||||||
|
今日费用: Daily Cost
|
||||||
|
按模型统计: By Model
|
||||||
|
按用户统计: By User
|
||||||
|
按日期统计: By Date
|
||||||
|
趋势图: Trend Chart
|
||||||
|
日: Day
|
||||||
|
周: Week
|
||||||
|
月: Month
|
||||||
|
年: Year
|
||||||
|
用户管理: User Management
|
||||||
|
用户名称: User Name
|
||||||
|
用户Token配额: User Token Quota
|
||||||
|
已使用: Used
|
||||||
|
剩余配额: Remaining Quota
|
||||||
|
配额重置: Quota Reset
|
||||||
|
模型映射: Model Mapping
|
||||||
|
映射名称: Mapping Name
|
||||||
|
源模型: Source Model
|
||||||
|
目标模型: Target Model
|
||||||
|
映射状态: Mapping Status
|
||||||
|
新增映射: Add Mapping
|
||||||
|
编辑映射: Edit Mapping
|
||||||
|
删除映射: Delete Mapping
|
||||||
|
密钥管理: Key Management
|
||||||
|
密钥名称: Key Name
|
||||||
|
密钥值: Key Value
|
||||||
|
密钥状态: Key Status
|
||||||
|
新增密钥: Add Key
|
||||||
|
编辑密钥: Edit Key
|
||||||
|
删除密钥: Delete Key
|
||||||
|
日志: Log
|
||||||
|
请求日志: Request Log
|
||||||
|
错误日志: Error Log
|
||||||
|
请求时间: Request Time
|
||||||
|
响应时间: Response Time
|
||||||
|
耗时: Duration
|
||||||
|
状态码: Status Code
|
||||||
|
错误信息: Error Message
|
||||||
|
请求参数: Request Parameters
|
||||||
|
响应内容: Response Content
|
||||||
|
供应商: Vendor
|
||||||
|
所属机构: Organization
|
||||||
|
定价项目: Pricing Item
|
||||||
|
定价属于: Pricing Belongs To
|
||||||
|
供应商折扣: Vendor Discount
|
||||||
|
描述: Description
|
||||||
|
规格明细: Specification Details
|
||||||
|
项目名称: Item Name
|
||||||
|
模型: Model
|
||||||
|
API: API
|
||||||
|
定价: Pricing
|
||||||
|
时序: Timeline
|
||||||
|
开始日期: Start Date
|
||||||
|
结束日期: End Date
|
||||||
|
生效日期: Effective Date
|
||||||
|
失效日期: Expiration Date
|
||||||
|
定价数据: Pricing Data
|
||||||
|
定价项目时序: Pricing Item Timeline
|
||||||
|
测试: Test
|
||||||
|
定价测试: Pricing Test
|
||||||
|
新增: Add
|
||||||
|
保存: Save
|
||||||
|
取消: Cancel
|
||||||
|
确认: Confirm
|
||||||
|
删除: Delete
|
||||||
|
编辑: Edit
|
||||||
|
查看: View
|
||||||
|
导出: Export
|
||||||
|
打印: Print
|
||||||
|
刷新: Refresh
|
||||||
|
返回: Back
|
||||||
|
提交: Submit
|
||||||
|
重置: Reset
|
||||||
|
Conform: Confirm
|
||||||
|
Discard: Discard
|
||||||
|
Submit: Submit
|
||||||
|
Reset: Reset
|
||||||
|
Cancel: Cancel
|
||||||
|
搜索: Search
|
||||||
|
操作: Action
|
||||||
|
类型: Type
|
||||||
|
状态: Status
|
||||||
|
名称: Name
|
||||||
|
编码: Code
|
||||||
|
备注: Remark
|
||||||
|
创建时间: Created Time
|
||||||
|
更新时间: Updated Time
|
||||||
|
全部: All
|
||||||
127
i18n/jp/msg.txt
Normal file
127
i18n/jp/msg.txt
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
模型管理: モデル管理
|
||||||
|
模型名称: モデル名
|
||||||
|
模型编码: モデルコード
|
||||||
|
模型类型: モデルタイプ
|
||||||
|
模型提供商: モデルプロバイダー
|
||||||
|
模型版本: モデルバージョン
|
||||||
|
模型状态: モデル状態
|
||||||
|
API接口: APIインターフェース
|
||||||
|
API密钥: APIキー
|
||||||
|
API地址: APIエンドポイント
|
||||||
|
最大Token: 最大トークン
|
||||||
|
输入价格: 入力価格
|
||||||
|
输出价格: 出力価格
|
||||||
|
折扣: 割引
|
||||||
|
启用: 有効化
|
||||||
|
停用: 無効化
|
||||||
|
已启用: 有効
|
||||||
|
已停用: 無効
|
||||||
|
新增模型: モデル追加
|
||||||
|
编辑模型: モデル編集
|
||||||
|
删除模型: モデル削除
|
||||||
|
测试模型: モデルテスト
|
||||||
|
模型分组: モデルグループ
|
||||||
|
分组名称: グループ名
|
||||||
|
分组描述: グループ説明
|
||||||
|
新增分组: グループ追加
|
||||||
|
编辑分组: グループ編集
|
||||||
|
删除分组: グループ削除
|
||||||
|
使用统计: 使用統計
|
||||||
|
调用次数: 呼び出し回数
|
||||||
|
成功次数: 成功回数
|
||||||
|
失败次数: 失敗回数
|
||||||
|
Token用量: トークン使用量
|
||||||
|
输入Token: 入力トークン
|
||||||
|
输出Token: 出力トークン
|
||||||
|
总Token: 合計トークン
|
||||||
|
费用统计: コスト統計
|
||||||
|
总费用: 合計コスト
|
||||||
|
本月费用: 今月コスト
|
||||||
|
今日费用: 今日コスト
|
||||||
|
按模型统计: モデル別統計
|
||||||
|
按用户统计: ユーザー別統計
|
||||||
|
按日期统计: 日付別統計
|
||||||
|
趋势图: トレンドチャート
|
||||||
|
日: 日
|
||||||
|
周: 週
|
||||||
|
月: 月
|
||||||
|
年: 年
|
||||||
|
用户管理: ユーザー管理
|
||||||
|
用户名称: ユーザー名
|
||||||
|
用户Token配额: ユーザートークンクォータ
|
||||||
|
已使用: 使用済み
|
||||||
|
剩余配额: 残りクォータ
|
||||||
|
配额重置: クォータリセット
|
||||||
|
模型映射: モデルマッピング
|
||||||
|
映射名称: マッピング名
|
||||||
|
源模型: ソースモデル
|
||||||
|
目标模型: ターゲットモデル
|
||||||
|
映射状态: マッピング状態
|
||||||
|
新增映射: マッピング追加
|
||||||
|
编辑映射: マッピング編集
|
||||||
|
删除映射: マッピング削除
|
||||||
|
密钥管理: キー管理
|
||||||
|
密钥名称: キー名
|
||||||
|
密钥值: キー値
|
||||||
|
密钥状态: キー状態
|
||||||
|
新增密钥: キー追加
|
||||||
|
编辑密钥: キー編集
|
||||||
|
删除密钥: キー削除
|
||||||
|
日志: ログ
|
||||||
|
请求日志: リクエストログ
|
||||||
|
错误日志: エラーログ
|
||||||
|
请求时间: リクエスト時間
|
||||||
|
响应时间: レスポンス時間
|
||||||
|
耗时: 所要時間
|
||||||
|
状态码: ステータスコード
|
||||||
|
错误信息: エラーメッセージ
|
||||||
|
请求参数: リクエストパラメータ
|
||||||
|
响应内容: レスポンス内容
|
||||||
|
供应商: ベンダー
|
||||||
|
所属机构: 所属組織
|
||||||
|
定价项目: 価格設定項目
|
||||||
|
定价属于: 価格設定帰属
|
||||||
|
供应商折扣: ベンダー割引
|
||||||
|
描述: 説明
|
||||||
|
规格明细: 仕様詳細
|
||||||
|
项目名称: 項目名
|
||||||
|
模型: モデル
|
||||||
|
API: API
|
||||||
|
定价: 価格設定
|
||||||
|
时序: 時系列
|
||||||
|
开始日期: 開始日
|
||||||
|
结束日期: 終了日
|
||||||
|
生效日期: 有効開始日
|
||||||
|
失效日期: 有効終了日
|
||||||
|
定价数据: 価格設定データ
|
||||||
|
定价项目时序: 価格設定項目時系列
|
||||||
|
测试: テスト
|
||||||
|
定价测试: 価格設定テスト
|
||||||
|
新增: 新規追加
|
||||||
|
保存: 保存
|
||||||
|
取消: キャンセル
|
||||||
|
确认: 確認
|
||||||
|
删除: 削除
|
||||||
|
编辑: 編集
|
||||||
|
查看: 表示
|
||||||
|
导出: エクスポート
|
||||||
|
打印: 印刷
|
||||||
|
刷新: 更新
|
||||||
|
返回: 戻る
|
||||||
|
提交: 送信
|
||||||
|
重置: リセット
|
||||||
|
Conform: 確認
|
||||||
|
Discard: 破棄
|
||||||
|
Submit: 送信
|
||||||
|
Reset: リセット
|
||||||
|
Cancel: キャンセル
|
||||||
|
搜索: 検索
|
||||||
|
操作: 操作
|
||||||
|
类型: タイプ
|
||||||
|
状态: ステータス
|
||||||
|
名称: 名前
|
||||||
|
编码: コード
|
||||||
|
备注: 備考
|
||||||
|
创建时间: 作成日時
|
||||||
|
更新时间: 更新日時
|
||||||
|
全部: 全部
|
||||||
127
i18n/ko/msg.txt
Normal file
127
i18n/ko/msg.txt
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
模型管理: 모델 관리
|
||||||
|
模型名称: 모델 이름
|
||||||
|
模型编码: 모델 코드
|
||||||
|
模型类型: 모델 유형
|
||||||
|
模型提供商: 모델 제공자
|
||||||
|
模型版本: 모델 버전
|
||||||
|
模型状态: 모델 상태
|
||||||
|
API接口: API 인터페이스
|
||||||
|
API密钥: API 키
|
||||||
|
API地址: API 엔드포인트
|
||||||
|
最大Token: 최대 토큰
|
||||||
|
输入价格: 입력 가격
|
||||||
|
输出价格: 출력 가격
|
||||||
|
折扣: 할인
|
||||||
|
启用: 활성화
|
||||||
|
停用: 비활성화
|
||||||
|
已启用: 활성화됨
|
||||||
|
已停用: 비활성화됨
|
||||||
|
新增模型: 모델 추가
|
||||||
|
编辑模型: 모델 편집
|
||||||
|
删除模型: 모델 삭제
|
||||||
|
测试模型: 모델 테스트
|
||||||
|
模型分组: 모델 그룹
|
||||||
|
分组名称: 그룹 이름
|
||||||
|
分组描述: 그룹 설명
|
||||||
|
新增分组: 그룹 추가
|
||||||
|
编辑分组: 그룹 편집
|
||||||
|
删除分组: 그룹 삭제
|
||||||
|
使用统计: 사용 통계
|
||||||
|
调用次数: 호출 횟수
|
||||||
|
成功次数: 성공 횟수
|
||||||
|
失败次数: 실패 횟수
|
||||||
|
Token用量: 토큰 사용량
|
||||||
|
输入Token: 입력 토큰
|
||||||
|
输出Token: 출력 토큰
|
||||||
|
总Token: 총 토큰
|
||||||
|
费用统计: 비용 통계
|
||||||
|
总费用: 총 비용
|
||||||
|
本月费用: 이번 달 비용
|
||||||
|
今日费用: 오늘 비용
|
||||||
|
按模型统计: 모델별 통계
|
||||||
|
按用户统计: 사용자별 통계
|
||||||
|
按日期统计: 날짜별 통계
|
||||||
|
趋势图: 추세 차트
|
||||||
|
日: 일
|
||||||
|
周: 주
|
||||||
|
月: 월
|
||||||
|
年: 년
|
||||||
|
用户管理: 사용자 관리
|
||||||
|
用户名称: 사용자 이름
|
||||||
|
用户Token配额: 사용자 토큰 쿼터
|
||||||
|
已使用: 사용됨
|
||||||
|
剩余配额: 잔여 쿼터
|
||||||
|
配额重置: 쿼터 초기화
|
||||||
|
模型映射: 모델 매핑
|
||||||
|
映射名称: 매핑 이름
|
||||||
|
源模型: 소스 모델
|
||||||
|
目标模型: 대상 모델
|
||||||
|
映射状态: 매핑 상태
|
||||||
|
新增映射: 매핑 추가
|
||||||
|
编辑映射: 매핑 편집
|
||||||
|
删除映射: 매핑 삭제
|
||||||
|
密钥管理: 키 관리
|
||||||
|
密钥名称: 키 이름
|
||||||
|
密钥值: 키 값
|
||||||
|
密钥状态: 키 상태
|
||||||
|
新增密钥: 키 추가
|
||||||
|
编辑密钥: 키 편집
|
||||||
|
删除密钥: 키 삭제
|
||||||
|
日志: 로그
|
||||||
|
请求日志: 요청 로그
|
||||||
|
错误日志: 오류 로그
|
||||||
|
请求时间: 요청 시간
|
||||||
|
响应时间: 응답 시간
|
||||||
|
耗时: 소요 시간
|
||||||
|
状态码: 상태 코드
|
||||||
|
错误信息: 오류 메시지
|
||||||
|
请求参数: 요청 파라미터
|
||||||
|
响应内容: 응답 내용
|
||||||
|
供应商: 공급업체
|
||||||
|
所属机构: 소속 기관
|
||||||
|
定价项目: 가격 항목
|
||||||
|
定价属于: 가격 귀속
|
||||||
|
供应商折扣: 공급업체 할인
|
||||||
|
描述: 설명
|
||||||
|
规格明细: 규격 상세
|
||||||
|
项目名称: 항목 이름
|
||||||
|
模型: 모델
|
||||||
|
API: API
|
||||||
|
定价: 가격
|
||||||
|
时序: 시계열
|
||||||
|
开始日期: 시작 날짜
|
||||||
|
结束日期: 종료 날짜
|
||||||
|
生效日期: 시작일
|
||||||
|
失效日期: 만료일
|
||||||
|
定价数据: 가격 데이터
|
||||||
|
定价项目时序: 가격 항목 시계열
|
||||||
|
测试: 테스트
|
||||||
|
定价测试: 가격 테스트
|
||||||
|
新增: 추가
|
||||||
|
保存: 저장
|
||||||
|
取消: 취소
|
||||||
|
确认: 확인
|
||||||
|
删除: 삭제
|
||||||
|
编辑: 편집
|
||||||
|
查看: 조회
|
||||||
|
导出: 내보내기
|
||||||
|
打印: 인쇄
|
||||||
|
刷新: 새로고침
|
||||||
|
返回: 뒤로
|
||||||
|
提交: 제출
|
||||||
|
重置: 초기화
|
||||||
|
Conform: 확인
|
||||||
|
Discard: 폐기
|
||||||
|
Submit: 제출
|
||||||
|
Reset: 초기화
|
||||||
|
Cancel: 취소
|
||||||
|
搜索: 검색
|
||||||
|
操作: 작업
|
||||||
|
类型: 유형
|
||||||
|
状态: 상태
|
||||||
|
名称: 이름
|
||||||
|
编码: 코드
|
||||||
|
备注: 비고
|
||||||
|
创建时间: 생성 시간
|
||||||
|
更新时间: 업데이트 시간
|
||||||
|
全部: 전체
|
||||||
140
i18n/zh/msg.txt
Normal file
140
i18n/zh/msg.txt
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
API接口: API接口
|
||||||
|
Add Error: Add Error
|
||||||
|
Add Success: Add Success
|
||||||
|
Authorization Error: Authorization Error
|
||||||
|
Cancel: Cancel
|
||||||
|
Conform: Conform
|
||||||
|
Delete Error: Delete Error
|
||||||
|
Delete Success: Delete Success
|
||||||
|
Discard: Discard
|
||||||
|
Error: Error
|
||||||
|
ID: ID
|
||||||
|
Invalid: Invalid
|
||||||
|
Messages array cannot be empty: Messages array cannot be empty
|
||||||
|
Missing required parameter\x3A model: Missing required parameter\x3A model
|
||||||
|
Not enrogh balance to use llm: Not enrogh balance to use llm
|
||||||
|
Please login: Please login
|
||||||
|
Record no exist or with wrong ownership: Record no exist or with wrong ownership
|
||||||
|
Reset: Reset
|
||||||
|
Submit: Submit
|
||||||
|
Success: Success
|
||||||
|
Update Error: Update Error
|
||||||
|
Update Success: Update Success
|
||||||
|
You need login to use llm: You need login to use llm
|
||||||
|
failed: failed
|
||||||
|
id: id
|
||||||
|
model parameter required: model parameter required
|
||||||
|
need a config_data: need a config_data
|
||||||
|
need a llmid: need a llmid
|
||||||
|
ok: ok
|
||||||
|
server error: server error
|
||||||
|
上位系统id: 上位系统id
|
||||||
|
上架: 上架
|
||||||
|
上架状态: 上架状态
|
||||||
|
上线检查: 上线检查
|
||||||
|
下架: 下架
|
||||||
|
主键ID: 主键ID
|
||||||
|
交互内容: 交互内容
|
||||||
|
交易号: 交易号
|
||||||
|
交易成本: 交易成本
|
||||||
|
交易金额: 交易金额
|
||||||
|
从IO文件恢复Usages: 从IO文件恢复Usages
|
||||||
|
任务号: 任务号
|
||||||
|
任务查询间隔(秒): 任务查询间隔(秒)
|
||||||
|
任务结果查询接口名称: 任务结果查询接口名称
|
||||||
|
体验一次: 体验一次
|
||||||
|
使用信息: 使用信息
|
||||||
|
使用日期: 使用日期
|
||||||
|
使用时间: 使用时间
|
||||||
|
使用记录ID: 使用记录ID
|
||||||
|
使用记录id: 使用记录id
|
||||||
|
供应商id: 供应商id
|
||||||
|
供应商模型列表: 供应商模型列表
|
||||||
|
全部: 全部
|
||||||
|
分类: 分类
|
||||||
|
删除成功: 删除成功
|
||||||
|
历史数据为只读,不可修改: 历史数据为只读,不可修改
|
||||||
|
历史数据为只读,不可删除: 历史数据为只读,不可删除
|
||||||
|
历史数据为只读,不可新增: 历史数据为只读,不可新增
|
||||||
|
原因: 原因
|
||||||
|
名称: 名称
|
||||||
|
启用日期: 启用日期
|
||||||
|
响应时间: 响应时间
|
||||||
|
图标id: 图标id
|
||||||
|
处理备注: 处理备注
|
||||||
|
处理时间: 处理时间
|
||||||
|
处理状态: 处理状态
|
||||||
|
备份时间: 备份时间
|
||||||
|
大语言模型: 大语言模型
|
||||||
|
失效日期: 失效日期
|
||||||
|
失败原因: 失败原因
|
||||||
|
失败时间: 失败时间
|
||||||
|
定价ID: 定价ID
|
||||||
|
开始日期: 开始日期
|
||||||
|
恢复Usages: 恢复Usages
|
||||||
|
恢复Usages失败: 恢复Usages失败
|
||||||
|
恢复Usages完成: 恢复Usages完成
|
||||||
|
成功: 成功
|
||||||
|
所属机构id: 所属机构id
|
||||||
|
按供应商: 按供应商
|
||||||
|
按分类: 按分类
|
||||||
|
探索和使用各类AI模型: 探索和使用各类AI模型
|
||||||
|
接口名称: 接口名称
|
||||||
|
提示: 提示
|
||||||
|
操作: 操作
|
||||||
|
无效的参数,未找到模型ID: 无效的参数,未找到模型ID
|
||||||
|
无权删除该映射: 无权删除该映射
|
||||||
|
无权操作该模型: 无权操作该模型
|
||||||
|
是否已处理: 是否已处理
|
||||||
|
最低余额: 最低余额
|
||||||
|
机构: 机构
|
||||||
|
查询API: 查询API
|
||||||
|
查询间隔(秒): 查询间隔(秒)
|
||||||
|
检查计费: 检查计费
|
||||||
|
模型: 模型
|
||||||
|
模型API映射表: 模型API映射表
|
||||||
|
模型ID: 模型ID
|
||||||
|
模型id: 模型id
|
||||||
|
模型、分类和API接口为必填项: 模型、分类和API接口为必填项
|
||||||
|
模型使用: 模型使用
|
||||||
|
模型使用历史记录: 模型使用历史记录
|
||||||
|
模型分类ID: 模型分类ID
|
||||||
|
模型列表: 模型列表
|
||||||
|
模型名称: 模型名称
|
||||||
|
模型广场: 模型广场
|
||||||
|
模型机构id: 模型机构id
|
||||||
|
模型用量: 模型用量
|
||||||
|
模型类型: 模型类型
|
||||||
|
模型类型管理: 模型类型管理
|
||||||
|
模型类目: 模型类目
|
||||||
|
没找到模型: 没找到模型
|
||||||
|
没有找到需要恢复的记录: 没有找到需要恢复的记录
|
||||||
|
添加成功: 添加成功
|
||||||
|
添加映射: 添加映射
|
||||||
|
状态: 状态
|
||||||
|
用户: 用户
|
||||||
|
用户id: 用户id
|
||||||
|
用户机构id: 用户机构id
|
||||||
|
类型名: 类型名
|
||||||
|
类型名不能为空: 类型名不能为空
|
||||||
|
类型说明: 类型说明
|
||||||
|
结束日期: 结束日期
|
||||||
|
结束时间: 结束时间
|
||||||
|
缺少ID参数: 缺少ID参数
|
||||||
|
缺省分类: 缺省分类
|
||||||
|
能力映射: 能力映射
|
||||||
|
计费项目: 计费项目
|
||||||
|
记录ID不能为空: 记录ID不能为空
|
||||||
|
记账失败记录: 记账失败记录
|
||||||
|
记账状态: 记账状态
|
||||||
|
识别名: 识别名
|
||||||
|
该模型的此API映射已存在: 该模型的此API映射已存在
|
||||||
|
说明: 说明
|
||||||
|
账户余额不够: 账户余额不够
|
||||||
|
选择分类: 选择分类
|
||||||
|
重试: 重试
|
||||||
|
重试次数: 重试次数
|
||||||
|
重试记账: 重试记账
|
||||||
|
金额: 金额
|
||||||
|
错误: 错误
|
||||||
|
间隔(秒): 间隔(秒)
|
||||||
47
init/data.json
Normal file
47
init/data.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"appcodes": [
|
||||||
|
{
|
||||||
|
"id": "llm_status",
|
||||||
|
"name": "模型上架状态",
|
||||||
|
"hierarchy_flg": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "llmusage_status",
|
||||||
|
"name": "调用状态",
|
||||||
|
"hierarchy_flg": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "accounting_status",
|
||||||
|
"name": "记账状态",
|
||||||
|
"hierarchy_flg": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "handled_flg",
|
||||||
|
"name": "是否已处理",
|
||||||
|
"hierarchy_flg": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "isdefaultcatelog_flg",
|
||||||
|
"name": "是否缺省分类",
|
||||||
|
"hierarchy_flg": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"appcodes_kv": [
|
||||||
|
{"id": "llm_status_published", "parentid": "llm_status", "k": "published", "v": "已上架"},
|
||||||
|
{"id": "llm_status_unpublished", "parentid": "llm_status", "k": "unpublished", "v": "已下架"},
|
||||||
|
|
||||||
|
{"id": "llmusage_status_succeeded", "parentid": "llmusage_status", "k": "SUCCEEDED", "v": "成功"},
|
||||||
|
{"id": "llmusage_status_failed", "parentid": "llmusage_status", "k": "FAILED", "v": "失败"},
|
||||||
|
{"id": "llmusage_status_unknown", "parentid": "llmusage_status", "k": "UNKNOWN", "v": "未知"},
|
||||||
|
|
||||||
|
{"id": "accounting_status_created", "parentid": "accounting_status", "k": "created", "v": "待记账"},
|
||||||
|
{"id": "accounting_status_accounted", "parentid": "accounting_status", "k": "accounted", "v": "已记账"},
|
||||||
|
{"id": "accounting_status_failed", "parentid": "accounting_status", "k": "failed", "v": "记账失败"},
|
||||||
|
|
||||||
|
{"id": "handled_flg_0", "parentid": "handled_flg", "k": "0", "v": "未处理"},
|
||||||
|
{"id": "handled_flg_1", "parentid": "handled_flg", "k": "1", "v": "已处理"},
|
||||||
|
|
||||||
|
{"id": "isdefaultcatelog_flg_0", "parentid": "isdefaultcatelog_flg", "k": "0", "v": "否"},
|
||||||
|
{"id": "isdefaultcatelog_flg_1", "parentid": "isdefaultcatelog_flg", "k": "1", "v": "是"}
|
||||||
|
]
|
||||||
|
}
|
||||||
101
json/llm.json
101
json/llm.json
@ -4,21 +4,59 @@
|
|||||||
"params": {
|
"params": {
|
||||||
"sortby":"model",
|
"sortby":"model",
|
||||||
"logined_userorgid": "ownerid",
|
"logined_userorgid": "ownerid",
|
||||||
|
"data_filter": {
|
||||||
|
"AND": [
|
||||||
|
{"field": "name", "op": "LIKE", "var": "name_input"},
|
||||||
|
{"field": "model", "op": "LIKE", "var": "model_input"},
|
||||||
|
{"field": "providerid", "op": "=", "var": "providerid_input"},
|
||||||
|
{"field": "upappid", "op": "=", "var": "upappid_input"},
|
||||||
|
{"field": "status", "op": "=", "var": "status_input"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filter_labels": {
|
||||||
|
"name_input": "名称",
|
||||||
|
"model_input": "识别名",
|
||||||
|
"providerid_input": "供应商",
|
||||||
|
"upappid_input": "上位系统",
|
||||||
|
"status_input": "上架状态"
|
||||||
|
},
|
||||||
"browserfields": {
|
"browserfields": {
|
||||||
"exclouded": ["id", "ownerid"],
|
"exclouded": ["id", "ownerid"],
|
||||||
"alters": {
|
"alters": {
|
||||||
"ppid":{
|
"ppid":{
|
||||||
"dataurl":"{{entire_url('/pricing/get_all_pricing_programs.dspy')}}",
|
"dataurl":"{{entire_url('/pricing/get_all_pricing_programs.dspy')}}",
|
||||||
"textField": "name",
|
"textField": "name",
|
||||||
"valueField": "id"
|
"valueField": "id"
|
||||||
}
|
},
|
||||||
|
"providerid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "{{entire_url('../api/get_search_providerid.dspy')}}",
|
||||||
|
"valueField": "providerid",
|
||||||
|
"textField": "providerid_text"
|
||||||
|
},
|
||||||
|
"upappid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "{{entire_url('../api/get_search_upappid.dspy')}}",
|
||||||
|
"valueField": "upappid",
|
||||||
|
"textField": "upappid_text"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"tools":[
|
"tools":[
|
||||||
{
|
{
|
||||||
"name":"test",
|
"name":"launch_check",
|
||||||
"label":"体验",
|
"label":"上线检查",
|
||||||
|
"selected_row":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"publish",
|
||||||
|
"label":"上架",
|
||||||
|
"selected_row":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"unpublish",
|
||||||
|
"label":"下架",
|
||||||
"selected_row":true
|
"selected_row":true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -26,25 +64,66 @@
|
|||||||
"binds":[
|
"binds":[
|
||||||
{
|
{
|
||||||
"wid":"self",
|
"wid":"self",
|
||||||
"event":"test",
|
"event":"launch_check",
|
||||||
"actiontype":"urlwidget",
|
"actiontype":"urlwidget",
|
||||||
"target":"PopupWindow",
|
"target":"PopupWindow",
|
||||||
"popup_options":{
|
"popup_options":{
|
||||||
"title":"model Test",
|
"title":"上线检查",
|
||||||
"cwidth":22,
|
"cwidth":25,
|
||||||
"height":"75%"
|
"cheight":20
|
||||||
},
|
},
|
||||||
"options":{
|
"options":{
|
||||||
"url":"{{entire_url('./llm_dialog.ui')}}",
|
"url":"{{entire_url('./llm_launch_check.ui')}}",
|
||||||
"params":{
|
"params":{
|
||||||
"id":"${id}"
|
"id":"${id}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"publish",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"PopupWindow",
|
||||||
|
"popup_options":{
|
||||||
|
"title":"上架",
|
||||||
|
"cwidth":20,
|
||||||
|
"cheight":8
|
||||||
|
},
|
||||||
|
"options":{
|
||||||
|
"url":"{{entire_url('../api/llm_status_update.dspy')}}",
|
||||||
|
"params":{
|
||||||
|
"id":"${id}",
|
||||||
|
"action":"published"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"unpublish",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"PopupWindow",
|
||||||
|
"popup_options":{
|
||||||
|
"title":"下架",
|
||||||
|
"cwidth":20,
|
||||||
|
"cheight":8
|
||||||
|
},
|
||||||
|
"options":{
|
||||||
|
"url":"{{entire_url('../api/llm_status_update.dspy')}}",
|
||||||
|
"params":{
|
||||||
|
"id":"${id}",
|
||||||
|
"action":"unpublished"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"editexclouded": [
|
"editexclouded": [
|
||||||
"id", "ownerid"
|
"id", "ownerid"
|
||||||
],
|
],
|
||||||
|
"editable": {
|
||||||
|
"new_data_url": "{{entire_url('../api/llm_create.dspy')}}",
|
||||||
|
"update_data_url": "{{entire_url('../api/llm_update.dspy')}}",
|
||||||
|
"delete_data_url": "{{entire_url('../api/llm_delete.dspy')}}"
|
||||||
|
},
|
||||||
"subtables":[
|
"subtables":[
|
||||||
{
|
{
|
||||||
"field":"llmid",
|
"field":"llmid",
|
||||||
|
|||||||
@ -5,6 +5,18 @@
|
|||||||
"browserfields": {
|
"browserfields": {
|
||||||
"exclouded": ["id", "llmid"],
|
"exclouded": ["id", "llmid"],
|
||||||
"alters": {
|
"alters": {
|
||||||
|
"apiname": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "{{entire_url('../api/get_search_apiname.dspy')}}?llmid={{params_kw.llmid}}",
|
||||||
|
"valueField": "apiname",
|
||||||
|
"textField": "apiname_text"
|
||||||
|
},
|
||||||
|
"query_apiname": {
|
||||||
|
"uitype": "code",
|
||||||
|
"dataurl": "{{entire_url('../api/get_search_apiname.dspy')}}?allow_empty=1&llmid={{params_kw.llmid}}",
|
||||||
|
"valueField": "apiname",
|
||||||
|
"textField": "apiname_text"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editexclouded": ["id", "llmid"]
|
"editexclouded": ["id", "llmid"]
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
},
|
},
|
||||||
"editexclouded": ["id"],
|
"editexclouded": ["id"],
|
||||||
"editable": {
|
"editable": {
|
||||||
|
"get_data_url": "{{entire_url(‘get_llmusage.dspy')}}?pagerows=50",
|
||||||
"new_data_url": "{{entire_url('../api/llmusage_create.dspy')}}",
|
"new_data_url": "{{entire_url('../api/llmusage_create.dspy')}}",
|
||||||
"update_data_url": "{{entire_url('../api/llmusage_update.dspy')}}",
|
"update_data_url": "{{entire_url('../api/llmusage_update.dspy')}}",
|
||||||
"delete_data_url": "{{entire_url('../api/llmusage_delete.dspy')}}"
|
"delete_data_url": "{{entire_url('../api/llmusage_delete.dspy')}}"
|
||||||
|
|||||||
@ -3,17 +3,34 @@
|
|||||||
"title": "记账失败记录",
|
"title": "记账失败记录",
|
||||||
"params": {
|
"params": {
|
||||||
"sortby": "failed_time desc",
|
"sortby": "failed_time desc",
|
||||||
"browserfields": {
|
"toolbar": {
|
||||||
"exclouded": ["id"],
|
"tools": [
|
||||||
"alters": {
|
{
|
||||||
"handled": {
|
"name": "show_reason",
|
||||||
"uitype": "code",
|
"label": "原因",
|
||||||
"data": [
|
"selected_row": true
|
||||||
{"value": "0", "text": "未处理"},
|
}
|
||||||
{"value": "1", "text": "已处理"}
|
]
|
||||||
]
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "show_reason",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "PopupWindow",
|
||||||
|
"popup_options": {
|
||||||
|
"title": "失败原因",
|
||||||
|
"cwidth": 30,
|
||||||
|
"cheight": 20
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('../api/show_failed_reason.dspy')}}?id=${id}$"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id", "failed_reason"],
|
||||||
|
"alters": {}
|
||||||
},
|
},
|
||||||
"editexclouded": ["id", "llmusageid", "failed_time"],
|
"editexclouded": ["id", "llmusageid", "failed_time"],
|
||||||
"editable": {
|
"editable": {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ async def checkCustomerBalance(llmid, userid, userorgid, catelogid=None):
|
|||||||
debug(f'checkCustomerBalance(): llmid is None')
|
debug(f'checkCustomerBalance(): llmid is None')
|
||||||
return False
|
return False
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
llm = await get_llm(llmid)
|
llm = await get_llmage_llm(llmid)
|
||||||
if llm.ownerid == userorgid:
|
if llm.ownerid == userorgid:
|
||||||
debug(f'self orgid user')
|
debug(f'self orgid user')
|
||||||
return True
|
return True
|
||||||
@ -64,37 +64,34 @@ async def checkCustomerBalance(llmid, userid, userorgid, catelogid=None):
|
|||||||
async def llm_accounting(llmusage):
|
async def llm_accounting(llmusage):
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
llmid = llmusage.llmid
|
llmid = llmusage.llmid
|
||||||
|
llm = await get_llmage_llm(llmid)
|
||||||
|
if llm is None:
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
ns = {
|
||||||
|
'id': llmusage.id,
|
||||||
|
'accounting_status': 'failed'
|
||||||
|
}
|
||||||
|
await sor.U('llmusage', ns)
|
||||||
|
e = Exception(f'llm not found({llmid})')
|
||||||
|
exception(f'{e}')
|
||||||
|
raise e
|
||||||
|
if llm.ppid is None:
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
ns = {
|
||||||
|
'id': llmusage.id,
|
||||||
|
'accounting_status': 'failed'
|
||||||
|
}
|
||||||
|
await sor.U('llmusage', ns)
|
||||||
|
e = Exception(f'llm ({llmid}) donot has a pricing_program')
|
||||||
|
exception(f'{e}')
|
||||||
|
raise e
|
||||||
|
customerid = llmusage.userorgid
|
||||||
|
userid = llmusage.userid
|
||||||
|
resellerid = llm.ownerid
|
||||||
|
providerid = llm.providerid
|
||||||
|
trans_amount = llmusage.amount
|
||||||
|
trans_cost = llmusage.cost
|
||||||
async with get_sor_context(env, 'llmage') as sor:
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
sql = """select a.*, b.ppid from llm a, llm_api_map b
|
|
||||||
where a.id=${llmid}$
|
|
||||||
and a.id = b.llmid
|
|
||||||
and b.isdefaultcatelog = '1'
|
|
||||||
"""
|
|
||||||
recs = await sor.sqlExe(sql, {'llmid': llmusage.llmid})
|
|
||||||
if len(recs) == 0:
|
|
||||||
ns = {
|
|
||||||
'id': llmusage.id,
|
|
||||||
'accounting_status': 'failed'
|
|
||||||
}
|
|
||||||
await sor.U('llmusage', ns)
|
|
||||||
e = Exception(f'llm not found({llmid})')
|
|
||||||
exception(f'{e}')
|
|
||||||
raise e
|
|
||||||
if recs[0].ppid is None:
|
|
||||||
ns = {
|
|
||||||
'id': llmusage.id,
|
|
||||||
'accounting_status': 'failed'
|
|
||||||
}
|
|
||||||
await sor.U('llmusage', ns)
|
|
||||||
e = Exception(f'llm ({llmid}) donot has a pricing_program')
|
|
||||||
exception(f'{e}')
|
|
||||||
raise e
|
|
||||||
customerid = llmusage.userorgid
|
|
||||||
userid = llmusage.userid
|
|
||||||
resellerid = recs[0].ownerid
|
|
||||||
providerid = recs[0].providerid
|
|
||||||
trans_amount = llmusage.amount
|
|
||||||
trans_cost = llmusage.cost
|
|
||||||
biz_date = await env.get_business_date(sor)
|
biz_date = await env.get_business_date(sor)
|
||||||
timestamp = env.timestampstr()
|
timestamp = env.timestampstr()
|
||||||
orderid = getID()
|
orderid = getID()
|
||||||
@ -163,7 +160,7 @@ async def get_accounting_llmusages(luid=None):
|
|||||||
dt = datetime.fromtimestamp(t)
|
dt = datetime.fromtimestamp(t)
|
||||||
tsstr = dt.strftime('%Y-%m-%d %H:%M:%S.') + f'{dt.microsecond // 1000:03d}'
|
tsstr = dt.strftime('%Y-%m-%d %H:%M:%S.') + f'{dt.microsecond // 1000:03d}'
|
||||||
async with get_sor_context(env, 'llmage') as sor:
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
sql = """select a.*, c.ppid
|
sql = """select a.*, b.model, c.ppid
|
||||||
from llmusage a, llm b, llm_api_map c
|
from llmusage a, llm b, llm_api_map c
|
||||||
where a.llmid = b.id
|
where a.llmid = b.id
|
||||||
and a.llmid = c.llmid
|
and a.llmid = c.llmid
|
||||||
@ -335,7 +332,7 @@ async def backend_accounting():
|
|||||||
tpac = await get_user_tpac(lu.userid)
|
tpac = await get_user_tpac(lu.userid)
|
||||||
if tpac:
|
if tpac:
|
||||||
debug(f'{lu.id=},{lu.userid=}, {tpac=}, go tpac')
|
debug(f'{lu.id=},{lu.userid=}, {tpac=}, go tpac')
|
||||||
await tpac_accounting(tpac, lu.userid, lu.llmid, lu.amount, lu.usages, lu.id)
|
await tpac_accounting(tpac, lu.userid, lu.llmid, lu.amount, lu.usages, lu.id, lu.model)
|
||||||
else:
|
else:
|
||||||
debug(f'{lu.id=},{lu.userid=}, {tpac=}, go local')
|
debug(f'{lu.id=},{lu.userid=}, {tpac=}, go local')
|
||||||
await llm_accounting(lu)
|
await llm_accounting(lu)
|
||||||
|
|||||||
@ -75,7 +75,8 @@ async def async_uapi_request(request, llm,
|
|||||||
estr = erase_apikey(e)
|
estr = erase_apikey(e)
|
||||||
ed = {"error": f"ERROR:{estr}", "status": "FAILED"}
|
ed = {"error": f"ERROR:{estr}", "status": "FAILED"}
|
||||||
exception(f'{ed}')
|
exception(f'{ed}')
|
||||||
yield f'{ed}\n'
|
estr = json.dumps(ed, ensure_ascii=False)
|
||||||
|
yield f'{estr}\n'
|
||||||
return
|
return
|
||||||
if isinstance(b, bytes):
|
if isinstance(b, bytes):
|
||||||
b = b.decode('utf-8')
|
b = b.decode('utf-8')
|
||||||
@ -141,9 +142,15 @@ async def get_llm_llmusage(luid):
|
|||||||
return
|
return
|
||||||
if llmusage.status == 'FAILED':
|
if llmusage.status == 'FAILED':
|
||||||
return
|
return
|
||||||
llms = await sor.R('llm', {'id': llmusage.llmid})
|
# Use JOIN to get query_apiname/query_period from llm_api_map
|
||||||
|
sql = """select a.id, a.name, a.model, a.upappid, a.ownerid, a.status,
|
||||||
|
m.apiname, m.query_apiname, m.query_period, m.ppid
|
||||||
|
from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
where a.id = ${llmid}$ and m.isdefaultcatelog = '1'"""
|
||||||
|
llms = await sor.sqlExe(sql, {'llmid': llmusage.llmid})
|
||||||
if len(llms) == 0:
|
if len(llms) == 0:
|
||||||
e = Exception(f'{llmusage.llmid=} not found in llm')
|
e = Exception(f'{llmusage.llmid=} not found in llm/llm_api_map')
|
||||||
exception(f'{e}')
|
exception(f'{e}')
|
||||||
raise e
|
raise e
|
||||||
llm = llms[0]
|
llm = llms[0]
|
||||||
|
|||||||
@ -9,13 +9,18 @@ from .utils import (
|
|||||||
llm_query_orders,
|
llm_query_orders,
|
||||||
read_webpath,
|
read_webpath,
|
||||||
llm_query_price,
|
llm_query_price,
|
||||||
|
get_user_tpac,
|
||||||
|
get_tpac_balance,
|
||||||
get_llm_by_model,
|
get_llm_by_model,
|
||||||
get_llms_by_catelog,
|
get_llms_by_catelog,
|
||||||
get_llms_sort_by_provider,
|
get_llms_sort_by_provider,
|
||||||
get_llmcatelogs,
|
get_llmcatelogs,
|
||||||
get_llms_by_catelog_to_customer,
|
get_llms_by_catelog_to_customer,
|
||||||
get_llmproviders,
|
get_llmproviders,
|
||||||
get_llm,
|
get_llm,
|
||||||
|
get_llmage_llm,
|
||||||
|
get_llm_catelogs,
|
||||||
|
invalidate_uapi_cache,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .llmclient import (
|
from .llmclient import (
|
||||||
@ -32,6 +37,7 @@ from .accounting import (
|
|||||||
get_failed_accounting_records,
|
get_failed_accounting_records,
|
||||||
llm_accoung_failed
|
llm_accoung_failed
|
||||||
)
|
)
|
||||||
|
from .stats import get_llmage_stats
|
||||||
|
|
||||||
from .asyncinference import (
|
from .asyncinference import (
|
||||||
get_asynctask_status,
|
get_asynctask_status,
|
||||||
@ -39,6 +45,14 @@ from .asyncinference import (
|
|||||||
get_today_asynctask_list
|
get_today_asynctask_list
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _on_hot_reload(data=None):
|
||||||
|
"""Event handler for hot_reload — wraps invalidate_uapi_cache to accept dispatcher's data arg."""
|
||||||
|
from appPublic.log import debug
|
||||||
|
debug(f'[llmage] on_hot_reload called, invalidating uapi cache (data={data})')
|
||||||
|
invalidate_uapi_cache()
|
||||||
|
|
||||||
|
|
||||||
def load_llmage():
|
def load_llmage():
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
env.llm_query_orders = llm_query_orders
|
env.llm_query_orders = llm_query_orders
|
||||||
@ -51,7 +65,12 @@ def load_llmage():
|
|||||||
env.get_asynctask_status = get_asynctask_status
|
env.get_asynctask_status = get_asynctask_status
|
||||||
env.query_task_status = query_task_status
|
env.query_task_status = query_task_status
|
||||||
env.get_llm = get_llm
|
env.get_llm = get_llm
|
||||||
|
env.get_llmage_llm = get_llmage_llm
|
||||||
|
env.get_llm_catelogs = get_llm_catelogs
|
||||||
|
env.invalidate_uapi_cache = invalidate_uapi_cache
|
||||||
env.inference = inference
|
env.inference = inference
|
||||||
|
env.get_user_tpac = get_user_tpac
|
||||||
|
env.get_tpac_balance = get_tpac_balance
|
||||||
env.inference_generator = inference_generator
|
env.inference_generator = inference_generator
|
||||||
env.get_llms_by_catelog = get_llms_by_catelog
|
env.get_llms_by_catelog = get_llms_by_catelog
|
||||||
env.get_llmcatelogs = get_llmcatelogs
|
env.get_llmcatelogs = get_llmcatelogs
|
||||||
@ -63,6 +82,10 @@ def load_llmage():
|
|||||||
env.get_llms_by_catelog_to_customer = get_llms_by_catelog_to_customer
|
env.get_llms_by_catelog_to_customer = get_llms_by_catelog_to_customer
|
||||||
env.backup_accounted_llmusage = backup_accounted_llmusage
|
env.backup_accounted_llmusage = backup_accounted_llmusage
|
||||||
env.get_failed_accounting_records = get_failed_accounting_records
|
env.get_failed_accounting_records = get_failed_accounting_records
|
||||||
|
env.get_llmage_stats = get_llmage_stats
|
||||||
|
# Bind hot_reload event — module-level function, ref safe (module keeps it alive)
|
||||||
|
if hasattr(env, 'event_dispatcher'):
|
||||||
|
env.event_dispatcher.bind('hot_reload', _on_hot_reload)
|
||||||
rf = RegisterFunction()
|
rf = RegisterFunction()
|
||||||
rf.register('jimeng_auth_headers', jimeng_auth_headers)
|
rf.register('jimeng_auth_headers', jimeng_auth_headers)
|
||||||
|
|
||||||
|
|||||||
@ -116,8 +116,9 @@ async def _inference_generator(request, callerid, callerorgid,
|
|||||||
if not params_kw.transno:
|
if not params_kw.transno:
|
||||||
params_kw.transno = getID()
|
params_kw.transno = getID()
|
||||||
llmid = params_kw.llmid
|
llmid = params_kw.llmid
|
||||||
|
catelogid = params_kw.get('llmcatelogid', None)
|
||||||
f = None
|
f = None
|
||||||
llm = await get_llm(llmid)
|
llm = await get_llm(llmid, catelogid)
|
||||||
if llm is None:
|
if llm is None:
|
||||||
errmsg = f'{{"status": "FAILED", "error":"llmid:{llmid}没找到模型"}}\n'
|
errmsg = f'{{"status": "FAILED", "error":"llmid:{llmid}没找到模型"}}\n'
|
||||||
exception(errmsg)
|
exception(errmsg)
|
||||||
|
|||||||
69
llmage/stats.py
Normal file
69
llmage/stats.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from sqlor.dbpools import get_sor_context
|
||||||
|
from appPublic.timeUtils import curDateString, timestampstr
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from appPublic.log import debug, exception
|
||||||
|
|
||||||
|
async def get_llmage_stats(request):
|
||||||
|
"""Get llmage module statistics"""
|
||||||
|
env = request._run_ns
|
||||||
|
userorgid = await env.get_userorgid()
|
||||||
|
today = curDateString()
|
||||||
|
tomorrow = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
'total_models': 0,
|
||||||
|
'today_usage_count': 0,
|
||||||
|
'today_amount': 0,
|
||||||
|
'catelog_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
# Total enabled models
|
||||||
|
sql_models = """
|
||||||
|
SELECT COUNT(DISTINCT id) as cnt FROM llm
|
||||||
|
WHERE enabled_date <= ${today}$
|
||||||
|
AND expired_date > ${today}$
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql_models, {'today': today})
|
||||||
|
if recs:
|
||||||
|
stats['total_models'] = int(recs[0].cnt or 0)
|
||||||
|
|
||||||
|
# Today's usage count
|
||||||
|
sql_usage = """
|
||||||
|
SELECT COUNT(*) as cnt FROM llmusage
|
||||||
|
WHERE userorgid = ${userorgid}$
|
||||||
|
AND use_date >= ${today}$
|
||||||
|
AND use_date < ${tomorrow}$
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql_usage, {
|
||||||
|
'userorgid': userorgid,
|
||||||
|
'today': today,
|
||||||
|
'tomorrow': tomorrow
|
||||||
|
})
|
||||||
|
if recs:
|
||||||
|
stats['today_usage_count'] = int(recs[0].cnt or 0)
|
||||||
|
|
||||||
|
# Today's total amount
|
||||||
|
sql_amount = """
|
||||||
|
SELECT COALESCE(SUM(amount), 0) as total FROM llmusage
|
||||||
|
WHERE userorgid = ${userorgid}$
|
||||||
|
AND use_date >= ${today}$
|
||||||
|
AND use_date < ${tomorrow}$
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql_amount, {
|
||||||
|
'userorgid': userorgid,
|
||||||
|
'today': today,
|
||||||
|
'tomorrow': tomorrow
|
||||||
|
})
|
||||||
|
if recs:
|
||||||
|
stats['today_amount'] = float(recs[0].total or 0)
|
||||||
|
|
||||||
|
# Catalog count
|
||||||
|
sql_catelog = """
|
||||||
|
SELECT COUNT(*) as cnt FROM llmcatelog
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql_catelog, {})
|
||||||
|
if recs:
|
||||||
|
stats['catelog_count'] = int(recs[0].cnt or 0)
|
||||||
|
|
||||||
|
return stats
|
||||||
244
llmage/utils.py
244
llmage/utils.py
@ -1,21 +1,68 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from random import randint
|
from random import randint
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
import time
|
||||||
from sqlor.dbpools import DBPools, get_sor_context
|
from sqlor.dbpools import DBPools, get_sor_context
|
||||||
from appPublic.log import debug, exception, error, critical
|
from appPublic.log import debug, exception, error, critical
|
||||||
from appPublic.uniqueID import getID
|
from appPublic.uniqueID import getID
|
||||||
from appPublic.dictObject import DictObject
|
from appPublic.dictObject import DictObject
|
||||||
from appPublic.timeUtils import curDateString, timestampstr
|
from appPublic.timeUtils import curDateString, timestampstr
|
||||||
from uapi.appapi import UAPI, sor_get_callerid, sor_get_uapi
|
from uapi.appapi import UAPI, sor_get_callerid, sor_get_uapi, get_uapi
|
||||||
from ahserver.serverenv import get_serverenv, ServerEnv
|
from ahserver.serverenv import get_serverenv, ServerEnv
|
||||||
from ahserver.filestorage import FileStorage
|
from ahserver.filestorage import FileStorage
|
||||||
from appPublic.jsonConfig import getConfig
|
from appPublic.jsonConfig import getConfig
|
||||||
from appPublic.streamhttpclient import StreamHttpClient
|
from appPublic.streamhttpclient import StreamHttpClient
|
||||||
|
|
||||||
|
# =============================================================
|
||||||
|
# Process-level cache for uapi/uapiio (static config, rarely changes)
|
||||||
|
# =============================================================
|
||||||
|
_UAPI_CACHE_TTL = 300 # 5 minutes
|
||||||
|
_uapi_cache = {} # key: "upappid:apiname" -> {data, ts}
|
||||||
|
_uapiio_cache = {} # key: "ioid" -> {data, ts}
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_uapi_cached(upappid, apiname):
|
||||||
|
"""Get uapi record with process-level cache (uapi config rarely changes)"""
|
||||||
|
global _uapi_cache
|
||||||
|
cache_key = f"{upappid}:{apiname}"
|
||||||
|
cached = _uapi_cache.get(cache_key)
|
||||||
|
if cached and (time.time() - cached['ts']) < _UAPI_CACHE_TTL:
|
||||||
|
return cached['data']
|
||||||
|
uapi_rec = await get_uapi(upappid, apiname)
|
||||||
|
_uapi_cache[cache_key] = {'data': uapi_rec, 'ts': time.time()}
|
||||||
|
return uapi_rec
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_uapiio_cached(ioid):
|
||||||
|
"""Get uapiio record with process-level cache (io config rarely changes)"""
|
||||||
|
global _uapiio_cache
|
||||||
|
if ioid is None:
|
||||||
|
return None
|
||||||
|
cached = _uapiio_cache.get(ioid)
|
||||||
|
if cached and (time.time() - cached['ts']) < _UAPI_CACHE_TTL:
|
||||||
|
return cached['data']
|
||||||
|
env = ServerEnv()
|
||||||
|
uapi_dbname = get_serverenv('get_module_dbname')('uapi')
|
||||||
|
async with DBPools().sqlorContext(uapi_dbname) as sor:
|
||||||
|
recs = await sor.R('uapiio', {'id': ioid})
|
||||||
|
result = recs[0] if recs else None
|
||||||
|
_uapiio_cache[ioid] = {'data': result, 'ts': time.time()}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_uapi_cache(upappid=None, apiname=None):
|
||||||
|
"""Invalidate uapi/uapiio cache entries. Call when uapi config changes."""
|
||||||
|
global _uapi_cache, _uapiio_cache
|
||||||
|
if upappid and apiname:
|
||||||
|
_uapi_cache.pop(f"{upappid}:{apiname}", None)
|
||||||
|
else:
|
||||||
|
_uapi_cache.clear()
|
||||||
|
_uapiio_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
async def update_llmusage(ns):
|
async def update_llmusage(ns):
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
async with get_sor_context(env, 'llmage') as sor:
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
@ -46,13 +93,14 @@ async def get_tpac_balance(tpac, userid):
|
|||||||
exception(f'{url=}, {userid=}, error:{e}')
|
exception(f'{url=}, {userid=}, error:{e}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def tpac_accounting(tpac, userid, llmid, amount, usage, luid):
|
async def tpac_accounting(tpac, userid, llmid, amount, usage, luid, model):
|
||||||
url = tpac.tpac_accounting_url
|
url = tpac.tpac_accounting_url
|
||||||
hc = StreamHttpClient()
|
hc = StreamHttpClient()
|
||||||
d = {
|
d = {
|
||||||
'userid': userid,
|
'userid': userid,
|
||||||
'llmid': llmid,
|
'llmid': llmid,
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
|
'model': model,
|
||||||
'usage': usage
|
'usage': usage
|
||||||
}
|
}
|
||||||
status = 'failed'
|
status = 'failed'
|
||||||
@ -154,6 +202,7 @@ async def get_llmproviders():
|
|||||||
sql = """select a.providerid, a.iconid, b.orgname
|
sql = """select a.providerid, a.iconid, b.orgname
|
||||||
from llm a, organization b
|
from llm a, organization b
|
||||||
where a.providerid = b.id
|
where a.providerid = b.id
|
||||||
|
and a.status = 'published'
|
||||||
group by a.providerid, a.iconid, b.orgname"""
|
group by a.providerid, a.iconid, b.orgname"""
|
||||||
return await sor.sqlExe(sql, {})
|
return await sor.sqlExe(sql, {})
|
||||||
return []
|
return []
|
||||||
@ -165,14 +214,36 @@ async def get_llms_sort_by_provider():
|
|||||||
sql = """select a.*, b.orgname from llm a, organization b
|
sql = """select a.*, b.orgname from llm a, organization b
|
||||||
where a.enabled_date <= ${today}$
|
where a.enabled_date <= ${today}$
|
||||||
and a.expired_date > ${today}$
|
and a.expired_date > ${today}$
|
||||||
|
and a.status = 'published'
|
||||||
and a.providerid = b.id
|
and a.providerid = b.id
|
||||||
order by a.providerid, a.id
|
order by a.providerid, a.id
|
||||||
"""
|
"""
|
||||||
recs = await sor.sqlExe(sql, {'today': today})
|
recs = await sor.sqlExe(sql, {'today': today})
|
||||||
|
# 批量查询所有模型的 ppid 映射
|
||||||
|
llm_ids = [r.id for r in recs]
|
||||||
|
pp_map = {} # llmid -> [ppid, ...]
|
||||||
|
if llm_ids:
|
||||||
|
placeholders = ','.join([f"'{lid}'" for lid in llm_ids])
|
||||||
|
pp_sql = f"select distinct llmid, ppid from llm_api_map where llmid in ({placeholders}) and ppid is not null"
|
||||||
|
pp_recs = await sor.sqlExe(pp_sql, {})
|
||||||
|
for pp in pp_recs:
|
||||||
|
pp_map.setdefault(pp.llmid, []).append(pp.ppid)
|
||||||
|
|
||||||
d = []
|
d = []
|
||||||
x = None
|
x = None
|
||||||
oldpid = '-111'
|
oldpid = '-111'
|
||||||
for l in recs:
|
for l in recs:
|
||||||
|
# 获取定价展示文本
|
||||||
|
pricing_list = []
|
||||||
|
for ppid in pp_map.get(l.id, []):
|
||||||
|
try:
|
||||||
|
pd = await env.get_pricing_display(ppid)
|
||||||
|
if pd:
|
||||||
|
pricing_list.append(pd.get('display_text', ''))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
l.pricing_display = pricing_list
|
||||||
|
|
||||||
if l.providerid != oldpid:
|
if l.providerid != oldpid:
|
||||||
x = {
|
x = {
|
||||||
'id': l.providerid,
|
'id': l.providerid,
|
||||||
@ -186,6 +257,44 @@ where a.enabled_date <= ${today}$
|
|||||||
return d
|
return d
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def get_llmage_llm(llmid=None, catelogid=None):
|
||||||
|
"""Unified accessor for llm + llm_api_map + llmcatelog.
|
||||||
|
For non-API-call scenarios only (display, listing, querying, accounting).
|
||||||
|
Do NOT use for vendor model API calls — use get_llm() instead.
|
||||||
|
|
||||||
|
- llmid: get specific llm by id (returns single DictObject or None)
|
||||||
|
- catelogid: filter by catalog (returns list)
|
||||||
|
- neither: return all with catalog info (returns list)
|
||||||
|
"""
|
||||||
|
env = ServerEnv()
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
sql = """select a.id, a.name, a.model, a.providerid, a.description,
|
||||||
|
a.iconid, a.upappid, a.ownerid, a.min_balance, a.status,
|
||||||
|
m.llmcatelogid, m.apiname, m.query_apiname, m.query_period, m.ppid, m.isdefaultcatelog,
|
||||||
|
lc.name as catelogname
|
||||||
|
from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join llmcatelog lc on m.llmcatelogid = lc.id
|
||||||
|
where 1=1
|
||||||
|
"""
|
||||||
|
ns = {}
|
||||||
|
if llmid:
|
||||||
|
sql += " and a.id = ${llmid}$"
|
||||||
|
ns['llmid'] = llmid
|
||||||
|
if catelogid:
|
||||||
|
sql += " and m.llmcatelogid = ${catelogid}$"
|
||||||
|
ns['catelogid'] = catelogid
|
||||||
|
else:
|
||||||
|
sql += " and m.isdefaultcatelog = '1'"
|
||||||
|
elif catelogid:
|
||||||
|
sql += " and m.llmcatelogid = ${catelogid}$"
|
||||||
|
ns['catelogid'] = catelogid
|
||||||
|
sql += " order by m.llmcatelogid, a.id"
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
if llmid:
|
||||||
|
return recs[0] if recs else None
|
||||||
|
return recs
|
||||||
|
|
||||||
async def get_llmcatelogs():
|
async def get_llmcatelogs():
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
dbname = get_serverenv('get_module_dbname')('llmage')
|
dbname = get_serverenv('get_module_dbname')('llmage')
|
||||||
@ -211,6 +320,7 @@ m.ppid
|
|||||||
join llm_api_map m on a.id = m.llmid
|
join llm_api_map m on a.id = m.llmid
|
||||||
join llmcatelog b on m.llmcatelogid = b.id
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
where a.enabled_date <= ${today}$
|
where a.enabled_date <= ${today}$
|
||||||
|
and a.status = 'published'
|
||||||
and m.ppid is not null
|
and m.ppid is not null
|
||||||
and a.expired_date > ${today}$
|
and a.expired_date > ${today}$
|
||||||
"""
|
"""
|
||||||
@ -250,6 +360,7 @@ async def get_llms_by_catelog(catelogid=None, orderby='providerid'):
|
|||||||
join llm_api_map m on a.id = m.llmid
|
join llm_api_map m on a.id = m.llmid
|
||||||
join llmcatelog b on m.llmcatelogid = b.id
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
where a.enabled_date <= ${today}$
|
where a.enabled_date <= ${today}$
|
||||||
|
and a.status = 'published'
|
||||||
and a.expired_date > ${today}$"""
|
and a.expired_date > ${today}$"""
|
||||||
params = {'today': today, 'sort': orderby}
|
params = {'today': today, 'sort': orderby}
|
||||||
if catelogid:
|
if catelogid:
|
||||||
@ -259,10 +370,30 @@ async def get_llms_by_catelog(catelogid=None, orderby='providerid'):
|
|||||||
sql += " order by m.llmcatelogid, a.id"
|
sql += " order by m.llmcatelogid, a.id"
|
||||||
|
|
||||||
recs = await sor.sqlExe(sql, params)
|
recs = await sor.sqlExe(sql, params)
|
||||||
|
# 批量查询所有模型的 ppid 映射(避免 N+1 查询)
|
||||||
|
llm_ids = [r.id for r in recs]
|
||||||
|
pp_map = {}
|
||||||
|
if llm_ids:
|
||||||
|
placeholders = ','.join([f"'{lid}'" for lid in llm_ids])
|
||||||
|
pp_sql = f"select distinct llmid, ppid from llm_api_map where llmid in ({placeholders}) and ppid is not null"
|
||||||
|
pp_recs = await sor.sqlExe(pp_sql, {})
|
||||||
|
for pp in pp_recs:
|
||||||
|
pp_map.setdefault(pp.llmid, []).append(pp.ppid)
|
||||||
|
|
||||||
d = []
|
d = []
|
||||||
cid = ''
|
cid = ''
|
||||||
x = None
|
x = None
|
||||||
for r in recs:
|
for r in recs:
|
||||||
|
pricing_list = []
|
||||||
|
for ppid in pp_map.get(r.id, []):
|
||||||
|
try:
|
||||||
|
pd = await env.get_pricing_display(ppid)
|
||||||
|
if pd:
|
||||||
|
pricing_list.append(pd.get('display_text', ''))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
r.pricing_display = pricing_list
|
||||||
|
|
||||||
if cid != r.catelog_id:
|
if cid != r.catelog_id:
|
||||||
x = {
|
x = {
|
||||||
'catelogid': r.catelog_id,
|
'catelogid': r.catelog_id,
|
||||||
@ -276,59 +407,58 @@ async def get_llms_by_catelog(catelogid=None, orderby='providerid'):
|
|||||||
return d
|
return d
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def get_llm_catelogs(llmid):
|
||||||
|
"""Get all catelog entries for a given llmid from llm_api_map + llmcatelog.
|
||||||
|
Returns list of {catelogid, catelogname, isdefaultcatelog}
|
||||||
|
"""
|
||||||
|
if not llmid:
|
||||||
|
return []
|
||||||
|
llmage_dbname = get_serverenv('get_module_dbname')('llmage')
|
||||||
|
async with DBPools().sqlorContext(llmage_dbname) as sor:
|
||||||
|
sql = """select m.llmcatelogid as catelogid, lc.name as catelogname, m.isdefaultcatelog
|
||||||
|
from llm_api_map m
|
||||||
|
join llmcatelog lc on m.llmcatelogid = lc.id
|
||||||
|
where m.llmid = ${llmid}$
|
||||||
|
order by m.isdefaultcatelog desc"""
|
||||||
|
recs = await sor.sqlExe(sql, {'llmid': llmid})
|
||||||
|
return [dict(catelogid=r.catelogid, catelogname=r.catelogname, isdefault=r.isdefaultcatelog == '1') for r in recs]
|
||||||
|
|
||||||
|
|
||||||
async def get_llm(llmid, catelogid=None):
|
async def get_llm(llmid, catelogid=None):
|
||||||
today = curDateString()
|
"""Get LLM with full uapi info for vendor API calls.
|
||||||
env = ServerEnv()
|
Refactored to use get_llmage_llm() + cached uapi/uapiio lookups
|
||||||
async with get_sor_context(env, 'llmage') as sor:
|
instead of a 6-table JOIN.
|
||||||
sql = """select a.id,
|
|
||||||
a.name,
|
Returns DictObject with merged fields:
|
||||||
a.model,
|
From get_llmage_llm: id, name, model, providerid, description,
|
||||||
a.providerid,
|
iconid, upappid, ownerid, min_balance, status, llmcatelogid,
|
||||||
a.description,
|
apiname, query_apiname, query_period, ppid, isdefaultcatelog,
|
||||||
a.iconid,
|
catelogname
|
||||||
a.upappid,
|
From uapi (cached): ioid, stream, callbackurl
|
||||||
a.ownerid,
|
From uapiio (cached): input_fields
|
||||||
a.min_balance,
|
"""
|
||||||
m.llmcatelogid,
|
# Step 1: Get base info from get_llmage_llm (3-table JOIN: llm + llm_api_map + llmcatelog)
|
||||||
m.apiname,
|
llm = await get_llmage_llm(llmid, catelogid)
|
||||||
m.query_apiname,
|
if not llm:
|
||||||
m.query_period,
|
debug(f'{llmid=} not found via get_llmage_llm')
|
||||||
m.ppid,
|
return None
|
||||||
e.ioid,
|
|
||||||
e.stream,
|
# Step 2: Get uapi info (cached, keyed by upappid:apiname)
|
||||||
e.callbackurl,
|
uapi = await _get_uapi_cached(llm.upappid, llm.apiname)
|
||||||
f.input_fields,
|
if not uapi:
|
||||||
lc.name as catelogname
|
debug(f'uapi not found: upappid={llm.upappid}, apiname={llm.apiname}')
|
||||||
from llm a
|
return None
|
||||||
,llm_api_map m
|
|
||||||
,llmcatelog lc
|
# Step 3: Get uapiio info (cached, keyed by ioid)
|
||||||
,upapp c
|
uapiio = await _get_uapiio_cached(uapi.ioid)
|
||||||
,uapi e
|
|
||||||
,uapiio f
|
# Merge uapi fields into llm result
|
||||||
where a.id = m.llmid
|
llm.ioid = uapi.ioid
|
||||||
and a.upappid = c.id
|
llm.stream = uapi.stream
|
||||||
and c.id = e.upappid
|
llm.callbackurl = uapi.callbackurl
|
||||||
and m.apiname = e.name
|
llm.input_fields = uapiio.input_fields if uapiio else '{}'
|
||||||
and e.ioid = f.id
|
|
||||||
and a.id = ${llmid}$
|
return llm
|
||||||
and a.expired_date > ${today}$
|
|
||||||
and a.enabled_date <= ${today}$
|
|
||||||
"""
|
|
||||||
ns = {'llmid': llmid, 'today': today}
|
|
||||||
if catelogid:
|
|
||||||
sql += ' and m.llmcatelogid = ${catelogid}$ '
|
|
||||||
ns['catelogid'] = catelogid
|
|
||||||
else:
|
|
||||||
sql += " and m.isdefaultcatelog = '1'"
|
|
||||||
recs = await sor.sqlExe(sql, ns.copy())
|
|
||||||
if len(recs) > 0:
|
|
||||||
r = recs[0]
|
|
||||||
return r
|
|
||||||
else:
|
|
||||||
debug(f'{llmid=} not found, {ns=}, {sql=}')
|
|
||||||
return None
|
|
||||||
exception(f'Error: {format_exc()}')
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
async def write_llmusage(llmusage):
|
async def write_llmusage(llmusage):
|
||||||
@ -338,7 +468,7 @@ async def write_llmusage(llmusage):
|
|||||||
|
|
||||||
async def llm_query_price(llmid, config_data):
|
async def llm_query_price(llmid, config_data):
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
llm = await get_llm(llmid)
|
llm = await get_llmage_llm(llmid)
|
||||||
if llm.ppid is None:
|
if llm.ppid is None:
|
||||||
e = Exception(f'{llm=} ppid is None')
|
e = Exception(f'{llm=} ppid is None')
|
||||||
exception(f'{e}')
|
exception(f'{e}')
|
||||||
|
|||||||
@ -72,7 +72,16 @@
|
|||||||
"title": "最低余额",
|
"title": "最低余额",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"length": 20,
|
"length": 20,
|
||||||
"default": 10
|
"default": 10,
|
||||||
|
"dec": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"title": "上架状态",
|
||||||
|
"type": "str",
|
||||||
|
"length": 16,
|
||||||
|
"nullable": "no",
|
||||||
|
"default": "unpublished"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"codes": [
|
"codes": [
|
||||||
@ -99,6 +108,13 @@
|
|||||||
"table": "organization",
|
"table": "organization",
|
||||||
"valuefield": "id",
|
"valuefield": "id",
|
||||||
"textfield": "orgname"
|
"textfield": "orgname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "status",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"valuefield": "k",
|
||||||
|
"textfield": "v",
|
||||||
|
"cond": "parentid='llm_status'"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
BIN
models/llm.xlsx
BIN
models/llm.xlsx
Binary file not shown.
@ -4,81 +4,99 @@
|
|||||||
{
|
{
|
||||||
"name": "llm_api_map",
|
"name": "llm_api_map",
|
||||||
"title": "模型API映射表",
|
"title": "模型API映射表",
|
||||||
"primary": "id",
|
"primary": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
"catelog": "relation"
|
"catelog": "relation"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "varchar(32)",
|
"type": "str",
|
||||||
"not_null": true,
|
"not_null": true,
|
||||||
"title": "主键ID"
|
"title": "主键ID",
|
||||||
|
"length": 32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "llmid",
|
"name": "llmid",
|
||||||
"type": "varchar(32)",
|
"type": "str",
|
||||||
"not_null": true,
|
"not_null": true,
|
||||||
"title": "模型ID"
|
"title": "模型ID",
|
||||||
|
"length": 32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "llmcatelogid",
|
"name": "llmcatelogid",
|
||||||
"type": "varchar(32)",
|
"type": "str",
|
||||||
"not_null": true,
|
"not_null": true,
|
||||||
"title": "模型分类ID"
|
"title": "模型分类ID",
|
||||||
|
"length": 32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "apiname",
|
"name": "apiname",
|
||||||
"type": "varchar(100)",
|
"type": "str",
|
||||||
"not_null": true,
|
"not_null": true,
|
||||||
"title": "接口名称"
|
"title": "接口名称",
|
||||||
|
"length": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "query_apiname",
|
"name": "query_apiname",
|
||||||
"type": "varchar(100)",
|
"type": "str",
|
||||||
"title": "任务结果查询接口名称"
|
"title": "任务结果查询接口名称",
|
||||||
|
"length": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "query_period",
|
"name": "query_period",
|
||||||
"type": "bigint",
|
"type": "long",
|
||||||
"default": 30,
|
"default": 30,
|
||||||
"title": "任务查询间隔(秒)"
|
"title": "任务查询间隔(秒)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ppid",
|
"name": "ppid",
|
||||||
"type": "varchar(32)",
|
"type": "str",
|
||||||
"title": "定价ID"
|
"title": "定价ID",
|
||||||
|
"length": 32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "isdefaultcatelog",
|
"name": "isdefaultcatelog",
|
||||||
"type": "varchar(1)",
|
"type": "str",
|
||||||
"not_null": true,
|
"not_null": true,
|
||||||
"title": "缺省分类"
|
"title": "缺省分类",
|
||||||
|
"length": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
{
|
||||||
"name": "idx_api_map_llmid",
|
"name": "idx_api_map_llmid",
|
||||||
"type": "normal",
|
"type": "normal",
|
||||||
"idxfields": ["llmid"],
|
"idxfields": [
|
||||||
|
"llmid"
|
||||||
|
],
|
||||||
"idxtype": "index"
|
"idxtype": "index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "idx_api_map_catelog",
|
"name": "idx_api_map_catelog",
|
||||||
"type": "normal",
|
"type": "normal",
|
||||||
"idxfields": ["llmcatelogid"],
|
"idxfields": [
|
||||||
|
"llmcatelogid"
|
||||||
|
],
|
||||||
"idxtype": "index"
|
"idxtype": "index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "idx_api_map_apiname",
|
"name": "idx_api_map_apiname",
|
||||||
"type": "normal",
|
"type": "normal",
|
||||||
"idxfields": ["apiname"],
|
"idxfields": [
|
||||||
|
"apiname"
|
||||||
|
],
|
||||||
"idxtype": "index"
|
"idxtype": "index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "uk_llmid_apiname",
|
"name": "uk_llmid_apiname",
|
||||||
"type": "unique",
|
"type": "unique",
|
||||||
"idxfields": ["llmid", "apiname"],
|
"idxfields": [
|
||||||
|
"llmid",
|
||||||
|
"apiname"
|
||||||
|
],
|
||||||
"idxtype": "unique"
|
"idxtype": "unique"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -100,6 +118,13 @@
|
|||||||
"table": "pricing_program",
|
"table": "pricing_program",
|
||||||
"valuefield": "id",
|
"valuefield": "id",
|
||||||
"textfield": "name"
|
"textfield": "name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "isdefaultcatelog",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"valuefield": "k",
|
||||||
|
"textfield": "v",
|
||||||
|
"cond": "parentid='isdefaultcatelog_flg'"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -58,13 +58,15 @@
|
|||||||
"name": "responsed_seconds",
|
"name": "responsed_seconds",
|
||||||
"title": "响应时间",
|
"title": "响应时间",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"length": 18
|
"length": 18,
|
||||||
|
"dec": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "finish_seconds",
|
"name": "finish_seconds",
|
||||||
"title": "结束时间",
|
"title": "结束时间",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"length": 18
|
"length": 18,
|
||||||
|
"dec": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status",
|
"name": "status",
|
||||||
@ -82,13 +84,15 @@
|
|||||||
"name": "amount",
|
"name": "amount",
|
||||||
"title": "交易金额",
|
"title": "交易金额",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"length": 18
|
"length": 18,
|
||||||
|
"dec": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "cost",
|
"name": "cost",
|
||||||
"title": "交易成本",
|
"title": "交易成本",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"length": 18
|
"length": 18,
|
||||||
|
"dec": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "userorgid",
|
"name": "userorgid",
|
||||||
@ -131,6 +135,30 @@
|
|||||||
"accounting_status",
|
"accounting_status",
|
||||||
"use_date"
|
"use_date"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "idx_llmusage_userid_usetime",
|
||||||
|
"idxtype": "index",
|
||||||
|
"idxfields": [
|
||||||
|
"userid",
|
||||||
|
"use_time"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": [
|
||||||
|
{
|
||||||
|
"field": "status",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"valuefield": "k",
|
||||||
|
"textfield": "v",
|
||||||
|
"cond": "parentid='llmusage_status'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "accounting_status",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"valuefield": "k",
|
||||||
|
"textfield": "v",
|
||||||
|
"cond": "parentid='accounting_status'"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
@ -121,5 +121,32 @@
|
|||||||
"idxtype": "index",
|
"idxtype": "index",
|
||||||
"idxfields": ["failed_time"]
|
"idxfields": ["failed_time"]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"codes": [
|
||||||
|
{
|
||||||
|
"field": "handled",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"valuefield": "k",
|
||||||
|
"textfield": "v",
|
||||||
|
"cond": "parentid='handled_flg'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "userid",
|
||||||
|
"table": "users",
|
||||||
|
"valuefield": "userid",
|
||||||
|
"textfield": "username"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "userorgid",
|
||||||
|
"table": "organization",
|
||||||
|
"valuefield": "id",
|
||||||
|
"textfield": "orgname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "llmid",
|
||||||
|
"table": "llm",
|
||||||
|
"valuefield": "id",
|
||||||
|
"textfield": "name"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,6 +136,27 @@
|
|||||||
"name": "idx_lh_backup_time",
|
"name": "idx_lh_backup_time",
|
||||||
"idxtype": "index",
|
"idxtype": "index",
|
||||||
"idxfields": ["backup_time"]
|
"idxfields": ["backup_time"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "idx_lh_userid_usetime",
|
||||||
|
"idxtype": "index",
|
||||||
|
"idxfields": ["userid", "use_time"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"codes": [
|
||||||
|
{
|
||||||
|
"field": "status",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"valuefield": "k",
|
||||||
|
"textfield": "v",
|
||||||
|
"cond": "parentid='llmusage_status'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "accounting_status",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"valuefield": "k",
|
||||||
|
"textfield": "v",
|
||||||
|
"cond": "parentid='accounting_status'"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
83
scripts/fix_digital_human_pricing.sql
Normal file
83
scripts/fix_digital_human_pricing.sql
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 修复数字人模型显示错误 — pricing timing缺失
|
||||||
|
-- 问题: get_ppid_pricing 找不到有效timing记录导致"data not found"
|
||||||
|
-- 影响:
|
||||||
|
-- 1. orNSwYIFP0HFv2UnY-9EW (wan2.6-i2v-flash) — 有program无timing
|
||||||
|
-- 2. 0B6aldoAej1PpZ4ydtrEZ (wan2.2-s2v数字人) — program和timing都缺
|
||||||
|
-- 生成时间: 2026-06-13
|
||||||
|
-- 执行用户: sword (bugfix模块) 或 root (mysql直接执行)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. 修复 wan2.6-i2v-flash: 设置discount + 创建timing记录
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 1a. 修复 discount (当前为null)
|
||||||
|
UPDATE pricing_program
|
||||||
|
SET discount = 1.0
|
||||||
|
WHERE id = 'orNSwYIFP0HFv2UnY-9EW';
|
||||||
|
|
||||||
|
-- 1b. 创建 pricing_program_timing 记录
|
||||||
|
-- 官方定价: 有声720P=0.3元/秒, 1080P=0.5元/秒; 无声720P=0.15元/秒, 1080P=0.25元/秒
|
||||||
|
INSERT INTO pricing_program_timing (id, ppid, name, enabled_date, expired_date, pricing_data)
|
||||||
|
VALUES (
|
||||||
|
'orNSwYIFP0HFv2UnY-t1',
|
||||||
|
'orNSwYIFP0HFv2UnY-9EW',
|
||||||
|
NULL,
|
||||||
|
'2026-06-13',
|
||||||
|
'9999-12-31',
|
||||||
|
'unit_values:\n 秒: 1\nfields:\n price_factors:\n type: string\n role: factor\n label: 计价因子\n unit_prices:\n type: float\n role: factor\n label: 单位定价\n unit:\n type: string\n role: factor\n label: 计价单位\n size:\n type: string\n role: filter\n label: 分辨率\n audio:\n type: string\n role: filter\n label: 音频\npricings:\n- price_factors: duration\n unit_prices: 0.3\n unit: 秒\n filters:\n - size: 720P\n - audio: true\n- price_factors: duration\n unit_prices: 0.5\n unit: 秒\n filters:\n - size: 1080P\n - audio: true\n- price_factors: duration\n unit_prices: 0.15\n unit: 秒\n filters:\n - size: 720P\n - audio: false\n- price_factors: duration\n unit_prices: 0.25\n unit: 秒\n filters:\n - size: 1080P\n - audio: false'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. 创建 wan2.2-s2v 数字人定价 (program + timing)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 2a. 创建 pricing_program
|
||||||
|
INSERT INTO pricing_program (id, name, ownerid, providerid, pricing_belong, discount, description, pricing_spec)
|
||||||
|
VALUES (
|
||||||
|
'0B6aldoAej1PpZ4ydtrEZ',
|
||||||
|
'通义万象-数字人 wan2.2-s2v',
|
||||||
|
'0',
|
||||||
|
'6fadgewjraOyvxC_EkHou',
|
||||||
|
'provider',
|
||||||
|
1.0,
|
||||||
|
'万相数字人视频生成定价,按输出视频秒数计费',
|
||||||
|
'fields:\n model:\n type: str\n label: 模型\n options:\n - wan2.2-s2v\n size:\n type: str\n label: 分辨率\n options:\n - 480P\n - 720P\n duration:\n type: factor\n label: 时长(秒)'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 2b. 创建 pricing_program_timing
|
||||||
|
-- 官方定价: 480P=0.5元/秒, 720P=0.9元/秒
|
||||||
|
INSERT INTO pricing_program_timing (id, ppid, name, enabled_date, expired_date, pricing_data)
|
||||||
|
VALUES (
|
||||||
|
'0B6aldoAej1PpZ4ydtrE-t1',
|
||||||
|
'0B6aldoAej1PpZ4ydtrEZ',
|
||||||
|
NULL,
|
||||||
|
'2026-06-13',
|
||||||
|
'9999-12-31',
|
||||||
|
'unit_values:\n 秒: 1\nfields:\n price_factors:\n type: string\n role: factor\n label: 计价因子\n unit_prices:\n type: float\n role: factor\n label: 单位定价\n unit:\n type: string\n role: factor\n label: 计价单位\n size:\n type: string\n role: filter\n label: 分辨率\npricings:\n- price_factors: duration\n unit_prices: 0.5\n unit: 秒\n filters:\n - size: 480P\n- price_factors: duration\n unit_prices: 0.9\n unit: 秒\n filters:\n - size: 720P'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 验证 (执行后运行以下查询确认)
|
||||||
|
-- ============================================================
|
||||||
|
-- SELECT pp.id, pp.name, pp.discount, COUNT(ppt.id) as timing_count
|
||||||
|
-- FROM pricing_program pp
|
||||||
|
-- LEFT JOIN pricing_program_timing ppt ON pp.id = ppt.ppid
|
||||||
|
-- WHERE pp.id IN ('orNSwYIFP0HFv2UnY-9EW', '0B6aldoAej1PpZ4ydtrEZ')
|
||||||
|
-- GROUP BY pp.id, pp.name, pp.discount;
|
||||||
|
--
|
||||||
|
-- 预期结果:
|
||||||
|
-- | id | name | discount | timing_count |
|
||||||
|
-- |-------------------------|----------------------------|----------|--------------|
|
||||||
|
-- | orNSwYIFP0HFv2UnY-9EW | wan2.6-i2v-flash | 1.0 | 1 |
|
||||||
|
-- | 0B6aldoAej1PpZ4ydtrEZ | 通义万象-数字人 wan2.2-s2v | 1.0 | 1 |
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 回滚 (如需回滚)
|
||||||
|
-- ============================================================
|
||||||
|
-- DELETE FROM pricing_program_timing WHERE id IN ('orNSwYIFP0HFv2UnY-t1', '0B6aldoAej1PpZ4ydtrE-t1');
|
||||||
|
-- DELETE FROM pricing_program WHERE id = '0B6aldoAej1PpZ4ydtrEZ';
|
||||||
|
-- UPDATE pricing_program SET discount = NULL WHERE id = 'orNSwYIFP0HFv2UnY-9EW';
|
||||||
26
scripts/fix_m3_pricing.sql
Normal file
26
scripts/fix_m3_pricing.sql
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- 修复 MiniMax-M3 定价重复条目
|
||||||
|
-- 问题:步骤11b的CONCAT重复执行导致M3条目重复
|
||||||
|
-- 解决:删除没有prompt_tokens filter的旧M3条目(前3条)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
UPDATE `pricing_program_timing`
|
||||||
|
SET `pricing_data` = REPLACE(`pricing_data`,
|
||||||
|
'- price_factors: prompt_tokens
|
||||||
|
unit_prices: 2.1
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
- price_factors: completion_tokens
|
||||||
|
unit_prices: 8.4
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
- price_factors: cached_tokens
|
||||||
|
unit_prices: 0.42
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
|
||||||
|
', '')
|
||||||
|
WHERE `ppid` = '5jmzupARABxkDFwUraFiQ' AND `enabled_date` = '2026-04-12';
|
||||||
245
scripts/load_path.py
Normal file
245
scripts/load_path.py
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
llmage 模块 RBAC 权限管理脚本
|
||||||
|
|
||||||
|
使用方法:
|
||||||
|
cd ~/repos/sage
|
||||||
|
./py3/bin/python ~/repos/llmage/scripts/load_path.py
|
||||||
|
|
||||||
|
每次代码变更如有新 path 出现,需同步更新此脚本。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def find_sage_root():
|
||||||
|
candidates = [
|
||||||
|
os.path.expanduser("~/repos/sage"),
|
||||||
|
os.path.expanduser("~/sage"),
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
||||||
|
]
|
||||||
|
for c in candidates:
|
||||||
|
if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")):
|
||||||
|
return c
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
SAGE_ROOT = find_sage_root()
|
||||||
|
if not SAGE_ROOT:
|
||||||
|
print("ERROR: Cannot find Sage root directory")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
PYTHON = os.path.join(SAGE_ROOT, "py3", "bin", "python")
|
||||||
|
SET_PERM_SCRIPT = os.path.join(SAGE_ROOT, "set_role_perm.py")
|
||||||
|
|
||||||
|
MOD = "llmage"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 权限路径定义 — 每次新增页面或API时同步更新
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# any — 无需登录(菜单、静态资源)
|
||||||
|
PATHS_ANY = [
|
||||||
|
f"/{MOD}/menu.ui",
|
||||||
|
f"/{MOD}/imgs/kdb.svg",
|
||||||
|
]
|
||||||
|
|
||||||
|
# logined — 所有已登录用户
|
||||||
|
PATHS_LOGINED = [
|
||||||
|
# 模块入口
|
||||||
|
f"/{MOD}",
|
||||||
|
f"/{MOD}/index.ui",
|
||||||
|
|
||||||
|
# 顶层 .ui 页面
|
||||||
|
f"/{MOD}/api_doc.ui",
|
||||||
|
f"/{MOD}/api_doc.md",
|
||||||
|
f"/{MOD}/llm_dialog.ui",
|
||||||
|
f"/{MOD}/llm_launch_check.ui",
|
||||||
|
f"/{MOD}/check_model_record.dspy",
|
||||||
|
f"/{MOD}/check_date_status.dspy",
|
||||||
|
f"/{MOD}/check_upapp.dspy",
|
||||||
|
f"/{MOD}/check_uapi.dspy",
|
||||||
|
f"/{MOD}/check_uapiio.dspy",
|
||||||
|
f"/{MOD}/check_llm_api_map.dspy",
|
||||||
|
f"/{MOD}/check_pricing_program.dspy",
|
||||||
|
f"/{MOD}/check_pricing_data.dspy",
|
||||||
|
f"/{MOD}/show_same_catelog_llm.ui",
|
||||||
|
f"/{MOD}/show_llms.ui",
|
||||||
|
f"/{MOD}/show_llms_by_providers.ui",
|
||||||
|
f"/{MOD}/model_plaza.ui",
|
||||||
|
f"/{MOD}/failed_accounting.ui",
|
||||||
|
f"/{MOD}/llmcatelog_list.ui",
|
||||||
|
|
||||||
|
# 顶层 .dspy(非 api/ 目录)
|
||||||
|
f"/{MOD}/get_accounting_llmusages.dspy",
|
||||||
|
f"/{MOD}/get_asynctask_status.dspy",
|
||||||
|
f"/{MOD}/get_my_asynctasks.dspy",
|
||||||
|
f"/{MOD}/get_type_llms.dspy",
|
||||||
|
f"/{MOD}/grap_task_status.dspy",
|
||||||
|
f"/{MOD}/list_catelog_models.dspy",
|
||||||
|
f"/{MOD}/list_paging_catelog_llms.dspy",
|
||||||
|
f"/{MOD}/llmaccounting.dspy",
|
||||||
|
f"/{MOD}/llmcheck.dspy",
|
||||||
|
f"/{MOD}/llmcost.dspy",
|
||||||
|
f"/{MOD}/llminference.dspy",
|
||||||
|
f"/{MOD}/model_estimate.dspy",
|
||||||
|
f"/{MOD}/query_orders.dspy",
|
||||||
|
f"/{MOD}/query_price.dspy",
|
||||||
|
f"/{MOD}/test_llm_charging.dspy",
|
||||||
|
f"/{MOD}/vidu_callback.dspy",
|
||||||
|
f"/{MOD}/vidu_inference.dspy",
|
||||||
|
|
||||||
|
# api/ 目录
|
||||||
|
f"/{MOD}/api/failed_accounting_list.dspy",
|
||||||
|
f"/{MOD}/api/get_inference_history.dspy",
|
||||||
|
f"/{MOD}/api/get_apis.dspy",
|
||||||
|
f"/{MOD}/api/get_catelogs.dspy",
|
||||||
|
f"/{MOD}/api/get_organizations.dspy",
|
||||||
|
f"/{MOD}/api/get_ppids.dspy",
|
||||||
|
f"/{MOD}/api/get_search_apiname.dspy",
|
||||||
|
f"/{MOD}/api/get_search_providerid.dspy",
|
||||||
|
f"/{MOD}/api/get_search_upappid.dspy",
|
||||||
|
f"/{MOD}/api/get_upapps.dspy",
|
||||||
|
f"/{MOD}/api/llm_launch_check_api.dspy",
|
||||||
|
f"/{MOD}/api/llm_api_map_create.dspy",
|
||||||
|
f"/{MOD}/api/llm_api_map_delete.dspy",
|
||||||
|
f"/{MOD}/api/llm_api_map_list.dspy",
|
||||||
|
f"/{MOD}/api/llm_api_map_options.dspy",
|
||||||
|
f"/{MOD}/api/llm_catelog_options.dspy",
|
||||||
|
f"/{MOD}/api/llm_create.dspy",
|
||||||
|
f"/{MOD}/api/llm_delete.dspy",
|
||||||
|
f"/{MOD}/api/llm_status_update.dspy",
|
||||||
|
f"/{MOD}/api/llm_update.dspy",
|
||||||
|
f"/{MOD}/api/llmcatelog_create.dspy",
|
||||||
|
f"/{MOD}/api/llmcatelog_delete.dspy",
|
||||||
|
f"/{MOD}/api/llmcatelog_list.dspy",
|
||||||
|
f"/{MOD}/api/llmcatelog_update.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_accounting_failed_create.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_accounting_failed_delete.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_accounting_failed_update.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_create.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_delete.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_history_create.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_history_delete.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_history_update.dspy",
|
||||||
|
f"/{MOD}/api/llmusage_update.dspy",
|
||||||
|
f"/{MOD}/api/retry_accounting.dspy",
|
||||||
|
f"/{MOD}/api/uapi_options.dspy",
|
||||||
|
|
||||||
|
# CRUD 子目录 — llm/
|
||||||
|
f"/{MOD}/llm/index.ui",
|
||||||
|
f"/{MOD}/llm/add_llm.dspy",
|
||||||
|
f"/{MOD}/llm/delete_llm.dspy",
|
||||||
|
f"/{MOD}/llm/get_llm.dspy",
|
||||||
|
f"/{MOD}/llm/update_llm.dspy",
|
||||||
|
|
||||||
|
# CRUD 子目录 — llm_api_map/
|
||||||
|
f"/{MOD}/llm_api_map/index.ui",
|
||||||
|
f"/{MOD}/llm_api_map/add_llm_api_map.dspy",
|
||||||
|
f"/{MOD}/llm_api_map/delete_llm_api_map.dspy",
|
||||||
|
f"/{MOD}/llm_api_map/get_llm_api_map.dspy",
|
||||||
|
f"/{MOD}/llm_api_map/update_llm_api_map.dspy",
|
||||||
|
|
||||||
|
# CRUD 子目录 — llmcatelog_list/ (alias for llmcatelog)
|
||||||
|
f"/{MOD}/llmcatelog_list/index.ui",
|
||||||
|
f"/{MOD}/llmcatelog_list/add_llmcatelog.dspy",
|
||||||
|
f"/{MOD}/llmcatelog_list/delete_llmcatelog.dspy",
|
||||||
|
f"/{MOD}/llmcatelog_list/get_llmcatelog.dspy",
|
||||||
|
f"/{MOD}/llmcatelog_list/update_llmcatelog.dspy",
|
||||||
|
|
||||||
|
# CRUD 子目录 — llmusage/
|
||||||
|
f"/{MOD}/llmusage/index.ui",
|
||||||
|
f"/{MOD}/llmusage/add_llmusage.dspy",
|
||||||
|
f"/{MOD}/llmusage/delete_llmusage.dspy",
|
||||||
|
f"/{MOD}/llmusage/get_llmusage.dspy",
|
||||||
|
f"/{MOD}/llmusage/update_llmusage.dspy",
|
||||||
|
|
||||||
|
# CRUD 子目录 — llmusage_accounting_failed/
|
||||||
|
f"/{MOD}/llmusage_accounting_failed/index.ui",
|
||||||
|
f"/{MOD}/llmusage_accounting_failed/add_llmusage_accounting_failed.dspy",
|
||||||
|
f"/{MOD}/llmusage_accounting_failed/delete_llmusage_accounting_failed.dspy",
|
||||||
|
f"/{MOD}/llmusage_accounting_failed/get_llmusage_accounting_failed.dspy",
|
||||||
|
f"/{MOD}/llmusage_accounting_failed/recover_usages.dspy",
|
||||||
|
f"/{MOD}/llmusage_accounting_failed/update_llmusage_accounting_failed.dspy",
|
||||||
|
|
||||||
|
# CRUD 子目录 — llmusage_history/
|
||||||
|
f"/{MOD}/llmusage_history/index.ui",
|
||||||
|
f"/{MOD}/llmusage_history/add_llmusage_history.dspy",
|
||||||
|
f"/{MOD}/llmusage_history/delete_llmusage_history.dspy",
|
||||||
|
f"/{MOD}/llmusage_history/get_llmusage_history.dspy",
|
||||||
|
f"/{MOD}/llmusage_history/update_llmusage_history.dspy",
|
||||||
|
|
||||||
|
# v1 API 目录
|
||||||
|
f"/{MOD}/v1/chat/completions/index.dspy",
|
||||||
|
f"/{MOD}/v1/image/generations/index.dspy",
|
||||||
|
f"/{MOD}/v1/models/catelog.dspy",
|
||||||
|
f"/{MOD}/v1/models/index.dspy",
|
||||||
|
f"/{MOD}/v1/tasks/index.dspy",
|
||||||
|
f"/{MOD}/v1/video/generations/index.dspy",
|
||||||
|
f"/{MOD}/v1/music/generations/index.dspy",
|
||||||
|
f"/{MOD}/v1/audio/speech/index.dspy",
|
||||||
|
f"/{MOD}/v1/audio/transcriptions/index.dspy",
|
||||||
|
f"/{MOD}/v1/pricing/index.dspy",
|
||||||
|
|
||||||
|
# 其他子目录
|
||||||
|
f"/{MOD}/list_llmcatelogs/index.dspy",
|
||||||
|
f"/{MOD}/list_llms/index.dspy",
|
||||||
|
f"/{MOD}/openai/index.dspy",
|
||||||
|
f"/{MOD}/t2t/index.dspy",
|
||||||
|
f"/{MOD}/tasks/index.dspy",
|
||||||
|
f"/{MOD}/upload_asset/index.dspy",
|
||||||
|
f"/{MOD}/video/index.dspy",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 客户角色 — v1 API 调用权限
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
PATHS_V1_CUSTOMER = [
|
||||||
|
f"/{MOD}/v1/chat/completions/index.dspy",
|
||||||
|
f"/{MOD}/v1/video/generations/index.dspy",
|
||||||
|
f"/{MOD}/v1/image/generations/index.dspy",
|
||||||
|
f"/{MOD}/v1/music/generations/index.dspy",
|
||||||
|
f"/{MOD}/v1/audio/speech/index.dspy",
|
||||||
|
f"/{MOD}/v1/audio/transcriptions/index.dspy",
|
||||||
|
f"/{MOD}/v1/pricing/index.dspy",
|
||||||
|
f"/{MOD}/v1/models/index.dspy",
|
||||||
|
f"/{MOD}/v1/tasks/index.dspy",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 执行注册
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
def run_set_perm(role, path):
|
||||||
|
cmd = [PYTHON, SET_PERM_SCRIPT, role, path]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def register_role_paths(role, paths):
|
||||||
|
count = 0
|
||||||
|
for p in paths:
|
||||||
|
if run_set_perm(role, p):
|
||||||
|
count += 1
|
||||||
|
print(f" {role}: {count}/{len(paths)} paths registered")
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(f"Sage root: {SAGE_ROOT}")
|
||||||
|
total = 0
|
||||||
|
total += register_role_paths("any", PATHS_ANY)
|
||||||
|
total += register_role_paths("logined", PATHS_LOGINED)
|
||||||
|
# 客户角色 — v1 API 调用权限
|
||||||
|
for role in ["customer.admin", "customer.user"]:
|
||||||
|
total += register_role_paths(role, PATHS_V1_CUSTOMER)
|
||||||
|
print(f"\nDone. Total {total} permission entries registered.")
|
||||||
|
print("NOTE: Restart Sage after permission changes to reload RBAC cache.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
245
scripts/migrate_llmcatelog_ids.py
Normal file
245
scripts/migrate_llmcatelog_ids.py
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
llmcatelog ID 迁移脚本
|
||||||
|
将 llmcatelog.id 和 llm_api_map.llmcatelogid 从旧ID迁移为有意义的缩写ID。
|
||||||
|
|
||||||
|
执行顺序:
|
||||||
|
1. 先更新 llm_api_map.llmcatelogid(外键表)
|
||||||
|
2. 再更新 llmcatelog.id(主表)
|
||||||
|
3. 验证迁移结果
|
||||||
|
|
||||||
|
用法:
|
||||||
|
# 预览模式(不执行,只显示将要做的变更)
|
||||||
|
python migrate_llmcatelog_ids.py --dry-run
|
||||||
|
|
||||||
|
# 正式执行
|
||||||
|
python migrate_llmcatelog_ids.py
|
||||||
|
|
||||||
|
# 指定数据库名(默认 llmage)
|
||||||
|
python migrate_llmcatelog_ids.py --dbname my_llmage
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 从脚本位置推断 sage 根目录(脚本在 pkgs/llmage/scripts/ 下)
|
||||||
|
_script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sage_root = os.path.abspath(os.path.join(_script_dir, '..', '..', '..'))
|
||||||
|
sys.path.insert(0, sage_root)
|
||||||
|
sys.path.insert(0, os.path.join(sage_root, 'py3/lib/python3.10/site-packages'))
|
||||||
|
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
|
||||||
|
# 旧ID -> 新ID 映射表
|
||||||
|
ID_MAP = {
|
||||||
|
'text2text': 't2t',
|
||||||
|
'text2image': 't2i',
|
||||||
|
'-i2ET0YkhfVQdHONfk9pX': 't2v',
|
||||||
|
'RdsO6pXgXcUTvUj819-7X': 'i2v',
|
||||||
|
'fHrfsOnAFCz53DAILMO7G': 'r2v',
|
||||||
|
'text2speech': 'tts',
|
||||||
|
'audio2text': 'asr',
|
||||||
|
'image2text': 'vision',
|
||||||
|
'9_P5y-qiQzQASacTVk2Lq': 'ai_search',
|
||||||
|
'czKvk-clQTRLS2KVddSWo': 'digital_human',
|
||||||
|
'HaRXiNCaAACurZsmEqpsU': 'music_gen',
|
||||||
|
'Rqj-QBj1v4560l-FPCrIU': 'text_cls',
|
||||||
|
's6-nhQtEvDKxG_qDPWwT7': '3d_gen',
|
||||||
|
'sRmpG8draTM-tsbO5nMJO': 'video_tool',
|
||||||
|
't7sUuj8BCnsD762PwMUKM': 'translate',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def migrate(dry_run=False, dbname='llmage'):
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from appPublic.log import debug
|
||||||
|
|
||||||
|
config = getConfig(sage_root)
|
||||||
|
db = DBPools(config.databases)
|
||||||
|
|
||||||
|
# 如果传入的 dbname 不在配置中,尝试使用第一个数据库
|
||||||
|
if dbname not in config.databases:
|
||||||
|
available = list(config.databases.keys())
|
||||||
|
print(f"Warning: '{dbname}' not in config.databases, available: {available}")
|
||||||
|
if available:
|
||||||
|
dbname = available[0]
|
||||||
|
print(f"Using '{dbname}' instead")
|
||||||
|
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
print(f"{'='*60}")
|
||||||
|
print(f"llmcatelog ID 迁移脚本")
|
||||||
|
print(f"数据库: {dbname}")
|
||||||
|
print(f"模式: {'预览(DRY-RUN)' if dry_run else '正式执行'}")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
|
# ===== 阶段0: 检查当前数据 =====
|
||||||
|
print("[阶段0] 检查当前 llmcatelog 数据...")
|
||||||
|
current = await sor.sqlExe("SELECT id, name FROM llmcatelog ORDER BY name", {})
|
||||||
|
if not current:
|
||||||
|
print(" llmcatelog 表为空,无需迁移。")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f" 当前共 {len(current)} 条记录:\n")
|
||||||
|
print(f" {'旧ID':<30} {'name':<15} {'新ID':<15} {'状态'}")
|
||||||
|
print(f" {'-'*30} {'-'*15} {'-'*15} {'-'*10}")
|
||||||
|
|
||||||
|
valid_records = []
|
||||||
|
unmapped = []
|
||||||
|
for row in current:
|
||||||
|
old_id = row['id']
|
||||||
|
name = row['name']
|
||||||
|
new_id = ID_MAP.get(old_id)
|
||||||
|
if new_id:
|
||||||
|
# 检查是否已经迁移过(old_id == new_id 的情况不会发生,
|
||||||
|
# 但如果 id 已经是新值则跳过)
|
||||||
|
if old_id == new_id:
|
||||||
|
status = '已迁移'
|
||||||
|
else:
|
||||||
|
status = '待迁移'
|
||||||
|
valid_records.append((old_id, new_id, name))
|
||||||
|
print(f" {old_id:<30} {name:<15} {new_id:<15} {status}")
|
||||||
|
else:
|
||||||
|
status = '无映射!'
|
||||||
|
unmapped.append((old_id, name))
|
||||||
|
print(f" {old_id:<30} {name:<15} {'---':<15} {status}")
|
||||||
|
|
||||||
|
if unmapped:
|
||||||
|
print(f"\n ⚠ 警告: {len(unmapped)} 条记录无映射关系,将跳过:")
|
||||||
|
for uid, uname in unmapped:
|
||||||
|
print(f" - {uid} ({uname})")
|
||||||
|
|
||||||
|
if not valid_records:
|
||||||
|
print("\n 没有需要迁移的记录。")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n 共 {len(valid_records)} 条记录需要迁移。\n")
|
||||||
|
|
||||||
|
# ===== 阶段1: 检查 llm_api_map 关联 =====
|
||||||
|
print("[阶段1] 检查 llm_api_map 关联...")
|
||||||
|
for old_id, new_id, name in valid_records:
|
||||||
|
maps = await sor.sqlExe(
|
||||||
|
"SELECT COUNT(*) as cnt FROM llm_api_map WHERE llmcatelogid = ${old_id}$",
|
||||||
|
{'old_id': old_id}
|
||||||
|
)
|
||||||
|
cnt = maps[0]['cnt'] if maps else 0
|
||||||
|
print(f" {name}({old_id}): {cnt} 条映射")
|
||||||
|
|
||||||
|
# ===== 阶段2: 检查新ID是否已被占用 =====
|
||||||
|
print(f"\n[阶段2] 检查新ID是否已被占用...")
|
||||||
|
conflict = False
|
||||||
|
for old_id, new_id, name in valid_records:
|
||||||
|
check = await sor.sqlExe(
|
||||||
|
"SELECT id, name FROM llmcatelog WHERE id = ${new_id}$",
|
||||||
|
{'new_id': new_id}
|
||||||
|
)
|
||||||
|
if check:
|
||||||
|
# 如果新ID已存在且就是当前记录(已经迁移过),跳过
|
||||||
|
if check[0]['id'] == old_id:
|
||||||
|
print(f" {new_id}: 已是当前记录,跳过")
|
||||||
|
else:
|
||||||
|
print(f" ✗ 冲突! 新ID '{new_id}' 已被 {check[0]['name']} 使用")
|
||||||
|
conflict = True
|
||||||
|
else:
|
||||||
|
print(f" ✓ {new_id}: 可用")
|
||||||
|
|
||||||
|
if conflict:
|
||||||
|
print("\n ✗ 存在ID冲突,终止迁移!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print("预览模式结束。以上是将会执行的变更。")
|
||||||
|
print("去掉 --dry-run 参数以正式执行。")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# ===== 阶段3: 执行迁移 =====
|
||||||
|
print(f"\n[阶段3] 开始执行迁移...")
|
||||||
|
|
||||||
|
# 3a: 先更新 llm_api_map(外键表)
|
||||||
|
print(f"\n --- 3a: 更新 llm_api_map.llmcatelogid ---")
|
||||||
|
for old_id, new_id, name in valid_records:
|
||||||
|
try:
|
||||||
|
await sor.sqlExe(
|
||||||
|
"UPDATE llm_api_map SET llmcatelogid = ${new_id}$ WHERE llmcatelogid = ${old_id}$",
|
||||||
|
{'new_id': new_id, 'old_id': old_id}
|
||||||
|
)
|
||||||
|
maps = await sor.sqlExe(
|
||||||
|
"SELECT COUNT(*) as cnt FROM llm_api_map WHERE llmcatelogid = ${new_id}$",
|
||||||
|
{'new_id': new_id}
|
||||||
|
)
|
||||||
|
cnt = maps[0]['cnt'] if maps else 0
|
||||||
|
print(f" ✓ {name}: {old_id} -> {new_id} (关联 {cnt} 条)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ {name}: 更新 llm_api_map 失败: {e}")
|
||||||
|
print(f" 回滚中...")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# 3b: 再更新 llmcatelog(主表)
|
||||||
|
print(f"\n --- 3b: 更新 llmcatelog.id ---")
|
||||||
|
for old_id, new_id, name in valid_records:
|
||||||
|
try:
|
||||||
|
await sor.sqlExe(
|
||||||
|
"UPDATE llmcatelog SET id = ${new_id}$ WHERE id = ${old_id}$",
|
||||||
|
{'new_id': new_id, 'old_id': old_id}
|
||||||
|
)
|
||||||
|
print(f" ✓ {name}: {old_id} -> {new_id}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ {name}: 更新 llmcatelog 失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# ===== 阶段4: 验证 =====
|
||||||
|
print(f"\n[阶段4] 验证迁移结果...")
|
||||||
|
|
||||||
|
# 验证 llmcatelog
|
||||||
|
catelogs = await sor.sqlExe("SELECT id, name FROM llmcatelog ORDER BY id", {})
|
||||||
|
print(f"\n llmcatelog ({len(catelogs)} 条):")
|
||||||
|
for row in catelogs:
|
||||||
|
print(f" {row['id']:<20} {row['name']}")
|
||||||
|
|
||||||
|
# 验证关联完整性
|
||||||
|
orphans = await sor.sqlExe("""
|
||||||
|
SELECT m.llmcatelogid, COUNT(*) as cnt
|
||||||
|
FROM llm_api_map m
|
||||||
|
LEFT JOIN llmcatelog c ON m.llmcatelogid = c.id
|
||||||
|
WHERE c.id IS NULL
|
||||||
|
GROUP BY m.llmcatelogid
|
||||||
|
""", {})
|
||||||
|
if orphans:
|
||||||
|
print(f"\n ✗ 发现孤立关联:")
|
||||||
|
for o in orphans:
|
||||||
|
print(f" llmcatelogid={o['llmcatelogid']}: {o['cnt']} 条无对应主记录")
|
||||||
|
else:
|
||||||
|
print(f"\n ✓ 所有 llm_api_map 关联完整,无孤立记录")
|
||||||
|
|
||||||
|
# 验证映射表
|
||||||
|
map_stats = await sor.sqlExe("""
|
||||||
|
SELECT m.llmcatelogid, c.name, COUNT(*) as cnt
|
||||||
|
FROM llm_api_map m
|
||||||
|
JOIN llmcatelog c ON m.llmcatelogid = c.id
|
||||||
|
GROUP BY m.llmcatelogid, c.name
|
||||||
|
ORDER BY m.llmcatelogid
|
||||||
|
""", {})
|
||||||
|
if map_stats:
|
||||||
|
print(f"\n llm_api_map 关联统计:")
|
||||||
|
for row in map_stats:
|
||||||
|
print(f" {row['llmcatelogid']:<20} {row['name']:<15} {row['cnt']} 条映射")
|
||||||
|
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print("迁移完成!")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='llmcatelog ID 迁移脚本')
|
||||||
|
parser.add_argument('--dry-run', action='store_true', help='预览模式,不执行实际变更')
|
||||||
|
parser.add_argument('--dbname', default='llmage', help='数据库名 (默认: llmage)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
asyncio.run(migrate(dry_run=args.dry_run, dbname=args.dbname))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
393
scripts/minimax_m3_add.sql
Normal file
393
scripts/minimax_m3_add.sql
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- MiniMax M3 接入 + M2.7-highspeed + 补充全模型定价
|
||||||
|
-- 生成时间: 2026-06-12
|
||||||
|
-- 数据来源: token.opencomputing.cn 实时查询 (bugfix/execute_sql)
|
||||||
|
-- 参考: qwen3.7-max (llm:u1EtkR9xRcmwMvdoCZRC8, ppid:5i1JIpqERgCWqKQ4DCegD)
|
||||||
|
-- 接口: 使用uapi模块, upappid=minimax, baseurl=https://api.minimaxi.com/v1
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. 新增 uapi: minimax t2t (纯文本对话, OpenAI兼容)
|
||||||
|
-- 复用ioid: Is8l4TGkcZcqFSjbbeIK2 (文本会话, 共享)
|
||||||
|
-- ============================================================
|
||||||
|
REPLACE INTO `uapi` (`id`, `name`, `need_auth`, `stream`, `path`, `httpmethod`, `chunk_match`, `headers`, `params`, `data`, `response`, `ioid`, `callbackurl`, `upappid`)
|
||||||
|
VALUES (
|
||||||
|
'mm_minimax_t2t',
|
||||||
|
't2t',
|
||||||
|
'0',
|
||||||
|
'stream',
|
||||||
|
'/chat/completions',
|
||||||
|
'POST',
|
||||||
|
'data: ',
|
||||||
|
'{
|
||||||
|
"Authorization": "Bearer {{apikey}}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}',
|
||||||
|
NULL,
|
||||||
|
'{
|
||||||
|
{% if stream %}
|
||||||
|
"stream_options":{
|
||||||
|
"include_usage": true
|
||||||
|
},
|
||||||
|
{% endif %}
|
||||||
|
{% if tools %}
|
||||||
|
"tools": {{json.dumps(tools, ensure_ascii=False)}},
|
||||||
|
{% endif %}
|
||||||
|
{% if tool_choice %}
|
||||||
|
"tool_choice": "{{tool_choice}}",
|
||||||
|
{% endif %}
|
||||||
|
{% if messages %}
|
||||||
|
"messages": {{json.dumps(messages, ensure_ascii=False)}},
|
||||||
|
{% else %}
|
||||||
|
"messages": [
|
||||||
|
{% if sys_prompt %}
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": {{json.dumps(sys_prompt, ensure_ascii=False)}}
|
||||||
|
},
|
||||||
|
{% endif %}
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": {{json.dumps(prompt, ensure_ascii=False)}}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{% endif %}
|
||||||
|
{% if stream %}
|
||||||
|
"stream":true,
|
||||||
|
{% endif %}
|
||||||
|
"model": "{{model}}"
|
||||||
|
}
|
||||||
|
',
|
||||||
|
'{
|
||||||
|
"id": "{{id}}",
|
||||||
|
"object": "{{object}}",
|
||||||
|
"created": {{created}},
|
||||||
|
"choices": {{json.dumps(choices, ensure_ascii=False)}},
|
||||||
|
"model": "{{model}}",
|
||||||
|
{% if object == "chat.completion" %}
|
||||||
|
"reasoning_content": {{json.dumps(choices[0].message.reasoning_content, ensure_ascii=False)}},
|
||||||
|
"content":{{json.dumps(choices[0].message.content, ensure_ascii=False)}},
|
||||||
|
{% elif len(choices)>0 %}
|
||||||
|
"reasoning_content": {{json.dumps(choices[0].delta.reasoning_content, ensure_ascii=False)}},
|
||||||
|
"content":{{json.dumps(choices[0].delta.content, ensure_ascii=False)}},
|
||||||
|
{% endif %}
|
||||||
|
{% if usage %}
|
||||||
|
{% set usage1 = usage.update({"model": model}) %}
|
||||||
|
"finish": "1",
|
||||||
|
"usage":{{json.dumps(usage)}}
|
||||||
|
{% else %}
|
||||||
|
"finish":"0"
|
||||||
|
{% endif %}
|
||||||
|
}',
|
||||||
|
'Is8l4TGkcZcqFSjbbeIK2',
|
||||||
|
NULL,
|
||||||
|
'minimax'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. 新增 uapi: minimax tm2t (多模态对话, 支持图片/视频/音频)
|
||||||
|
-- 复用ioid: t-ujII59ku45tIPcdXu4O (文本媒体转文本, 共享)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `uapi` (`id`, `name`, `need_auth`, `stream`, `path`, `httpmethod`, `chunk_match`, `headers`, `params`, `data`, `response`, `ioid`, `callbackurl`, `upappid`)
|
||||||
|
VALUES (
|
||||||
|
'mm_minimax_tm2t',
|
||||||
|
'tm2t',
|
||||||
|
'0',
|
||||||
|
'stream',
|
||||||
|
'/chat/completions',
|
||||||
|
'POST',
|
||||||
|
'data: ',
|
||||||
|
'{
|
||||||
|
"Authorization": "Bearer {{apikey}}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}',
|
||||||
|
NULL,
|
||||||
|
'{
|
||||||
|
"model": "{{model}}",
|
||||||
|
"stream_options":{
|
||||||
|
"include_usage": true
|
||||||
|
},
|
||||||
|
"messages": [
|
||||||
|
{% if sys_prompt %}
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": {{json.dumps(sys_prompt, ensure_ascii=False)}}
|
||||||
|
},
|
||||||
|
{% endif %}
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{% if image_file %}
|
||||||
|
{
|
||||||
|
"type": "image_url",
|
||||||
|
"image_url":"{{b64media2url(request, image_file)}}"
|
||||||
|
},
|
||||||
|
{% endif %}
|
||||||
|
{% if video_file %}
|
||||||
|
{
|
||||||
|
"type": "video_url",
|
||||||
|
"video_url":"{{b64media2url(request, video_file)}}"
|
||||||
|
},
|
||||||
|
{% endif %}
|
||||||
|
{% if audio_file %}
|
||||||
|
{
|
||||||
|
"type": "audio_url",
|
||||||
|
"audio_url":"{{b64media2url(request, audio_file)}}"
|
||||||
|
},
|
||||||
|
{% endif %}
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": {{json.dumps(prompt, ensure_ascii=False)}}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stream":true
|
||||||
|
}',
|
||||||
|
'{
|
||||||
|
"model": "{{model}}",
|
||||||
|
{% if object == "chat.completion" %}
|
||||||
|
"reasoning_content": {{json.dumps(choices[0].message.reasoning_content, ensure_ascii=False)}},
|
||||||
|
"content":{{json.dumps(choices[0].message.content, ensure_ascii=False)}},
|
||||||
|
{% elif len(choices)>0 %}
|
||||||
|
"reasoning_content": {{json.dumps(choices[0].delta.reasoning_content, ensure_ascii=False)}},
|
||||||
|
"content":{{json.dumps(choices[0].delta.content, ensure_ascii=False)}},
|
||||||
|
{% endif %}
|
||||||
|
{% if usage %}
|
||||||
|
"finish": "1",
|
||||||
|
"usage": {{json.dumps(usage)}}
|
||||||
|
{% else %}
|
||||||
|
"finish":"0"
|
||||||
|
{% endif %}
|
||||||
|
}',
|
||||||
|
't-ujII59ku45tIPcdXu4O',
|
||||||
|
NULL,
|
||||||
|
'minimax'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 3. 新增 llm: MiniMax-M3
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `llm` (`id`, `name`, `model`, `description`, `iconid`, `upappid`, `providerid`, `ownerid`, `enabled_date`, `expired_date`, `min_balance`, `status`)
|
||||||
|
VALUES (
|
||||||
|
'mm3_MiniMax_M3',
|
||||||
|
'MiniMax M3',
|
||||||
|
'MiniMax-M3',
|
||||||
|
'MiniMax M3: 编程及Agent SOTA, 1M超长上下文, 多模态, 交错思维链。≤512K永久五折。',
|
||||||
|
'minimax',
|
||||||
|
'minimax',
|
||||||
|
'ww4e_kfX3Lh65Sdys0Vku',
|
||||||
|
'0',
|
||||||
|
'2026-06-12',
|
||||||
|
'9999-12-31',
|
||||||
|
10.00,
|
||||||
|
'published'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 4. 新增 llm: MiniMax-M2.7-highspeed
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `llm` (`id`, `name`, `model`, `description`, `iconid`, `upappid`, `providerid`, `ownerid`, `enabled_date`, `expired_date`, `min_balance`, `status`)
|
||||||
|
VALUES (
|
||||||
|
'mm_m27_highspeed',
|
||||||
|
'MiniMax M2.7 Highspeed',
|
||||||
|
'MiniMax-M2.7-highspeed',
|
||||||
|
'MiniMax M2.7高速版, 更快速度, 适合低延迟场景。输入¥4.2/百万tokens, 输出¥16.8/百万tokens。',
|
||||||
|
'minimax',
|
||||||
|
'minimax',
|
||||||
|
'ww4e_kfX3Lh65Sdys0Vku',
|
||||||
|
'0',
|
||||||
|
'2026-06-12',
|
||||||
|
'9999-12-31',
|
||||||
|
10.00,
|
||||||
|
'published'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 5. 新增 llm_api_map: MiniMax-M3 (t2t)
|
||||||
|
-- apiname='t2t' → 匹配 uapi name='t2t' + upappid='minimax'
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `llm_api_map` (`id`, `llmid`, `llmcatelogid`, `apiname`, `query_apiname`, `query_period`, `ppid`, `isdefaultcatelog`)
|
||||||
|
VALUES (
|
||||||
|
'mm3_map_t2t',
|
||||||
|
'mm3_MiniMax_M3',
|
||||||
|
't2t',
|
||||||
|
't2t',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
'5jmzupARABxkDFwUraFiQ',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 6. 新增 llm_api_map: MiniMax-M3 (tm2t, 多模态)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `llm_api_map` (`id`, `llmid`, `llmcatelogid`, `apiname`, `query_apiname`, `query_period`, `ppid`, `isdefaultcatelog`)
|
||||||
|
VALUES (
|
||||||
|
'mm3_map_tm2t',
|
||||||
|
'mm3_MiniMax_M3',
|
||||||
|
'tm2t',
|
||||||
|
'tm2t',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
'5jmzupARABxkDFwUraFiQ',
|
||||||
|
'0'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 7. 新增 llm_api_map: MiniMax-M2.7-highspeed (t2t)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `llm_api_map` (`id`, `llmid`, `llmcatelogid`, `apiname`, `query_apiname`, `query_period`, `ppid`, `isdefaultcatelog`)
|
||||||
|
VALUES (
|
||||||
|
'mm_m27hs_map_t2t',
|
||||||
|
'mm_m27_highspeed',
|
||||||
|
't2t',
|
||||||
|
't2t',
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
'5jmzupARABxkDFwUraFiQ',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 8. 补充现有模型 llm_api_map.ppid
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 8a. MiniMax-Hailuo-2.3 (视频i2v) → 0V89
|
||||||
|
UPDATE `llm_api_map` SET `ppid` = '0V89eilc_UQ2KiZIRJO8M'
|
||||||
|
WHERE `llmid` = 'AU1f40HV3tqFjxcVWWpyR' AND (`ppid` IS NULL OR `ppid` = '');
|
||||||
|
|
||||||
|
-- 8b. Minimax海螺参考生视频 S2V-01 (视频i2v) → 0V89
|
||||||
|
UPDATE `llm_api_map` SET `ppid` = '0V89eilc_UQ2KiZIRJO8M'
|
||||||
|
WHERE `llmid` = 'oks-VG9D8p2b0Agvs-LeQ' AND (`ppid` IS NULL OR `ppid` = '');
|
||||||
|
|
||||||
|
-- 8c. music-2.0 (音乐) → fQzk
|
||||||
|
UPDATE `llm_api_map` SET `ppid` = 'fQzkUeS6t6NBz_Fu4Fi77'
|
||||||
|
WHERE `llmid` = 'ns7egG9aXi91wjI62yKfu' AND (`ppid` IS NULL OR `ppid` = '');
|
||||||
|
|
||||||
|
-- 8d. speech-2.6-hd (TTS) → mm_tts_pricing
|
||||||
|
UPDATE `llm_api_map` SET `ppid` = 'mm_tts_pricing'
|
||||||
|
WHERE `llmid` = 'q6rdMUsGD1z3S3NyZh_A_' AND (`ppid` IS NULL OR `ppid` = '');
|
||||||
|
|
||||||
|
-- 8e. speech-2.6-turbo (TTS) → mm_tts_pricing
|
||||||
|
UPDATE `llm_api_map` SET `ppid` = 'mm_tts_pricing'
|
||||||
|
WHERE `llmid` = 'CEYD4YWRxjCj4k_6bpzIM' AND (`ppid` IS NULL OR `ppid` = '');
|
||||||
|
|
||||||
|
-- 8f. speech-2.5-hd-preview (TTS) → mm_tts_pricing
|
||||||
|
UPDATE `llm_api_map` SET `ppid` = 'mm_tts_pricing'
|
||||||
|
WHERE `llmid` = 'Si2g0XJ9ym3P5jlrdmcfB' AND (`ppid` IS NULL OR `ppid` = '');
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 9. 新增 pricing_program: MiniMax TTS定价 (元/万字符)
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `pricing_program` (`id`, `name`, `ownerid`, `providerid`, `pricing_belong`, `discount`, `description`, `pricing_spec`)
|
||||||
|
VALUES (
|
||||||
|
'mm_tts_pricing',
|
||||||
|
'MiniMax语音合成定价',
|
||||||
|
'0',
|
||||||
|
'ww4e_kfX3Lh65Sdys0Vku',
|
||||||
|
'provider',
|
||||||
|
1.000,
|
||||||
|
'MiniMax speech系列TTS定价,按万字符计费',
|
||||||
|
'fields:\n model:\n type: str\n label: 模型\n formula:\n type: str\n label: 公式\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 10. 新增 pricing_program_timing: MiniMax TTS
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `pricing_program_timing` (`id`, `ppid`, `name`, `enabled_date`, `expired_date`, `pricing_data`)
|
||||||
|
VALUES (
|
||||||
|
'mm_tts_timing',
|
||||||
|
'mm_tts_pricing',
|
||||||
|
'MiniMax TTS全价',
|
||||||
|
'2026-06-12',
|
||||||
|
'9999-12-31',
|
||||||
|
'unit_values:\n 万字符: 10000\nfields:\n price_factors:\n type: string\n role: factor\n label: 计价因子\n unit_prices:\n type: float\n role: factor\n label: 单位定价\n unit:\n type: string\n role: factor\n label: 计价单位\n model:\n type: string\n role: filter\n label: model\npricings:\n- price_factors: flat\n unit_prices: 3.5\n unit: 万字符\n filters:\n - model: speech-2.6-hd\n- price_factors: flat\n unit_prices: 2.0\n unit: 万字符\n filters:\n - model: speech-2.6-turbo\n- price_factors: flat\n unit_prices: 3.5\n unit: 万字符\n filters:\n - model: speech-2.5-hd-preview\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 11a. 更新 5jmzup fields: 添加 prompt_tokens 字段定义
|
||||||
|
-- 用于分段定价的 range filter,需要 value_mode: between
|
||||||
|
-- ============================================================
|
||||||
|
UPDATE `pricing_program_timing`
|
||||||
|
SET `pricing_data` = REPLACE(`pricing_data`,
|
||||||
|
' model:\n type: string\n role: filter\n label: model',
|
||||||
|
' model:\n type: string\n role: filter\n label: model\n prompt_tokens:\n type: int\n role: filter\n label: prompt_tokens\n value_mode: between')
|
||||||
|
WHERE `ppid` = '5jmzupARABxkDFwUraFiQ' AND `enabled_date` = '2026-04-12';
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 11b. 更新 5jmzup timing: 添加 MiniMax-M3 分段定价
|
||||||
|
-- M3 ≤512K永久五折: 输入2.1/输出8.4/缓存0.42 元/百万tokens
|
||||||
|
-- M3 512K~1M: 输入4.2/输出16.8/缓存0.84
|
||||||
|
-- 使用 prompt_tokens range filter 区分两个计价段
|
||||||
|
-- ============================================================
|
||||||
|
UPDATE `pricing_program_timing`
|
||||||
|
SET `pricing_data` = CONCAT(`pricing_data`, '
|
||||||
|
- price_factors: prompt_tokens
|
||||||
|
unit_prices: 2.1
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
prompt_tokens: 0 =~ 524288
|
||||||
|
value_mode: between
|
||||||
|
- price_factors: completion_tokens
|
||||||
|
unit_prices: 8.4
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
prompt_tokens: 0 =~ 524288
|
||||||
|
value_mode: between
|
||||||
|
- price_factors: cached_tokens
|
||||||
|
unit_prices: 0.42
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
prompt_tokens: 0 =~ 524288
|
||||||
|
value_mode: between
|
||||||
|
- price_factors: prompt_tokens
|
||||||
|
unit_prices: 4.2
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
prompt_tokens: 524288 =~ 1048576
|
||||||
|
value_mode: between
|
||||||
|
- price_factors: completion_tokens
|
||||||
|
unit_prices: 16.8
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
prompt_tokens: 524288 =~ 1048576
|
||||||
|
value_mode: between
|
||||||
|
- price_factors: cached_tokens
|
||||||
|
unit_prices: 0.84
|
||||||
|
unit: 百万
|
||||||
|
filters:
|
||||||
|
- model: MiniMax-M3
|
||||||
|
prompt_tokens: 524288 =~ 1048576
|
||||||
|
value_mode: between
|
||||||
|
')
|
||||||
|
WHERE `ppid` = '5jmzupARABxkDFwUraFiQ' AND `enabled_date` = '2026-04-12';
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 12. 更新 pricing_program 5jmzup: 添加M3到模型选项
|
||||||
|
-- ============================================================
|
||||||
|
UPDATE `pricing_program`
|
||||||
|
SET `pricing_spec` = 'fields:\n model:\n type: str\n label: 模型\n options:\n - MiniMax-M3\n - MiniMax-M2.7\n - MiniMax-M2.7-highspeed\n - MiniMax-M2.5\n - MiniMax-M2.5-highspeed\n - M2-her\n formula:\n type: str\n label: 公式\n'
|
||||||
|
WHERE `id` = '5jmzupARABxkDFwUraFiQ';
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- ROLLBACK 语句 (如需回滚)
|
||||||
|
-- ============================================================
|
||||||
|
-- DELETE FROM `uapi` WHERE `id` IN ('mm_minimax_t2t', 'mm_minimax_tm2t');
|
||||||
|
-- DELETE FROM `llm` WHERE `id` IN ('mm3_MiniMax_M3', 'mm_m27_highspeed');
|
||||||
|
-- DELETE FROM `llm_api_map` WHERE `id` IN ('mm3_map_t2t', 'mm3_map_tm2t', 'mm_m27hs_map_t2t');
|
||||||
|
-- UPDATE `llm_api_map` SET `ppid` = NULL WHERE `llmid` = 'AU1f40HV3tqFjxcVWWpyR';
|
||||||
|
-- UPDATE `llm_api_map` SET `ppid` = NULL WHERE `llmid` = 'oks-VG9D8p2b0Agvs-LeQ';
|
||||||
|
-- UPDATE `llm_api_map` SET `ppid` = NULL WHERE `llmid` = 'ns7egG9aXi91wjI62yKfu';
|
||||||
|
-- UPDATE `llm_api_map` SET `ppid` = NULL WHERE `llmid` = 'q6rdMUsGD1z3S3NyZh_A_';
|
||||||
|
-- UPDATE `llm_api_map` SET `ppid` = NULL WHERE `llmid` = 'CEYD4YWRxjCj4k_6bpzIM';
|
||||||
|
-- UPDATE `llm_api_map` SET `ppid` = NULL WHERE `llmid` = 'Si2g0XJ9ym3P5jlrdmcfB';
|
||||||
|
-- DELETE FROM `pricing_program` WHERE `id` = 'mm_tts_pricing';
|
||||||
|
-- DELETE FROM `pricing_program_timing` WHERE `id` = 'mm_tts_timing';
|
||||||
|
-- -- 5jmzup的pricing_data CONCAT追加需手动编辑YAML移除M3条目
|
||||||
@ -96,6 +96,27 @@ for p in "${LLMUSAGE_PATHS[@]}"; do
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "============================================"
|
||||||
|
echo " llmage: 客户 v1 API 调用权限"
|
||||||
|
echo "============================================"
|
||||||
|
|
||||||
|
CUSTOMER_ROLES=("customer.admin" "customer.user")
|
||||||
|
|
||||||
|
V1_API_PATHS=(
|
||||||
|
"/llmage/v1/chat/completions/index.dspy"
|
||||||
|
"/llmage/v1/video/generations/index.dspy"
|
||||||
|
"/llmage/v1/image/generations/index.dspy"
|
||||||
|
"/llmage/v1/models/index.dspy"
|
||||||
|
"/llmage/v1/tasks/index.dspy"
|
||||||
|
)
|
||||||
|
|
||||||
|
for p in "${V1_API_PATHS[@]}"; do
|
||||||
|
for role in "${CUSTOMER_ROLES[@]}"; do
|
||||||
|
set_perm "${role}" "${p}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo " 权限配置完成,共设置 ${COUNT} 条权限"
|
echo " 权限配置完成,共设置 ${COUNT} 条权限"
|
||||||
|
|||||||
63
scripts/wan27_video_add.sql
Normal file
63
scripts/wan27_video_add.sql
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
-- ============================================================
|
||||||
|
--
|
||||||
|
-- Wan2.7 文生视频 API接口接入
|
||||||
|
-- 生成时间: 2026-06-12 (重写: 2026-06-13)
|
||||||
|
-- 模型: wan2.7-t2v-2026-04-25 (文生视频)
|
||||||
|
-- 支持: 720P/1080P, 2-15秒, 音频, 多镜头叙事
|
||||||
|
-- ============================================================
|
||||||
|
-- 前置条件:
|
||||||
|
-- llm表已有记录: id='IE8Ws20ZSoyAkOryWqhG_', model='wan2.7-t2v-2026-04-25'
|
||||||
|
-- pricing_program已有记录: id='GFJm2LIQoq2C70fFoY1H3', name='通义万相 wan2.7-t2v'
|
||||||
|
-- uapi 't2v' (id='It-ShFhCGIhS0ds3C2JJ0') 已有,复用万象通用文生视频接口
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. 新增 llm_api_map: wan2.7-t2v → t2v接口 + 定价
|
||||||
|
-- ============================================================
|
||||||
|
INSERT IGNORE INTO `llm_api_map` (`id`, `llmid`, `llmcatelogid`, `apiname`, `query_apiname`, `query_period`, `ppid`, `isdefaultcatelog`)
|
||||||
|
VALUES (
|
||||||
|
'wan27t2v_map_001',
|
||||||
|
'IE8Ws20ZSoyAkOryWqhG_',
|
||||||
|
't2v',
|
||||||
|
't2v',
|
||||||
|
't2vstatus',
|
||||||
|
10,
|
||||||
|
'GFJm2LIQoq2C70fFoY1H3',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. 新增 pricing_program_timing: wan2.7-t2v 定价
|
||||||
|
-- 官方定价: 720P=0.6元/秒, 1080P=1.0元/秒
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO `pricing_program_timing` (`id`, `ppid`, `name`, `enabled_date`, `expired_date`, `pricing_data`)
|
||||||
|
VALUES (
|
||||||
|
'wan27t2v_timing_001',
|
||||||
|
'GFJm2LIQoq2C70fFoY1H3',
|
||||||
|
NULL,
|
||||||
|
'2026-05-20',
|
||||||
|
'9999-12-31',
|
||||||
|
'unit_values:\n 秒: 1\nfields:\n price_factors:\n type: string\n role: factor\n label: 计价因子\n unit_prices:\n type: float\n role: factor\n label: 单位定价\n unit:\n type: string\n role: factor\n label: 计价单位\n SR:\n type: string\n role: filter\n label: SR\npricings:\n- price_factors: duration\n unit_prices: 0.6\n unit: 秒\n filters:\n - SR: 720\n- price_factors: duration\n unit_prices: 1.0\n unit: 秒\n filters:\n - SR: 1080'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 验证 (执行后运行确认)
|
||||||
|
-- ============================================================
|
||||||
|
-- SELECT m.id, m.llmid, m.llmcatelogid, m.apiname, m.query_apiname, m.ppid,
|
||||||
|
-- l.name as model_name, l.model,
|
||||||
|
-- pp.name as pricing_name,
|
||||||
|
-- (SELECT COUNT(*) FROM pricing_program_timing WHERE ppid = m.ppid) as timing_count
|
||||||
|
-- FROM llm_api_map m
|
||||||
|
-- JOIN llm l ON m.llmid = l.id
|
||||||
|
-- JOIN pricing_program pp ON m.ppid = pp.id
|
||||||
|
-- WHERE m.llmid = 'IE8Ws20ZSoyAkOryWqhG_';
|
||||||
|
--
|
||||||
|
-- 预期: timing_count = 1
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 回滚
|
||||||
|
-- ============================================================
|
||||||
|
-- DELETE FROM llm_api_map WHERE id = 'wan27t2v_map_001';
|
||||||
|
-- DELETE FROM pricing_program_timing WHERE id = 'wan27t2v_timing_001';
|
||||||
11
sql/add_status_field.sql
Normal file
11
sql/add_status_field.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- llmage: 添加模型上架/下架功能
|
||||||
|
-- 执行此 SQL 后,所有现有模型默认已上架,不影响线上使用
|
||||||
|
|
||||||
|
-- 1. 添加 status 字段
|
||||||
|
ALTER TABLE llm ADD COLUMN `status` VARCHAR(16) NOT NULL DEFAULT 'unpublished' COMMENT '上架状态: published/unpublished' AFTER `min_balance`;
|
||||||
|
|
||||||
|
-- 2. 现有模型全部设为已上架
|
||||||
|
UPDATE llm SET status = 'published';
|
||||||
|
|
||||||
|
-- 3. 添加索引(按状态筛选是高频操作)
|
||||||
|
ALTER TABLE llm ADD INDEX `idx_status` (`status`);
|
||||||
@ -1,76 +1,101 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
result = {'success': False, 'rows': [], 'total': 0, 'page': 1, 'page_size': 50}
|
result = {'success': False, 'rows': [], 'total': 0, 'page': 1, 'page_size': 50}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dbname = get_module_dbname('llmage')
|
llmage_db = get_module_dbname('llmage')
|
||||||
user_orgid = await get_userorgid()
|
sage_db = get_module_dbname('sage')
|
||||||
|
db = DBPools()
|
||||||
|
|
||||||
# Extract filter parameters from params_kw
|
filters = {}
|
||||||
filters = {}
|
if params_kw.get('userorgid'):
|
||||||
if params_kw.get('userorgid'):
|
filters['userorgid'] = params_kw.get('userorgid')
|
||||||
filters['userorgid'] = params_kw.get('userorgid')
|
if params_kw.get('llmid'):
|
||||||
if params_kw.get('llmid'):
|
filters['llmid'] = params_kw.get('llmid')
|
||||||
filters['llmid'] = params_kw.get('llmid')
|
if params_kw.get('handled') is not None and params_kw.get('handled') != '':
|
||||||
if params_kw.get('handled') is not None:
|
filters['handled'] = params_kw.get('handled')
|
||||||
filters['handled'] = params_kw.get('handled')
|
if params_kw.get('start_date'):
|
||||||
if params_kw.get('start_date'):
|
filters['start_date'] = params_kw.get('start_date')
|
||||||
filters['start_date'] = params_kw.get('start_date')
|
if params_kw.get('end_date'):
|
||||||
if params_kw.get('end_date'):
|
filters['end_date'] = params_kw.get('end_date')
|
||||||
filters['end_date'] = params_kw.get('end_date')
|
if params_kw.get('filter_userid'):
|
||||||
|
filters['filter_userid'] = params_kw.get('filter_userid')
|
||||||
|
if params_kw.get('filter_llmid'):
|
||||||
|
filters['filter_llmid'] = params_kw.get('filter_llmid')
|
||||||
|
|
||||||
page = int(params_kw.get('page', 1))
|
page = int(params_kw.get('page', 1))
|
||||||
page_size = int(params_kw.get('page_size', 50))
|
page_size = int(params_kw.get('page_size', 50))
|
||||||
|
|
||||||
async with DBPools().sqlorContext(dbname) as sor:
|
async with db.sqlorContext(llmage_db) as sor:
|
||||||
# Build dynamic SQL
|
conditions = []
|
||||||
conditions = []
|
ns = {}
|
||||||
ns = {}
|
|
||||||
|
|
||||||
# Default: show unhandled records
|
if filters.get('userorgid'):
|
||||||
if 'handled' not in filters:
|
conditions.append("f.userorgid=${userorgid}$")
|
||||||
conditions.append("handled='0'")
|
ns['userorgid'] = filters['userorgid']
|
||||||
|
if filters.get('llmid'):
|
||||||
|
conditions.append("f.llmid=${llmid}$")
|
||||||
|
ns['llmid'] = filters['llmid']
|
||||||
|
if filters.get('handled') is not None:
|
||||||
|
conditions.append("f.handled=${handled}$")
|
||||||
|
ns['handled'] = filters['handled']
|
||||||
|
if filters.get('start_date'):
|
||||||
|
conditions.append("f.use_date>=${start_date}$")
|
||||||
|
ns['start_date'] = filters['start_date']
|
||||||
|
if filters.get('end_date'):
|
||||||
|
conditions.append("f.use_date<=${end_date}$")
|
||||||
|
ns['end_date'] = filters['end_date']
|
||||||
|
if filters.get('filter_userid'):
|
||||||
|
conditions.append("(u.username LIKE ${filter_userid}$ OR u.name LIKE ${filter_userid}$)")
|
||||||
|
ns['filter_userid'] = '%' + filters['filter_userid'] + '%'
|
||||||
|
if filters.get('filter_llmid'):
|
||||||
|
conditions.append("(f.llmid LIKE ${filter_llmid}$ OR l.name LIKE ${filter_llmid}$)")
|
||||||
|
ns['filter_llmid'] = '%' + filters['filter_llmid'] + '%'
|
||||||
|
|
||||||
if filters.get('userorgid'):
|
where = ""
|
||||||
conditions.append("userorgid=${userorgid}$")
|
if conditions:
|
||||||
ns['userorgid'] = filters['userorgid']
|
where = "WHERE " + " AND ".join(conditions)
|
||||||
if filters.get('llmid'):
|
|
||||||
conditions.append("llmid=${llmid}$")
|
|
||||||
ns['llmid'] = filters['llmid']
|
|
||||||
if filters.get('handled') is not None:
|
|
||||||
conditions.append("handled=${handled}$")
|
|
||||||
ns['handled'] = filters['handled']
|
|
||||||
if filters.get('start_date'):
|
|
||||||
conditions.append("use_date>=${start_date}$")
|
|
||||||
ns['start_date'] = filters['start_date']
|
|
||||||
if filters.get('end_date'):
|
|
||||||
conditions.append("use_date<=${end_date}$")
|
|
||||||
ns['end_date'] = filters['end_date']
|
|
||||||
|
|
||||||
where = ""
|
# 跨库JOIN获取名称
|
||||||
if conditions:
|
sql = f"""
|
||||||
where = "where " + " and ".join(conditions)
|
SELECT f.*,
|
||||||
|
u.username as userid_text,
|
||||||
|
o.orgname as userorgid_text,
|
||||||
|
l.name as llmid_text
|
||||||
|
FROM llmusage_accounting_failed f
|
||||||
|
LEFT JOIN {sage_db}.users u ON f.userid = u.id
|
||||||
|
LEFT JOIN {sage_db}.organization o ON f.userorgid = o.id
|
||||||
|
LEFT JOIN {llmage_db}.llm l ON f.llmid = l.id
|
||||||
|
{where}
|
||||||
|
ORDER BY f.failed_time DESC
|
||||||
|
"""
|
||||||
|
|
||||||
# Count total
|
count_sql = f"""
|
||||||
count_sql = f"select count(*) as cnt from llmusage_accounting_failed {where}"
|
SELECT count(*) as cnt
|
||||||
count_recs = await sor.sqlExe(count_sql, ns)
|
FROM llmusage_accounting_failed f
|
||||||
total = count_recs[0].cnt if count_recs else 0
|
LEFT JOIN {sage_db}.users u ON f.userid = u.id
|
||||||
|
LEFT JOIN {sage_db}.organization o ON f.userorgid = o.id
|
||||||
|
LEFT JOIN {llmage_db}.llm l ON f.llmid = l.id
|
||||||
|
{where}
|
||||||
|
"""
|
||||||
|
count_recs = await sor.sqlExe(count_sql, ns)
|
||||||
|
total = count_recs[0].cnt if count_recs else 0
|
||||||
|
|
||||||
# Query with pagination
|
offset = (page - 1) * page_size
|
||||||
offset = (page - 1) * page_size
|
query_sql = sql + f" LIMIT {page_size} OFFSET {offset}"
|
||||||
query_sql = f"""select * from llmusage_accounting_failed {where}
|
recs = await sor.sqlExe(query_sql, ns)
|
||||||
order by failed_time desc limit {page_size} offset {offset}"""
|
|
||||||
recs = await sor.sqlExe(query_sql, ns)
|
|
||||||
|
|
||||||
result['rows'] = [dict(r) for r in (recs or [])]
|
rows = []
|
||||||
result['total'] = total
|
for r in (recs or []):
|
||||||
result['page'] = page
|
d = dict(r)
|
||||||
result['page_size'] = page_size
|
rows.append(d)
|
||||||
result['success'] = True
|
|
||||||
|
result['rows'] = rows
|
||||||
|
result['total'] = total
|
||||||
|
result['page'] = page
|
||||||
|
result['page_size'] = page_size
|
||||||
|
result['success'] = True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result['error'] = str(e)
|
result['error'] = str(e)
|
||||||
|
debug(f'failed_accounting_list error: {format_exc()}')
|
||||||
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
|
|||||||
14
wwwroot/api/get_apis.dspy
Normal file
14
wwwroot/api/get_apis.dspy
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("select name, path from uapi order by name", {})
|
||||||
|
result = [{'value': r['name'], 'text': f"{r['name']} ({r['path']})"} for r in (rows or [])]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
14
wwwroot/api/get_catelogs.dspy
Normal file
14
wwwroot/api/get_catelogs.dspy
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("select id, name from llmcatelog order by name", {})
|
||||||
|
result = [{'value': r['id'], 'text': r['name']} for r in (rows or [])]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
93
wwwroot/api/get_inference_history.dspy
Normal file
93
wwwroot/api/get_inference_history.dspy
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
result = {'success': False, 'rows': [], 'total': 0, 'page': 1, 'page_size': 10}
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
userid = await get_user()
|
||||||
|
|
||||||
|
page = int(params_kw.get('page', 1))
|
||||||
|
page_size = int(params_kw.get('pagerows', 10))
|
||||||
|
llmcatelogid = params_kw.get('llmcatelogid')
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
# Build filter conditions
|
||||||
|
conditions = ["userid = ${userid}$"]
|
||||||
|
ns = {'userid': userid}
|
||||||
|
if llmcatelogid:
|
||||||
|
conditions.append("llmid in (select llmid from llm_api_map where llmcatelogid = ${llmcatelogid}$)")
|
||||||
|
ns['llmcatelogid'] = llmcatelogid
|
||||||
|
|
||||||
|
where_clause = " and ".join(conditions)
|
||||||
|
# Count total from both tables (并行两个 count 查询)
|
||||||
|
sql1 = f"select count(*) as cnt from llmusage where {where_clause}"
|
||||||
|
sql2 = f"select count(*) as cnt from llmusage_history where {where_clause}"
|
||||||
|
cnt1_recs = await sor.sqlExe(sql1, ns.copy())
|
||||||
|
cnt2_recs = await sor.sqlExe(sql2, ns.copy())
|
||||||
|
total = (cnt1_recs[0].cnt if cnt1_recs else 0) + (cnt2_recs[0].cnt if cnt2_recs else 0)
|
||||||
|
# 优化点 1: 分别查询两张表, 让各自走 (userid, use_time) 复合索引
|
||||||
|
# 每表取前 offset+page_size 条 (已按 use_time desc 排好)
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
fetch = offset + page_size
|
||||||
|
select_cols = ("id, llmid, use_date, use_time, userid, usages, ioinfo, "
|
||||||
|
"status, taskid, amount, cost, userorgid, accounting_status")
|
||||||
|
|
||||||
|
q1 = f"select {select_cols} from llmusage where {where_clause} order by use_time desc limit {fetch}"
|
||||||
|
q2 = f"select {select_cols} from llmusage_history where {where_clause} order by use_time desc limit {fetch}"
|
||||||
|
recs1 = await sor.sqlExe(q1, ns)
|
||||||
|
recs2 = await sor.sqlExe(q2, ns)
|
||||||
|
|
||||||
|
# 优化点 2: Python 归并两个已排序序列 (O(n) 比 SQL UNION+sort 快)
|
||||||
|
merged = []
|
||||||
|
i = j = 0
|
||||||
|
rows1 = [dict(r) for r in (recs1 or [])]
|
||||||
|
rows2 = [dict(r) for r in (recs2 or [])]
|
||||||
|
while i < len(rows1) and j < len(rows2):
|
||||||
|
if (rows1[i].get('use_time') or '') >= (rows2[j].get('use_time') or ''):
|
||||||
|
merged.append(rows1[i]); i += 1
|
||||||
|
else:
|
||||||
|
merged.append(rows2[j]); j += 1
|
||||||
|
merged.extend(rows1[i:])
|
||||||
|
merged.extend(rows2[j:])
|
||||||
|
|
||||||
|
# 应用分页
|
||||||
|
page_rows = merged[offset:offset + page_size]
|
||||||
|
|
||||||
|
# 优化点 3: 并发读取 ioinfo 文件 (不再串行 await)
|
||||||
|
import aiofiles
|
||||||
|
from ahserver.filestorage import FileStorage
|
||||||
|
fs = FileStorage()
|
||||||
|
|
||||||
|
async def _load_io(row):
|
||||||
|
webpath = row.get('ioinfo')
|
||||||
|
io_content = None
|
||||||
|
if webpath:
|
||||||
|
try:
|
||||||
|
real_path = fs.realPath(webpath)
|
||||||
|
async with aiofiles.open(real_path, 'rb') as f:
|
||||||
|
bin_data = await f.read()
|
||||||
|
io_content = json.loads(bin_data.decode('utf-8'))
|
||||||
|
except Exception:
|
||||||
|
io_content = None
|
||||||
|
row['io_content'] = io_content
|
||||||
|
if isinstance(row.get('usages'), str):
|
||||||
|
try:
|
||||||
|
row['usages'] = json.loads(row['usages'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return row
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for r in page_rows:
|
||||||
|
d = await _load_io(r)
|
||||||
|
rows.append(d)
|
||||||
|
result['rows'] = list(rows)
|
||||||
|
result['total'] = total
|
||||||
|
result['page'] = page
|
||||||
|
result['page_size'] = page_size
|
||||||
|
result['success'] = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
exception(f'{e}{format_exc()}')
|
||||||
|
result['error'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
|
|
||||||
15
wwwroot/api/get_organizations.dspy
Normal file
15
wwwroot/api/get_organizations.dspy
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
result = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'rbac') as sor:
|
||||||
|
orgs = await sor.sqlExe(
|
||||||
|
"select id, orgname from organization order by orgname",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
if orgs:
|
||||||
|
for r in orgs:
|
||||||
|
result.append({'providerid': str(r.id), 'providerid_text': r.orgname or ''})
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'get_organizations error: {e}')
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
14
wwwroot/api/get_ppids.dspy
Normal file
14
wwwroot/api/get_ppids.dspy
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('pricing')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.sqlExe("select id, name from pricing_program order by name", {})
|
||||||
|
result = [{'value': r['id'], 'text': r['name']} for r in (rows or [])]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
37
wwwroot/api/get_search_apiname.dspy
Normal file
37
wwwroot/api/get_search_apiname.dspy
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
llmid = params_kw.get('llmid')
|
||||||
|
allow_empty = params_kw.get('allow_empty', '')
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if allow_empty:
|
||||||
|
result = [{'apiname': '', 'apiname_text': '不指定', 'value': '', 'text': '不指定'}]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
# Get model's upappid from llmage db
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
llm_recs = await sor.sqlExe(
|
||||||
|
"select upappid from llm where id = ${llmid}$",
|
||||||
|
{'llmid': llmid}
|
||||||
|
)
|
||||||
|
if not llm_recs or not llm_recs[0].get('upappid'):
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
upappid = llm_recs[0].upappid
|
||||||
|
|
||||||
|
# Query uapi table from uapi module's db
|
||||||
|
async with get_sor_context(request._run_ns, 'uapi') as sor:
|
||||||
|
apis = await sor.sqlExe(
|
||||||
|
"select name as apiname, name as apiname_text from uapi where upappid = ${upappid}$ order by name",
|
||||||
|
{'upappid': upappid}
|
||||||
|
)
|
||||||
|
# Add value/text keys for form dropdown compatibility
|
||||||
|
for a in apis:
|
||||||
|
a['value'] = a['apiname']
|
||||||
|
a['text'] = a['apiname_text']
|
||||||
|
return json.dumps(result + list(apis), ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'get_search_apiname error: {e}')
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
13
wwwroot/api/get_search_providerid.dspy
Normal file
13
wwwroot/api/get_search_providerid.dspy
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
result = [{'providerid': '', 'providerid_text': '全部'}]
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'rbac') as sor:
|
||||||
|
orgs = await sor.sqlExe(
|
||||||
|
"select id as providerid, orgname as providerid_text from organization order by orgname",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return json.dumps([{'providerid': '', 'providerid_text': '全部'}] + list(orgs), ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'get_search_providerid error: {e}')
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
13
wwwroot/api/get_search_upappid.dspy
Normal file
13
wwwroot/api/get_search_upappid.dspy
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
result = [{'upappid': '', 'upappid_text': '全部'}]
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'uapi') as sor:
|
||||||
|
apps = await sor.sqlExe(
|
||||||
|
"select id as upappid, name as upappid_text from upapp order by name",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return json.dumps([{'upappid': '', 'upappid_text': '全部'}] + list(apps), ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'get_search_upappid error: {e}')
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
16
wwwroot/api/get_upapps.dspy
Normal file
16
wwwroot/api/get_upapps.dspy
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
result = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'uapi') as sor:
|
||||||
|
user_orgid = await get_userorgid()
|
||||||
|
apps = await sor.sqlExe(
|
||||||
|
"select id, name from upapp order by name",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
if apps:
|
||||||
|
for r in apps:
|
||||||
|
result.append({'upappid': str(r.id), 'upappid_text': r.name or ''})
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'get_upapps error: {e}')
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
21
wwwroot/api/llm_create.dspy
Normal file
21
wwwroot/api/llm_create.dspy
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
|
||||||
|
result = {'success': False, 'message': ''}
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
data = params_kw.copy()
|
||||||
|
data.pop('page', None)
|
||||||
|
data.pop('rows', None)
|
||||||
|
data.pop('data_filter', None)
|
||||||
|
data['id'] = getID()
|
||||||
|
await sor.C('llm', data)
|
||||||
|
result['success'] = True
|
||||||
|
result['message'] = '创建成功'
|
||||||
|
except Exception as e:
|
||||||
|
result['message'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
23
wwwroot/api/llm_delete.dspy
Normal file
23
wwwroot/api/llm_delete.dspy
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = {'success': False, 'message': ''}
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
data = params_kw.copy()
|
||||||
|
data.pop('page', None)
|
||||||
|
data.pop('rows', None)
|
||||||
|
data.pop('data_filter', None)
|
||||||
|
record_id = data.get('id')
|
||||||
|
if not record_id:
|
||||||
|
result['message'] = '缺少id'
|
||||||
|
else:
|
||||||
|
await sor.D('llm', {'id': record_id})
|
||||||
|
result['success'] = True
|
||||||
|
result['message'] = '删除成功'
|
||||||
|
except Exception as e:
|
||||||
|
result['message'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
88
wwwroot/api/llm_launch_check_api.dspy
Normal file
88
wwwroot/api/llm_launch_check_api.dspy
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
action = params_kw.get('action', 'check')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({'error': 'missing llmid'}, ensure_ascii=False)
|
||||||
|
|
||||||
|
if action == 'inference':
|
||||||
|
# 验证推理配置是否完整
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"select * from llm where id=${llmid}$", {'llmid': llmid})
|
||||||
|
if not recs:
|
||||||
|
return '❌ 模型记录不存在'
|
||||||
|
llm = recs[0]
|
||||||
|
|
||||||
|
# 检查 API 映射
|
||||||
|
maps = await sor.sqlExe(
|
||||||
|
"select * from llm_api_map where llmid=${llmid}$",
|
||||||
|
{'llmid': llmid})
|
||||||
|
if not maps:
|
||||||
|
return '❌ 无 API 映射配置'
|
||||||
|
|
||||||
|
# 检查 upapp 和 uapi
|
||||||
|
uapi_recs = await sor.sqlExe("""
|
||||||
|
select a.*, e.ioid, e.stream, e.name as api_name
|
||||||
|
from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join upapp c on a.upappid = c.id
|
||||||
|
join uapi e on c.id = e.upappid and m.apiname = e.name
|
||||||
|
where a.id=${llmid}$""", {'llmid': llmid})
|
||||||
|
|
||||||
|
if not uapi_recs:
|
||||||
|
return '❌ uapi 配置不完整,无法调用'
|
||||||
|
|
||||||
|
uapi = uapi_recs[0]
|
||||||
|
|
||||||
|
# 检查 ioid
|
||||||
|
io_recs = await sor.sqlExe(
|
||||||
|
"select * from uapiio where id=${ioid}$", {'ioid': uapi.ioid})
|
||||||
|
if not io_recs:
|
||||||
|
return '❌ IO 定义不存在'
|
||||||
|
|
||||||
|
return f'✅ 推理配置验证通过\n模型: {llm.name}\nAPI: {uapi.api_name}\nIO: {uapi.ioid}\nStream: {uapi.stream}'
|
||||||
|
|
||||||
|
elif action == 'check_charging':
|
||||||
|
# 验证计费配置是否完整
|
||||||
|
usages_str = params_kw.get('usages', '{}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
usages = json.loads(usages_str) if isinstance(usages_str, str) else usages_str
|
||||||
|
except:
|
||||||
|
usages = {}
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
maps = await sor.sqlExe(
|
||||||
|
"select * from llm_api_map where llmid=${llmid}$",
|
||||||
|
{'llmid': llmid})
|
||||||
|
if not maps:
|
||||||
|
return '❌ 无 API 映射'
|
||||||
|
|
||||||
|
ppids = [m.ppid for m in maps if m.ppid]
|
||||||
|
if not ppids:
|
||||||
|
return '❌ 无定价项目(ppid)'
|
||||||
|
|
||||||
|
ppid = ppids[0]
|
||||||
|
|
||||||
|
# 检查 pricing_program
|
||||||
|
async with get_sor_context(request._run_ns, 'pricing') as psor:
|
||||||
|
pregs = await psor.sqlExe(
|
||||||
|
"select * from pricing_program where id=${ppid}$", {'ppid': ppid})
|
||||||
|
if not pregs:
|
||||||
|
return f'❌ 定价项目不存在 (ppid={ppid})'
|
||||||
|
|
||||||
|
pp = pregs[0]
|
||||||
|
|
||||||
|
# 检查 pricing_program_timing
|
||||||
|
try:
|
||||||
|
timings = await psor.sqlExe(
|
||||||
|
"select * from pricing_program_timing where ppid=${ppid}$",
|
||||||
|
{'ppid': ppid})
|
||||||
|
if timings:
|
||||||
|
return f'✅ 计费配置验证通过\n定价项目: {pp.name}\n定价数据: {len(timings)}条记录\n测试用量: {json.dumps(usages)}'
|
||||||
|
else:
|
||||||
|
return f'⚠️ 定价项目存在但无定价数据\n定价项目: {pp.name}'
|
||||||
|
except Exception as e:
|
||||||
|
return f'⚠️ 定价数据查询失败: {e}'
|
||||||
|
|
||||||
|
return '无效的操作'
|
||||||
24
wwwroot/api/llm_status_update.dspy
Normal file
24
wwwroot/api/llm_status_update.dspy
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
result = {'success': False, 'message': ''}
|
||||||
|
action = params_kw.action
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
record_id = params_kw.get('id')
|
||||||
|
if not record_id:
|
||||||
|
result['message'] = '缺少id'
|
||||||
|
elif action not in ('published', 'unpublished'):
|
||||||
|
result['message'] = '无效的状态值'
|
||||||
|
else:
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
await sor.U('llm', {'id': record_id, 'status': action})
|
||||||
|
result['success'] = True
|
||||||
|
result['message'] = '上架成功' if action == 'published' else '下架成功'
|
||||||
|
except Exception as e:
|
||||||
|
result['message'] = str(e)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"otext": result['message'],
|
||||||
|
"i18n": True
|
||||||
|
}
|
||||||
|
}
|
||||||
23
wwwroot/api/llm_update.dspy
Normal file
23
wwwroot/api/llm_update.dspy
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = {'success': False, 'message': ''}
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
data = params_kw.copy()
|
||||||
|
data.pop('page', None)
|
||||||
|
data.pop('rows', None)
|
||||||
|
data.pop('data_filter', None)
|
||||||
|
record_id = data.pop('id', None)
|
||||||
|
if not record_id:
|
||||||
|
result['message'] = '缺少id'
|
||||||
|
else:
|
||||||
|
await sor.U('llm', data, {'id': record_id})
|
||||||
|
result['success'] = True
|
||||||
|
result['message'] = '更新成功'
|
||||||
|
except Exception as e:
|
||||||
|
result['message'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
@ -1,7 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
result = {'success': False, 'message': ''}
|
result = {'success': False, 'message': ''}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -11,14 +7,12 @@ try:
|
|||||||
result['message'] = '缺少llmusageid参数'
|
result['message'] = '缺少llmusageid参数'
|
||||||
else:
|
else:
|
||||||
async with DBPools().sqlorContext(dbname) as sor:
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
# 1. 重置 llmusage 记账状态为 created,让后台循环重新处理
|
|
||||||
await sor.U('llmusage', {
|
await sor.U('llmusage', {
|
||||||
'id': luid,
|
'id': luid,
|
||||||
'accounting_status': 'created'
|
'accounting_status': 'created'
|
||||||
})
|
})
|
||||||
|
|
||||||
# 2. 更新失败记录:标记已处理,增加重试次数
|
now = curDateString() + ' ' + timestampstr().split(' ')[1] if ' ' not in curDateString() else curDateString()
|
||||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
await sor.execute("""
|
await sor.execute("""
|
||||||
UPDATE llmusage_accounting_failed
|
UPDATE llmusage_accounting_failed
|
||||||
SET handled = '1',
|
SET handled = '1',
|
||||||
@ -27,10 +21,10 @@ try:
|
|||||||
handled_note = CONCAT(IFNULL(handled_note, ''), '[', ${now}$, '] 触发重试; ')
|
handled_note = CONCAT(IFNULL(handled_note, ''), '[', ${now}$, '] 触发重试; ')
|
||||||
WHERE llmusageid = ${luid}$
|
WHERE llmusageid = ${luid}$
|
||||||
""", {'luid': luid, 'now': now})
|
""", {'luid': luid, 'now': now})
|
||||||
|
|
||||||
result['success'] = True
|
result['success'] = True
|
||||||
result['message'] = '已重置为待记账状态,后台循环将重新处理'
|
result['message'] = '已重置为待记账状态,后台循环将重新处理'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result['message'] = str(e)
|
result['message'] = str(e)
|
||||||
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
|||||||
28
wwwroot/api/show_failed_reason.dspy
Normal file
28
wwwroot/api/show_failed_reason.dspy
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
record_id = params_kw.get('id', '')
|
||||||
|
|
||||||
|
reason = '未找到记录'
|
||||||
|
if record_id:
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
rows = await sor.R('llmusage_accounting_failed', {'id': record_id})
|
||||||
|
if rows:
|
||||||
|
reason = rows[0].get('failed_reason', '') or '(空)'
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype": "VScrollPanel",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
"css": "card",
|
||||||
|
"padding": "12px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": reason,
|
||||||
|
"i18n": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,17 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import json
|
import json
|
||||||
|
|
||||||
result = {'success': False, 'data': []}
|
result = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dbname = get_module_dbname('llmage')
|
dbname = get_module_dbname('llmage')
|
||||||
|
|
||||||
async with DBPools().sqlorContext(dbname) as sor:
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
rows = await sor.sqlExe("select name, path from uapi order by name", {})
|
rows = await sor.sqlExe("select name, path from uapi order by name", {})
|
||||||
result['data'] = [{'id': r['name'], 'text': f"{r['name']} ({r['path']})"} for r in (rows or [])]
|
result = [{'value': r['name'], 'text': f"{r['name']} ({r['path']})"} for r in (rows or [])]
|
||||||
result['success'] = True
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result['error'] = str(e)
|
pass
|
||||||
|
|
||||||
return json.dumps(result, ensure_ascii=False, default=str)
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
|
|||||||
1
wwwroot/api_doc.md
Symbolic link
1
wwwroot/api_doc.md
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../docs/API.md
|
||||||
41
wwwroot/api_doc.ui
Normal file
41
wwwroot/api_doc.ui
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
"padding": "0"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"alignItems": "center",
|
||||||
|
"marginBottom": "16px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Title2",
|
||||||
|
"options": {
|
||||||
|
"text": "大模型 API 文档"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VScrollPanel",
|
||||||
|
"options": {
|
||||||
|
"css": "filler"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "MarkdownViewer",
|
||||||
|
"options": {
|
||||||
|
"md_url": "{{entire_url('/llmage/api_doc.md')}}",
|
||||||
|
"width": "100%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
31
wwwroot/check_date_status.dspy
Normal file
31
wwwroot/check_date_status.dspy
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ 日期与状态: 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"select * from llm where id=${llmid}$", {'llmid': llmid})
|
||||||
|
|
||||||
|
if not recs:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ 日期与状态: 模型不存在", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
llm = recs[0]
|
||||||
|
date_ok = bool(llm.enabled_date and llm.expired_date)
|
||||||
|
status_ok = llm.status == 'published'
|
||||||
|
|
||||||
|
if date_ok and status_ok:
|
||||||
|
text = f"✅ 日期与状态: 启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}"
|
||||||
|
else:
|
||||||
|
text = f"❌ 日期与状态: 启用:{llm.enabled_date} 失效:{llm.expired_date} 状态:{llm.status}"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
22
wwwroot/check_llm_api_map.dspy
Normal file
22
wwwroot/check_llm_api_map.dspy
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ 能力映射(llm_api_map): 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
maps = await sor.sqlExe(
|
||||||
|
"select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
|
||||||
|
|
||||||
|
if maps:
|
||||||
|
ppids = [m.ppid for m in maps if m.ppid]
|
||||||
|
text = f"✅ 能力映射(llm_api_map): {len(maps)}条记录, {len(ppids)}个有定价"
|
||||||
|
else:
|
||||||
|
text = "❌ 能力映射(llm_api_map): 无映射记录"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
22
wwwroot/check_model_record.dspy
Normal file
22
wwwroot/check_model_record.dspy
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ 模型记录: 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"select * from llm where id=${llmid}$", {'llmid': llmid})
|
||||||
|
|
||||||
|
if recs:
|
||||||
|
llm = recs[0]
|
||||||
|
text = f"✅ 模型记录: {llm.name} ({llm.model})"
|
||||||
|
else:
|
||||||
|
text = f"❌ 模型记录: llm id={llmid} 不存在"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
42
wwwroot/check_pricing_data.dspy
Normal file
42
wwwroot/check_pricing_data.dspy
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ 定价数据: 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
maps = await sor.sqlExe(
|
||||||
|
"select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
|
||||||
|
ppids = [m.ppid for m in maps if m.ppid] if maps else []
|
||||||
|
|
||||||
|
if not ppids:
|
||||||
|
text = "❌ 定价数据: 无定价项目"
|
||||||
|
else:
|
||||||
|
ppid = ppids[0]
|
||||||
|
try:
|
||||||
|
async with get_sor_context(request._run_ns, 'pricing') as psor:
|
||||||
|
pregs = await psor.sqlExe(
|
||||||
|
"select * from pricing_program where id=${ppid}$", {'ppid': ppid})
|
||||||
|
if not pregs:
|
||||||
|
text = "❌ 定价数据: 依赖定价项目未通过"
|
||||||
|
else:
|
||||||
|
# 检查 pricing_program_timing 表
|
||||||
|
try:
|
||||||
|
timings = await psor.sqlExe(
|
||||||
|
"select count(*) as cnt from pricing_program_timing where ppid=${ppid}$", {'ppid': ppid})
|
||||||
|
cnt = timings[0].cnt if timings else 0
|
||||||
|
if cnt > 0:
|
||||||
|
text = f"✅ 定价数据(pricing_program_timing): {cnt}条记录"
|
||||||
|
else:
|
||||||
|
text = "❌ 定价数据: pricing_program_timing 无记录"
|
||||||
|
except Exception as e:
|
||||||
|
text = f"❌ 定价数据: {e}"
|
||||||
|
except Exception as e:
|
||||||
|
text = f"❌ 定价数据: {e}"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
33
wwwroot/check_pricing_program.dspy
Normal file
33
wwwroot/check_pricing_program.dspy
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ 定价项目(pricing_program): 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
maps = await sor.sqlExe(
|
||||||
|
"select * from llm_api_map where llmid=${llmid}$", {'llmid': llmid})
|
||||||
|
ppids = [m.ppid for m in maps if m.ppid] if maps else []
|
||||||
|
|
||||||
|
if not ppids:
|
||||||
|
text = "❌ 定价项目(pricing_program): llm_api_map中无ppid"
|
||||||
|
else:
|
||||||
|
ppid = ppids[0]
|
||||||
|
async with get_sor_context(request._run_ns, 'pricing') as psor:
|
||||||
|
pregs = await psor.sqlExe(
|
||||||
|
"select * from pricing_program where id=${ppid}$", {'ppid': ppid})
|
||||||
|
if pregs:
|
||||||
|
p = pregs[0]
|
||||||
|
display_name = getattr(p, 'display_text', '') or getattr(p, 'name', '')
|
||||||
|
text = f"✅ 定价项目(pricing_program): {display_name}"
|
||||||
|
if hasattr(p, 'id'):
|
||||||
|
text += f" (id={p.id})"
|
||||||
|
else:
|
||||||
|
text = f"❌ 定价项目(pricing_program): ppid={ppid} 未找到"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
30
wwwroot/check_uapi.dspy
Normal file
30
wwwroot/check_uapi.dspy
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ API映射(uapi): 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
recs = await sor.sqlExe("""
|
||||||
|
select a.*, e.ioid, e.stream
|
||||||
|
from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join upapp c on a.upappid = c.id
|
||||||
|
join uapi e on c.id = e.upappid and m.apiname = e.name
|
||||||
|
where a.id=${llmid}$""", {'llmid': llmid})
|
||||||
|
|
||||||
|
if recs:
|
||||||
|
text = f"✅ API映射(uapi): ioid={recs[0].ioid}, stream={recs[0].stream}"
|
||||||
|
else:
|
||||||
|
# Get apiname from llm
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
llm_recs = await sor.sqlExe("select apiname from llm where id=${llmid}$", {'llmid': llmid})
|
||||||
|
apiname = llm_recs[0].apiname if llm_recs else 'N/A'
|
||||||
|
text = f"❌ API映射(uapi): apiname={apiname} 在upapp中未找到"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
33
wwwroot/check_uapiio.dspy
Normal file
33
wwwroot/check_uapiio.dspy
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ IO定义(uapiio): 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
# First get ioid from uapi
|
||||||
|
recs = await sor.sqlExe("""
|
||||||
|
select e.ioid
|
||||||
|
from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join upapp c on a.upappid = c.id
|
||||||
|
join uapi e on c.id = e.upappid and m.apiname = e.name
|
||||||
|
where a.id=${llmid}$""", {'llmid': llmid})
|
||||||
|
|
||||||
|
if not recs:
|
||||||
|
text = "❌ IO定义(uapiio): 依赖 uapi 未通过"
|
||||||
|
else:
|
||||||
|
ioid = recs[0].ioid
|
||||||
|
recs2 = await sor.sqlExe(
|
||||||
|
"select * from uapiio where id=${ioid}$", {'ioid': ioid})
|
||||||
|
if recs2:
|
||||||
|
text = f"✅ IO定义(uapiio): uapiio id={ioid}"
|
||||||
|
else:
|
||||||
|
text = f"❌ IO定义(uapiio): ioid={ioid} 未找到"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
27
wwwroot/check_upapp.dspy
Normal file
27
wwwroot/check_upapp.dspy
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
llmid = params_kw.get('llmid', '')
|
||||||
|
|
||||||
|
if not llmid:
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": "❌ 上位系统(upapp): 缺少llmid参数", "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"select a.* from llm a, upapp b where a.id=${llmid}$ and a.upappid=b.id",
|
||||||
|
{'llmid': llmid})
|
||||||
|
|
||||||
|
if recs:
|
||||||
|
llm = recs[0]
|
||||||
|
text = f"✅ 上位系统(upapp): upappid={llm.upappid}"
|
||||||
|
else:
|
||||||
|
# Get llm info to show upappid
|
||||||
|
llm_recs = await sor.sqlExe(
|
||||||
|
"select upappid from llm where id=${llmid}$", {'llmid': llmid})
|
||||||
|
upappid = llm_recs[0].upappid if llm_recs else '未知'
|
||||||
|
text = f"❌ 上位系统(upapp): upappid={upappid} 未找到关联"
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {"text": text, "i18n": False}
|
||||||
|
}, ensure_ascii=False)
|
||||||
@ -3,134 +3,170 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"padding": "16px",
|
"padding": "8px",
|
||||||
"spacing": 12
|
"gap": "8px"
|
||||||
},
|
},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "Title2",
|
"widgettype": "InlineForm",
|
||||||
|
"id": "filter_form",
|
||||||
"options": {
|
"options": {
|
||||||
"text": "记账失败记录",
|
"css": "card",
|
||||||
"halign": "left"
|
"padding": "8px",
|
||||||
}
|
"submit_label": "查询",
|
||||||
},
|
"submit_css": "primary",
|
||||||
{
|
"fields": [
|
||||||
"widgettype": "HBox",
|
{
|
||||||
"options": {
|
"name": "start_date",
|
||||||
"width": "100%",
|
"label": "开始日期",
|
||||||
"spacing": 12,
|
"uitype": "date",
|
||||||
"alignItems": "flex-end"
|
"cwidth": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "end_date",
|
||||||
|
"label": "结束日期",
|
||||||
|
"uitype": "date",
|
||||||
|
"cwidth": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled",
|
||||||
|
"label": "处理状态",
|
||||||
|
"uitype": "code",
|
||||||
|
"cwidth": 8,
|
||||||
|
"data": [
|
||||||
|
{"value": "", "text": "全部"},
|
||||||
|
{"value": "0", "text": "未处理"},
|
||||||
|
{"value": "1", "text": "已处理"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"subwidgets": [
|
"binds": [
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"wid": "self",
|
||||||
"options": {"spacing": 4},
|
"event": "submit",
|
||||||
"subwidgets": [
|
"actiontype": "script",
|
||||||
{"widgettype": "Text", "options": {"text": "开始日期", "fontSize": "12px"}},
|
"target": "failed_table",
|
||||||
{"widgettype": "UiDate", "id": "start_date", "options": {"width": "150px"}}
|
"script": "var tbl = bricks.getWidgetById('failed_table', bricks.app.root); if(tbl) await tbl.render(params);"
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"spacing": 4},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "结束日期", "fontSize": "12px"}},
|
|
||||||
{"widgettype": "UiDate", "id": "end_date", "options": {"width": "150px"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"spacing": 4},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "处理状态", "fontSize": "12px"}},
|
|
||||||
{
|
|
||||||
"widgettype": "Combobox",
|
|
||||||
"id": "handled_filter",
|
|
||||||
"options": {
|
|
||||||
"width": "120px",
|
|
||||||
"data": [
|
|
||||||
{"value": "", "text": "全部"},
|
|
||||||
{"value": "0", "text": "未处理"},
|
|
||||||
{"value": "1", "text": "已处理"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"spacing": 4},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "", "fontSize": "12px"}},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"id": "search_btn",
|
|
||||||
"options": {
|
|
||||||
"label": "查询",
|
|
||||||
"bgcolor": "#1976d2",
|
|
||||||
"color": "#ffffff",
|
|
||||||
"width": "80px"
|
|
||||||
},
|
|
||||||
"binds": [{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "failed_table",
|
|
||||||
"script": "var sd = this.root.getElementById('start_date'); var ed = this.root.getElementById('end_date'); var hf = this.root.getElementById('handled_filter'); var params = {handled: hf.value}; if(sd.value) params.start_date = sd.value; if(ed.value) params.end_date = ed.value; this.root.getElementById('failed_table').load(params);"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {"spacing": 4},
|
|
||||||
"subwidgets": [
|
|
||||||
{"widgettype": "Text", "options": {"text": "", "fontSize": "12px"}},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"id": "retry_btn",
|
|
||||||
"options": {
|
|
||||||
"label": "重试",
|
|
||||||
"bgcolor": "#4caf50",
|
|
||||||
"color": "#ffffff",
|
|
||||||
"width": "80px"
|
|
||||||
},
|
|
||||||
"binds": [{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"target": "self",
|
|
||||||
"script": "var dv = this.root.getElementById('failed_table'); var row = dv.selected_row || (dv.selected_rows && dv.selected_rows[0]); if(!row || !row.llmusageid) { alert('请先选中一条记录'); return; } var url = bricks.build_url ? bricks.build_url('/llmage/api/retry_accounting.dspy') : '/llmage/api/retry_accounting.dspy'; fetch(url + '?id=' + row.llmusageid).then(function(r){return r.json();}).then(function(d){ if(d.success) { alert(d.message); dv.load({}); } else { alert('失败: ' + d.message); } }).catch(function(e){ alert('请求异常: ' + e); });"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "DataViewer",
|
"widgettype": "Tabular",
|
||||||
"id": "failed_table",
|
"id": "failed_table",
|
||||||
"options": {
|
"options": {
|
||||||
"url": "{{entire_url('/llmage/api/failed_accounting_list.dspy')}}",
|
"width": "100%",
|
||||||
"title": "失败记录列表",
|
"height": "100%",
|
||||||
"pageSize": 20,
|
"css": "card",
|
||||||
"fields": [
|
"toolbar": {
|
||||||
{"name": "id", "title": "ID", "hidden": true},
|
"tools": [
|
||||||
{"name": "llmusageid", "title": "使用记录ID", "width": "120px"},
|
{
|
||||||
{"name": "llmid", "title": "模型ID", "width": "120px"},
|
"name": "show_reason",
|
||||||
{"name": "userid", "title": "用户ID", "width": "120px"},
|
"label": "原因",
|
||||||
{"name": "userorgid", "title": "机构ID", "width": "120px"},
|
"selected_row": true
|
||||||
{"name": "use_date", "title": "使用日期", "width": "110px"},
|
},
|
||||||
{"name": "use_time", "title": "使用时间", "width": "160px"},
|
{
|
||||||
{"name": "amount", "title": "金额", "width": "80px"},
|
"name": "retry_accounting",
|
||||||
{"name": "cost", "title": "成本", "width": "80px"},
|
"label": "重试记账",
|
||||||
{"name": "failed_reason", "title": "失败原因", "width": "30%"},
|
"selected_row": true
|
||||||
{"name": "failed_time", "title": "失败时间", "width": "160px"},
|
}
|
||||||
{"name": "retry_count", "title": "重试次数", "width": "80px"},
|
]
|
||||||
{"name": "handled", "title": "状态", "width": "80px",
|
},
|
||||||
"formatter": "function(v){return v==='1'?'已处理':'未处理';}"}
|
"data_url": "{{entire_url('/llmage/api/failed_accounting_list.dspy')}}",
|
||||||
]
|
"data_method": "GET",
|
||||||
}
|
"page_rows": 20,
|
||||||
|
"row_options": {
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id", "failed_reason"],
|
||||||
|
"alters": {
|
||||||
|
"handled": {
|
||||||
|
"uitype": "code",
|
||||||
|
"data": [
|
||||||
|
{"value": "0", "text": "未处理"},
|
||||||
|
{"value": "1", "text": "已处理"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"userid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "userid",
|
||||||
|
"textField": "userid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "users",
|
||||||
|
"tblvalue": "userid",
|
||||||
|
"tbltext": "username",
|
||||||
|
"valueField": "userid",
|
||||||
|
"textField": "userid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
},
|
||||||
|
"userorgid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "userorgid",
|
||||||
|
"textField": "userorgid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "organization",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "orgname",
|
||||||
|
"valueField": "userorgid",
|
||||||
|
"textField": "userorgid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
},
|
||||||
|
"llmid": {
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "llmid",
|
||||||
|
"textField": "llmid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "llmage",
|
||||||
|
"table": "llm",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "name",
|
||||||
|
"valueField": "llmid",
|
||||||
|
"textField": "llmid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
{"name": "llmusageid", "title": "使用记录ID", "type": "str", "length": 32, "cwidth": 12, "uitype": "str", "label": "使用记录ID"},
|
||||||
|
{"name": "llmid", "title": "模型", "type": "str", "length": 32, "cwidth": 12, "uitype": "str", "label": "模型"},
|
||||||
|
{"name": "userid", "title": "用户", "type": "str", "length": 32, "cwidth": 10, "uitype": "str", "label": "用户"},
|
||||||
|
{"name": "userorgid", "title": "机构", "type": "str", "length": 32, "cwidth": 10, "uitype": "str", "label": "机构"},
|
||||||
|
{"name": "use_time", "title": "使用时间", "type": "timestamp", "cwidth": 14, "uitype": "str", "label": "使用时间"},
|
||||||
|
{"name": "amount", "title": "金额", "type": "double", "length": 18, "dec": 5, "cwidth": 8, "uitype": "float", "label": "金额"},
|
||||||
|
{"name": "failed_reason", "title": "失败原因", "type": "text", "cwidth": 20, "uitype": "text", "label": "失败原因"},
|
||||||
|
{"name": "failed_time", "title": "失败时间", "type": "timestamp", "cwidth": 14, "uitype": "str", "label": "失败时间"},
|
||||||
|
{"name": "retry_count", "title": "重试", "type": "int", "cwidth": 4, "uitype": "int", "label": "重试"},
|
||||||
|
{"name": "handled", "title": "状态", "type": "str", "length": 1, "cwidth": 6, "uitype": "code", "label": "状态"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "show_reason",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "PopupWindow",
|
||||||
|
"popup_options": {
|
||||||
|
"title": "失败原因",
|
||||||
|
"cwidth": 30,
|
||||||
|
"cheight": 20
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('/llmage/api/show_failed_reason.dspy')}}?id=${id}$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "retry_accounting",
|
||||||
|
"actiontype": "script",
|
||||||
|
"target": "self",
|
||||||
|
"script": "var dv = bricks.getWidgetById('failed_table', bricks.app.root); if(!dv || !dv.select_row || !dv.select_row.user_data) { alert('请先选中一条记录'); return; } var row = dv.select_row.user_data; if(!row.llmusageid) { alert('记录缺少llmusageid'); return; } var resp = await fetch('{{entire_url('/llmage/api/retry_accounting.dspy')}}?id=' + row.llmusageid); var d = await resp.json(); if(d.success) { alert(d.message); await dv.render({}); } else { alert('失败: ' + d.message); }"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,23 @@
|
|||||||
userid = await get_user()
|
userid = await get_user()
|
||||||
|
llmcatelogid = params_kw.get('llmcatelogid', 't2t')
|
||||||
tasks = await get_today_asynctask_list(userid)
|
tasks = await get_today_asynctask_list(userid)
|
||||||
|
|
||||||
for t in tasks:
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
bin = await read_webpath(t.ioinfo)
|
for t in tasks:
|
||||||
t.ioinfo = json.loads(bin.decode('utf-8'))
|
bin = await read_webpath(t.ioinfo)
|
||||||
|
t.ioinfo = json.loads(bin.decode('utf-8'))
|
||||||
|
|
||||||
|
# 查询 llmcatelogid
|
||||||
|
catid = None
|
||||||
|
if hasattr(t, 'llmid') and t.llmid:
|
||||||
|
sql = '''select m.llmcatelogid from llm_api_map m where m.llmid = ${llmid}$ limit 1'''
|
||||||
|
recs = await sor.sqlExe(sql, {'llmid': t.llmid})
|
||||||
|
if recs:
|
||||||
|
catid = recs[0].llmcatelogid
|
||||||
|
t.llmcatelogid = catid
|
||||||
|
|
||||||
|
# 按 llmcatelogid 过滤
|
||||||
|
tasks = [t for t in tasks if t.llmcatelogid == llmcatelogid]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
|
|||||||
@ -1,32 +1,36 @@
|
|||||||
|
lt = params_kw.llmcatelogid or 't2v'
|
||||||
lt = '文生视频'
|
debug(f'{lt=}')
|
||||||
if params_kw.type in ['文生视频', '参考生视频', '图生视频']:
|
try:
|
||||||
lt = params_kw.type
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
sql = '''select distinct a.*, e.input_fields from llm a
|
||||||
sql = '''select distinct a.*, e.input_fields from llm a
|
join llm_api_map m on a.id = m.llmid
|
||||||
join llm_api_map m on a.id = m.llmid
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
join llmcatelog b on m.llmcatelogid = b.id
|
join uapi d on d.upappid = a.upappid and m.apiname = d.name
|
||||||
join upapp c on a.upappid = c.id
|
join uapiio e on d.ioid = e.id
|
||||||
join uapi d on c.apisetid = d.apisetid and a.apiname = d.name
|
where (b.id=${lt}$ OR b.name=${lt}$)
|
||||||
join uapiio e on d.ioid = e.id
|
and a.enabled_date <= ${biz_date}$
|
||||||
where b.name=${lt}$
|
and ${biz_date}$ < a.expired_date
|
||||||
and a.enabled_date <= ${biz_date}$
|
and a.status = 'published'
|
||||||
and ${biz_date}$ < a.expired_date
|
and m.ppid is not NULL'''
|
||||||
and ppid is not NULL'''
|
biz_date = await get_business_date(sor)
|
||||||
biz_date = await get_business_date(sor)
|
recs = await sor.sqlExe(sql, {
|
||||||
recs = await sor.sqlExe(sql, {
|
'biz_date': biz_date,
|
||||||
'biz_date': biz_date,
|
'lt': lt
|
||||||
'lt': lt
|
})
|
||||||
})
|
for r in recs:
|
||||||
for r in recs:
|
r.input_fields = json.loads(r.input_fields)
|
||||||
r.input_fields = json.loads(r.input_fields)
|
return {
|
||||||
|
'status': 'ok',
|
||||||
|
'data': recs
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'error',
|
||||||
'data': recs
|
'data':{
|
||||||
|
'message': 'server error'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
except Exception as e:
|
||||||
'status': 'error',
|
debug(f'{lt=},{e},{format_exc()}')
|
||||||
'data':{
|
|
||||||
'message': 'server error'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
295
wwwroot/index.ui
295
wwwroot/index.ui
@ -17,9 +17,7 @@
|
|||||||
{
|
{
|
||||||
"widgettype": "Title2",
|
"widgettype": "Title2",
|
||||||
"options": {
|
"options": {
|
||||||
"text": "LLM 模型管理",
|
"text": "LLM 模型管理"
|
||||||
"color": "#F1F5F9",
|
|
||||||
"fontWeight": "700"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -28,181 +26,176 @@
|
|||||||
{
|
{
|
||||||
"widgettype": "Text",
|
"widgettype": "Text",
|
||||||
"options": {
|
"options": {
|
||||||
"text": "模型配置、目录分类与调用监控",
|
"text": "模型类型、模型配置与记账失败记录",
|
||||||
"fontSize": "14px",
|
"cfontsize": 1.2
|
||||||
"color": "#64748B"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "ResponsableBox",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {
|
||||||
"gap": "16px",
|
"css": "filler",
|
||||||
"minWidth": "250px",
|
"spacing": 16
|
||||||
"marginBottom": "24px"
|
|
||||||
},
|
},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "ResponsableBox",
|
||||||
"options": {
|
"options": {
|
||||||
"bgcolor": "#1E293B",
|
"gap": "16px",
|
||||||
"padding": "24px",
|
"minWidth": "250px"
|
||||||
"borderRadius": "12px",
|
|
||||||
"border": "1px solid #334155",
|
|
||||||
"cursor": "pointer"
|
|
||||||
},
|
},
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.llmage_content",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/llmage/llmcatelog_list.ui')}}"
|
|
||||||
},
|
|
||||||
"mode": "replace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "Svg",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {
|
||||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#90caf9\" stroke-width=\"1.5\"><path d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"/></svg>",
|
"css": "card",
|
||||||
"width": "36px",
|
"cwidth": 23,
|
||||||
"height": "36px",
|
"padding": "16px",
|
||||||
"marginBottom": "16px"
|
"cursor": "pointer",
|
||||||
}
|
"borderRadius": "8px"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "app.llmage_content",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('/llmage/llmcatelog_list.ui')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Svg",
|
||||||
|
"options": {
|
||||||
|
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\"><path d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"/></svg>",
|
||||||
|
"width": "28px",
|
||||||
|
"height": "28px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Title4",
|
||||||
|
"options": {
|
||||||
|
"text": "模型类型管理",
|
||||||
|
"marginTop": "8px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "管理模型的分类和类型",
|
||||||
|
"cfontsize": 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "Title4",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {
|
||||||
"text": "模型类型管理",
|
"css": "card",
|
||||||
"color": "#F1F5F9",
|
"cwidth": 23,
|
||||||
"fontWeight": "600",
|
"padding": "16px",
|
||||||
"marginBottom": "8px"
|
"cursor": "pointer",
|
||||||
}
|
"borderRadius": "8px"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "app.llmage_content",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('/llmage/llm')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Svg",
|
||||||
|
"options": {
|
||||||
|
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22C55E\" stroke-width=\"2\"><path d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15.75c-2.062 0-4.024-.614-5.67-1.757l-1.57-.393m15.04 0L12 21 5.25 13.893\"/></svg>",
|
||||||
|
"width": "28px",
|
||||||
|
"height": "28px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Title4",
|
||||||
|
"options": {
|
||||||
|
"text": "模型管理",
|
||||||
|
"marginTop": "8px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "管理 LLM 模型配置",
|
||||||
|
"cfontsize": 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "Text",
|
"widgettype": "VBox",
|
||||||
"options": {
|
"options": {
|
||||||
"text": "管理模型的分类目录和类型定义",
|
"css": "card",
|
||||||
"fontSize": "14px",
|
"cwidth": 23,
|
||||||
"color": "#94A3B8"
|
"padding": "16px",
|
||||||
}
|
"cursor": "pointer",
|
||||||
|
"borderRadius": "8px"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target": "app.llmage_content",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('/llmage/failed_accounting.ui')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Svg",
|
||||||
|
"options": {
|
||||||
|
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#EF4444\" stroke-width=\"2\"><path d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"/></svg>",
|
||||||
|
"width": "28px",
|
||||||
|
"height": "28px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Title4",
|
||||||
|
"options": {
|
||||||
|
"text": "记账失败记录",
|
||||||
|
"marginTop": "8px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "查看和检索记账失败的记录",
|
||||||
|
"cfontsize": 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "VScrollPanel",
|
||||||
|
"id": "llmage_content",
|
||||||
"options": {
|
"options": {
|
||||||
"bgcolor": "#1E293B",
|
"css": "filler",
|
||||||
"padding": "24px",
|
"width": "100%",
|
||||||
"borderRadius": "12px",
|
"height": "100%"
|
||||||
"border": "1px solid #334155",
|
}
|
||||||
"cursor": "pointer"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.llmage_content",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/llmage/llm')}}"
|
|
||||||
},
|
|
||||||
"mode": "replace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Svg",
|
|
||||||
"options": {
|
|
||||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#4caf50\" stroke-width=\"1.5\"><path d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15.75c-2.062 0-4.024-.614-5.67-1.757l-1.57-.393m15.04 0L12 21 5.25 13.893\"/></svg>",
|
|
||||||
"width": "36px",
|
|
||||||
"height": "36px",
|
|
||||||
"marginBottom": "16px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Title4",
|
|
||||||
"options": {
|
|
||||||
"text": "模型配置",
|
|
||||||
"color": "#F1F5F9",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginBottom": "8px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "管理 LLM 模型的API配置与供应商映射",
|
|
||||||
"fontSize": "14px",
|
|
||||||
"color": "#94A3B8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"bgcolor": "#1E293B",
|
|
||||||
"padding": "24px",
|
|
||||||
"borderRadius": "12px",
|
|
||||||
"border": "1px solid #334155",
|
|
||||||
"cursor": "pointer"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.llmage_content",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/llmage/failed_accounting.ui')}}"
|
|
||||||
},
|
|
||||||
"mode": "replace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Svg",
|
|
||||||
"options": {
|
|
||||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#EF4444\" stroke-width=\"1.5\"><path d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"/></svg>",
|
|
||||||
"width": "36px",
|
|
||||||
"height": "36px",
|
|
||||||
"marginBottom": "16px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Title4",
|
|
||||||
"options": {
|
|
||||||
"text": "记账失败记录",
|
|
||||||
"color": "#F1F5F9",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginBottom": "8px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "查看和检索调用计费失败记录",
|
|
||||||
"fontSize": "14px",
|
|
||||||
"color": "#94A3B8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"id": "llmage_content",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"flex": "1",
|
|
||||||
"marginTop": "20px"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ db = DBPools()
|
|||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
sql = """select distinct a.* from llm a
|
sql = """select distinct a.* from llm a
|
||||||
join llm_api_map m on a.id = m.llmid
|
join llm_api_map m on a.id = m.llmid
|
||||||
where m.llmcatelogid = ${llmcatelogid}$ and a.id != ${llmid}$"""
|
where m.llmcatelogid = ${llmcatelogid}$ and a.id != ${llmid}$ and a.status = 'published'"""
|
||||||
ns = params_kw.copy()
|
ns = params_kw.copy()
|
||||||
recs = await sor.sqlExe(sql, ns)
|
recs = await sor.sqlExe(sql, ns)
|
||||||
for r in recs.get('rows', []):
|
for r in recs.get('rows', []):
|
||||||
|
|||||||
@ -6,6 +6,17 @@ page = int(params_kw.get('page', 1))
|
|||||||
dbname = get_module_dbname('llmage')
|
dbname = get_module_dbname('llmage')
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
# If llmcatelogid not provided, derive it from llmid via llm_api_map
|
||||||
|
llmcatelogid = params_kw.get('llmcatelogid')
|
||||||
|
if not llmcatelogid:
|
||||||
|
llmid = params_kw.get('llmid')
|
||||||
|
if llmid:
|
||||||
|
recs = await sor.sqlExe("select llmcatelogid from llm_api_map where llmid=${llmid}$ limit 1", {'llmid': llmid})
|
||||||
|
if recs:
|
||||||
|
llmcatelogid = recs[0].llmcatelogid
|
||||||
|
if not llmcatelogid:
|
||||||
|
return {}
|
||||||
|
params_kw.llmcatelogid = llmcatelogid
|
||||||
sql = """select x.*,
|
sql = """select x.*,
|
||||||
z.input_fields,
|
z.input_fields,
|
||||||
y.system_message,
|
y.system_message,
|
||||||
@ -17,18 +28,23 @@ from llm a
|
|||||||
join llm_api_map m on a.id = m.llmid
|
join llm_api_map m on a.id = m.llmid
|
||||||
join llmcatelog b on m.llmcatelogid = b.id
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
join upapp c on a.upappid = c.id
|
join upapp c on a.upappid = c.id
|
||||||
join uapi e on c.apisetid = e.apisetid and a.apiname = e.name
|
join uapi e on c.id = e.upappid and m.apiname = e.name
|
||||||
|
where a.status = 'published'
|
||||||
|
and m.llmcatelogid = ${llmcatelogid}$
|
||||||
) x left join historyformat y on x.hfid = y.id
|
) x left join historyformat y on x.hfid = y.id
|
||||||
left join uapiio z on x.ioid = z.id
|
left join uapiio z on x.ioid = z.id
|
||||||
where m.llmcatelogid = ${llmcatelogid}$
|
where 1=1
|
||||||
and x.id != ${llmid}$
|
|
||||||
"""
|
"""
|
||||||
|
llmid = params_kw.get('llmid')
|
||||||
|
if llmid:
|
||||||
|
sql += " and x.id != ${llmid}$"
|
||||||
ns = params_kw.copy()
|
ns = params_kw.copy()
|
||||||
ns.page = page
|
ns.page = page
|
||||||
ns.pagerows = pagerows
|
ns.pagerows = pagerows
|
||||||
recs = await sor.sqlPaging(sql, ns)
|
recs = await sor.sqlPaging(sql, ns)
|
||||||
for r in recs.get('rows', []):
|
for r in recs.get('rows', []):
|
||||||
r.llmid = r.id
|
r.llmid = r.id
|
||||||
|
r.llmcatelogid = llmcatelogid
|
||||||
r.modelname = r.name
|
r.modelname = r.name
|
||||||
r.description = ''.join(''.join(r.description.split('\n')).split('\r'))
|
r.description = ''.join(''.join(r.description.split('\n')).split('\r'))
|
||||||
r.response_mode = r.stream
|
r.response_mode = r.stream
|
||||||
|
|||||||
@ -53,23 +53,22 @@
|
|||||||
"name": "llmcatelogid",
|
"name": "llmcatelogid",
|
||||||
"label": "选择分类",
|
"label": "选择分类",
|
||||||
"uitype": "code",
|
"uitype": "code",
|
||||||
"dataurl": "{{entire_url('./api/llm_api_map_options.dspy')}}",
|
"dataurl": "{{entire_url('./api/get_catelogs.dspy')}}",
|
||||||
"data_field": "catelogs",
|
|
||||||
"placeholder": "请选择分类"
|
"placeholder": "请选择分类"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "apiname",
|
"name": "apiname",
|
||||||
"label": "API接口",
|
"label": "API接口",
|
||||||
"uitype": "code",
|
"uitype": "code",
|
||||||
"dataurl": "{{entire_url('./api/llm_api_map_options.dspy')}}",
|
"dataurl": "{{entire_url('./api/get_search_apiname.dspy')}}",
|
||||||
"data_field": "apis",
|
|
||||||
"placeholder": "请选择API接口"
|
"placeholder": "请选择API接口"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "query_apiname",
|
"name": "query_apiname",
|
||||||
"label": "查询API",
|
"label": "查询API",
|
||||||
"uitype": "str",
|
"uitype": "code",
|
||||||
"placeholder": "异步查询API名,多个用逗号分隔"
|
"dataurl": "{{entire_url('./api/get_search_apiname.dspy?allow_empty=1')}}",
|
||||||
|
"placeholder": "不指定或选择查询API"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "query_period",
|
"name": "query_period",
|
||||||
@ -81,8 +80,7 @@
|
|||||||
"name": "ppid",
|
"name": "ppid",
|
||||||
"label": "计费项目",
|
"label": "计费项目",
|
||||||
"uitype": "code",
|
"uitype": "code",
|
||||||
"dataurl": "{{entire_url('./api/llm_api_map_options.dspy')}}",
|
"dataurl": "{{entire_url('./api/get_ppids.dspy')}}",
|
||||||
"data_field": "ppids",
|
|
||||||
"placeholder": "请选择计费项目"
|
"placeholder": "请选择计费项目"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -100,7 +98,11 @@
|
|||||||
"event": "click",
|
"event": "click",
|
||||||
"actiontype": "urlwidget",
|
"actiontype": "urlwidget",
|
||||||
"target": "PopupWindow",
|
"target": "PopupWindow",
|
||||||
"popup_options": {"archor": "cc", "width": "30%", "height": "20%"},
|
"popup_options": {
|
||||||
|
"archor": "cc",
|
||||||
|
"width": "30%",
|
||||||
|
"height": "20%"
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"url": "{{entire_url('./api/llm_api_map_create.dspy')}}",
|
"url": "{{entire_url('./api/llm_api_map_create.dspy')}}",
|
||||||
"params": {
|
"params": {
|
||||||
@ -124,7 +126,8 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"width": "calc(100% - 40px)",
|
"width": "calc(100% - 40px)",
|
||||||
"margin": "0 20px",
|
"margin": "0 20px",
|
||||||
"spacing": 12
|
"spacing": 12,
|
||||||
|
"cheight": 30
|
||||||
},
|
},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
@ -146,19 +149,46 @@
|
|||||||
"page_rows": 20,
|
"page_rows": 20,
|
||||||
"row_options": {
|
"row_options": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "llm_name", "title": "模型名称", "width": 180},
|
{
|
||||||
{"name": "catelog_name", "title": "分类", "width": 120},
|
"name": "llm_name",
|
||||||
{"name": "apiname", "title": "API接口", "width": 150},
|
"title": "模型名称",
|
||||||
{"name": "query_apiname", "title": "查询API", "width": 180},
|
"width": 180
|
||||||
{"name": "query_period", "title": "间隔(秒)", "width": 80},
|
},
|
||||||
{"name": "ppid_name", "title": "计费项目", "width": 150},
|
{
|
||||||
|
"name": "catelog_name",
|
||||||
|
"title": "分类",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiname",
|
||||||
|
"title": "API接口",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "query_apiname",
|
||||||
|
"title": "查询API",
|
||||||
|
"width": 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "query_period",
|
||||||
|
"title": "间隔(秒)",
|
||||||
|
"width": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ppid_name",
|
||||||
|
"title": "计费项目",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "actions",
|
"name": "actions",
|
||||||
"title": "操作",
|
"title": "操作",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
"uitype": "button",
|
"uitype": "button",
|
||||||
"data": [
|
"data": [
|
||||||
{"text": "删除", "event": "delete_map"}
|
{
|
||||||
|
"text": "删除",
|
||||||
|
"event": "delete_map"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -170,7 +200,11 @@
|
|||||||
"event": "delete_map",
|
"event": "delete_map",
|
||||||
"actiontype": "urlwidget",
|
"actiontype": "urlwidget",
|
||||||
"target": "PopupWindow",
|
"target": "PopupWindow",
|
||||||
"popup_options": {"archor": "cc", "width": "30%", "height": "20%"},
|
"popup_options": {
|
||||||
|
"archor": "cc",
|
||||||
|
"width": "30%",
|
||||||
|
"height": "20%"
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"url": "{{entire_url('./api/llm_api_map_delete.dspy')}}",
|
"url": "{{entire_url('./api/llm_api_map_delete.dspy')}}",
|
||||||
"params": {
|
"params": {
|
||||||
@ -183,4 +217,4 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -3,10 +3,82 @@
|
|||||||
{% set userorgid = get_userorgid() %}
|
{% set userorgid = get_userorgid() %}
|
||||||
{% if params_kw.id %}
|
{% if params_kw.id %}
|
||||||
{% if checkCustomerBalance(params_kw.id, userid, userorgid) %}
|
{% if checkCustomerBalance(params_kw.id, userid, userorgid) %}
|
||||||
{% set llm = get_llm(params_kw.id) %}
|
{% set catelogs = get_llm_catelogs(params_kw.id) %}
|
||||||
{% set oops=debug(json.dumps(llm, ensure_ascii=Fasle)) %}
|
{% set ns = namespace(active_catelogid=params_kw.get('catelogid', '')) %}
|
||||||
|
{% if not ns.active_catelogid %}
|
||||||
|
{% for c in catelogs %}{% if c.isdefault and not ns.active_catelogid %}{% set ns.active_catelogid = c.catelogid %}{% endif %}{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if not ns.active_catelogid and catelogs %}{% set ns.active_catelogid = catelogs[0].catelogid %}{% endif %}
|
||||||
|
{% set llm = get_llm(params_kw.id, ns.active_catelogid) %}
|
||||||
{% set kdbs = get_user_kdbs(request) %}
|
{% set kdbs = get_user_kdbs(request) %}
|
||||||
{% if llm %}
|
{% if llm %}
|
||||||
|
{% if len(catelogs) > 1 %}
|
||||||
|
{
|
||||||
|
"widgettype":"VBox",
|
||||||
|
"options":{
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"HBox",
|
||||||
|
"options":{
|
||||||
|
"cheight":3,
|
||||||
|
"css":"card",
|
||||||
|
"padding":"8px"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{% for c in catelogs %}
|
||||||
|
{
|
||||||
|
"widgettype":"Button",
|
||||||
|
"options":{
|
||||||
|
"label":"{{c.catelogname}}",
|
||||||
|
"actiontype":"link",
|
||||||
|
{% if c.catelogid == ns.active_catelogid %}
|
||||||
|
"css":"primary",
|
||||||
|
{% endif %}
|
||||||
|
"url":"{{entire_url('/llmage/llm_dialog.ui')}}?id={{params_kw.id}}&catelogid={{c.catelogid}}"
|
||||||
|
}
|
||||||
|
}{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"LlmIO",
|
||||||
|
"options":{
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%",
|
||||||
|
"title":"{{llm.name}}",
|
||||||
|
{% if len(kdbs) > 0 %}
|
||||||
|
"enabled_kdb": true,
|
||||||
|
"kdb_setting":{},
|
||||||
|
"get_kdb_url": "{{entire_url('/rag/get_my_kdbs.dspy')}}",
|
||||||
|
{% endif %}
|
||||||
|
"list_models_url":"{{entire_url('list_paging_catelog_llms.dspy')}}?llmcatelogid={{ns.active_catelogid}}",
|
||||||
|
"estimate_url":"{{entire_url('model_estimate.dspy')}}",
|
||||||
|
"input_fields":{{llm.input_fields}},
|
||||||
|
"models":[
|
||||||
|
{
|
||||||
|
"llmid":"{{llm.id}}",
|
||||||
|
"llmcatelogid":"{{ns.active_catelogid}}",
|
||||||
|
"response_mode": "{{llm.stream}}",
|
||||||
|
"icon":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}",
|
||||||
|
"url":"{{entire_url('/llmage/llminference.dspy')}}",
|
||||||
|
{% if llm.stream == 'stream' %}
|
||||||
|
"stream": true,
|
||||||
|
{% endif %}
|
||||||
|
{% if llm.stream =='async' %}
|
||||||
|
"query_url": "{{entire_url('/llmage/tasks')}}",
|
||||||
|
{% endif %}
|
||||||
|
"model":"{{llm.model}}",
|
||||||
|
"modelname":"{{llm.name}}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{% else %}
|
||||||
{
|
{
|
||||||
"widgettype":"LlmIO",
|
"widgettype":"LlmIO",
|
||||||
"options":{
|
"options":{
|
||||||
@ -18,12 +90,15 @@
|
|||||||
"kdb_setting":{},
|
"kdb_setting":{},
|
||||||
"get_kdb_url": "{{entire_url('/rag/get_my_kdbs.dspy')}}",
|
"get_kdb_url": "{{entire_url('/rag/get_my_kdbs.dspy')}}",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"list_models_url":"{{entire_url('list_paging_catelog_llms.dspy')}}",
|
"list_models_url":"{{entire_url('list_paging_catelog_llms.dspy')}}{% if ns.active_catelogid %}?llmcatelogid={{ns.active_catelogid}}{% endif %}",
|
||||||
"estimate_url":"{{entire_url('model_estimate.dspy')}}",
|
"estimate_url":"{{entire_url('model_estimate.dspy')}}",
|
||||||
"input_fields":{{llm.input_fields}},
|
"input_fields":{{llm.input_fields}},
|
||||||
"models":[
|
"models":[
|
||||||
{
|
{
|
||||||
"llmid":"{{llm.id}}",
|
"llmid":"{{llm.id}}",
|
||||||
|
{% if ns.active_catelogid %}
|
||||||
|
"llmcatelogid":"{{ns.active_catelogid}}",
|
||||||
|
{% endif %}
|
||||||
"response_mode": "{{llm.stream}}",
|
"response_mode": "{{llm.stream}}",
|
||||||
"icon":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}",
|
"icon":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}",
|
||||||
"url":"{{entire_url('/llmage/llminference.dspy')}}",
|
"url":"{{entire_url('/llmage/llminference.dspy')}}",
|
||||||
@ -39,6 +114,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{
|
{
|
||||||
"widgettype":"Text",
|
"widgettype":"Text",
|
||||||
|
|||||||
156
wwwroot/llm_launch_check.ui
Normal file
156
wwwroot/llm_launch_check.ui
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
{% if params_kw.id %}
|
||||||
|
{% set llmid = params_kw.id %}
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
"spacing": 10
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Title",
|
||||||
|
"options": {
|
||||||
|
"text": "模型上线检查",
|
||||||
|
"level": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VScrollPanel",
|
||||||
|
"id": "check_scroll",
|
||||||
|
"options": {
|
||||||
|
"css": "filler",
|
||||||
|
"width": "100%"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"id": "checks_list",
|
||||||
|
"options": {
|
||||||
|
"spacing": 5
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_model_record.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_date_status.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_upapp.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_uapi.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_uapiio.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_llm_api_map.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_pricing_program.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/check_pricing_data.dspy?llmid={{llmid}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"id": "test_result",
|
||||||
|
"options": {
|
||||||
|
"text": "",
|
||||||
|
"i18n": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"id": "charge_result",
|
||||||
|
"options": {
|
||||||
|
"text": "",
|
||||||
|
"i18n": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"spacing": 10,
|
||||||
|
"alignItems": "center"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"id": "test_btn",
|
||||||
|
"options": {
|
||||||
|
"label": "体验一次",
|
||||||
|
"i18n": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"id": "charge_btn",
|
||||||
|
"options": {
|
||||||
|
"label": "检查计费",
|
||||||
|
"i18n": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "test_btn",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urldata",
|
||||||
|
"target": "test_result",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/api/llm_launch_check_api.dspy?llmid={{llmid}}&action=inference"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wid": "charge_btn",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urldata",
|
||||||
|
"target": "charge_result",
|
||||||
|
"options": {
|
||||||
|
"url": "/llmage/api/llm_launch_check_api.dspy?llmid={{llmid}}&action=check_charging&usages={\"prompt_tokens\":1000,\"completion_tokens\":500}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{% else %}
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "缺少模型ID参数",
|
||||||
|
"i18n": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
@ -2,7 +2,7 @@ llmid = params_kw.llmid
|
|||||||
today= params_kw.today
|
today= params_kw.today
|
||||||
msgs = []
|
msgs = []
|
||||||
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
async with get_sor_context(request._run_ns, 'llmage') as sor:
|
||||||
sql = "select * from llm where id=${llmid}$ and enabled_date <= ${today}$ and expired_date > ${today}$"
|
sql = "select * from llm where id=${llmid}$ and enabled_date <= ${today}$ and expired_date > ${today}$ and status = 'published'"
|
||||||
ns = {'llmid': llmid, 'today': today}
|
ns = {'llmid': llmid, 'today': today}
|
||||||
recs = await sor.sqlExe(sql, ns.copy())
|
recs = await sor.sqlExe(sql, ns.copy())
|
||||||
if recs:
|
if recs:
|
||||||
@ -24,8 +24,9 @@ where a.id=${llmid}$
|
|||||||
|
|
||||||
sql = """select a.*, e.ioid, e.stream
|
sql = """select a.*, e.ioid, e.stream
|
||||||
from llm a
|
from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
join upapp c on a.upappid = c.id
|
join upapp c on a.upappid = c.id
|
||||||
join uapi e on c.apisetid = e.apisetid and a.apiname = e.name
|
join uapi e on c.id = e.upappid and m.apiname = e.name
|
||||||
where a.id=${llmid}$
|
where a.id=${llmid}$
|
||||||
and a.expired_date > ${today}$
|
and a.expired_date > ${today}$
|
||||||
and a.enabled_date <= ${today}$"""
|
and a.enabled_date <= ${today}$"""
|
||||||
@ -38,8 +39,9 @@ where a.id=${llmid}$
|
|||||||
|
|
||||||
sql = """select a.*, e.ioid, e.stream
|
sql = """select a.*, e.ioid, e.stream
|
||||||
from llm a
|
from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
join upapp c on a.upappid = c.id
|
join upapp c on a.upappid = c.id
|
||||||
join uapi e on c.apisetid = e.apisetid and a.apiname = e.name
|
join uapi e on c.id = e.upappid and m.apiname = e.name
|
||||||
join uapiio b on e.ioid = b.id
|
join uapiio b on e.ioid = b.id
|
||||||
where a.id=${llmid}$
|
where a.id=${llmid}$
|
||||||
and a.expired_date > ${today}$
|
and a.expired_date > ${today}$
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
debug(f'{params_kw=}')
|
debug_params('params_kw', params_kw)
|
||||||
ns = params_kw.copy()
|
ns = params_kw.copy()
|
||||||
if not ns.page:
|
if not ns.page:
|
||||||
ns.page = 1
|
ns.page = 1
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
debug(f'{params_kw=}')
|
debug_params('params_kw', params_kw)
|
||||||
if params_kw.off_peak:
|
if params_kw.off_peak:
|
||||||
off_peak = params_kw.off_peak
|
off_peak = params_kw.off_peak
|
||||||
if off_peak in [True, "Y" "y", 1, "1"]:
|
if off_peak in [True, "Y" "y", 1, "1"]:
|
||||||
@ -11,6 +11,8 @@ userorgid = await get_userorgid()
|
|||||||
if userid is None:
|
if userid is None:
|
||||||
return UiError(title='llm inference', message='Please login first')
|
return UiError(title='llm inference', message='Please login first')
|
||||||
f = await checkCustomerBalance(params_kw.llmid, userid, userorgid)
|
f = await checkCustomerBalance(params_kw.llmid, userid, userorgid)
|
||||||
|
if not f:
|
||||||
|
return UiError(title='llm inference', message='余额不足或模型未配置定价')
|
||||||
kdbids = params_kw.kdbids
|
kdbids = params_kw.kdbids
|
||||||
if kdbids:
|
if kdbids:
|
||||||
data = {
|
data = {
|
||||||
@ -25,7 +27,7 @@ if kdbids:
|
|||||||
ret = await rfexe('fusedsearch', request, params)
|
ret = await rfexe('fusedsearch', request, params)
|
||||||
data.update(ret)
|
data.update(ret)
|
||||||
params_kw.prompt = await tmpl_engine.renders(tmpl, data)
|
params_kw.prompt = await tmpl_engine.renders(tmpl, data)
|
||||||
debug(f'{params=}rag return {data}, {params_kw.prompt=}')
|
debug_params('rag', {'query': params.get('query',''), 'prompt_len': len(str(params_kw.prompt))})
|
||||||
|
|
||||||
env = DictObject(**globals())
|
env = DictObject(**globals())
|
||||||
return await inference(request, env=env)
|
return await inference(request, env=env)
|
||||||
|
|||||||
@ -0,0 +1,161 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
|
||||||
|
|
||||||
|
debug(f'get_llmusage_accounting_failed.dspy:{ns=}')
|
||||||
|
if not ns.get('page'):
|
||||||
|
ns['page'] = 1
|
||||||
|
if not ns.get('sort'):
|
||||||
|
|
||||||
|
|
||||||
|
ns['sort'] = 'failed_time desc'
|
||||||
|
|
||||||
|
# InlineForm filter conditions
|
||||||
|
extra_conds = []
|
||||||
|
if ns.get('handled') and ns['handled'] != '':
|
||||||
|
extra_conds.append("handled = ${filter_handled}$")
|
||||||
|
ns['filter_handled'] = ns['handled']
|
||||||
|
if ns.get('filter_llmid') and ns['filter_llmid'] != '':
|
||||||
|
extra_conds.append("llmid like ${filter_llmid}$")
|
||||||
|
ns['filter_llmid'] = f"%{ns['filter_llmid']}%"
|
||||||
|
if ns.get('filter_userid') and ns['filter_userid'] != '':
|
||||||
|
extra_conds.append("userid like ${filter_userid}$")
|
||||||
|
ns['filter_userid'] = f"%{ns['filter_userid']}%"
|
||||||
|
if ns.get('use_date') and ns['use_date'] != '':
|
||||||
|
extra_conds.append("use_date = ${filter_use_date}$")
|
||||||
|
ns['filter_use_date'] = ns['use_date']
|
||||||
|
if ns.get('failed_reason') and ns['failed_reason'] != '':
|
||||||
|
extra_conds.append("failed_reason like ${filter_failed_reason}$")
|
||||||
|
ns['filter_failed_reason'] = f"%{ns['failed_reason']}%"
|
||||||
|
|
||||||
|
extra_filterstr = ''
|
||||||
|
if extra_conds:
|
||||||
|
extra_filterstr = ' and ' + ' and '.join(extra_conds)
|
||||||
|
|
||||||
|
sql = f'''select * from llmusage_accounting_failed where 1=1 {extra_filterstr} [[filterstr]]'''
|
||||||
|
|
||||||
|
filterjson = params_kw.get('data_filter')
|
||||||
|
if filterjson and isinstance(filterjson, str):
|
||||||
|
try:
|
||||||
|
filterjson = json.loads(filterjson)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
filterjson = None
|
||||||
|
fields_str=r'''[
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "llmusageid",
|
||||||
|
"title": "使用记录id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "llmid",
|
||||||
|
"title": "模型id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userid",
|
||||||
|
"title": "用户id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userorgid",
|
||||||
|
"title": "用户机构id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "use_date",
|
||||||
|
"title": "使用日期",
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "use_time",
|
||||||
|
"title": "使用时间",
|
||||||
|
"type": "timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amount",
|
||||||
|
"title": "交易金额",
|
||||||
|
"type": "double",
|
||||||
|
"length": 18,
|
||||||
|
"dec": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cost",
|
||||||
|
"title": "交易成本",
|
||||||
|
"type": "double",
|
||||||
|
"length": 18,
|
||||||
|
"dec": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "failed_reason",
|
||||||
|
"title": "失败原因",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "failed_time",
|
||||||
|
"title": "失败时间",
|
||||||
|
"type": "timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "retry_count",
|
||||||
|
"title": "重试次数",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled",
|
||||||
|
"title": "是否已处理",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1,
|
||||||
|
"default": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled_time",
|
||||||
|
"title": "处理时间",
|
||||||
|
"type": "timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled_note",
|
||||||
|
"title": "处理备注",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]'''
|
||||||
|
ori_fields = json.loads(fields_str)
|
||||||
|
if not filterjson:
|
||||||
|
fields = [ f['name'] for f in ori_fields ]
|
||||||
|
filterjson = default_filterjson(fields, ns)
|
||||||
|
filterdic = ns.copy()
|
||||||
|
filterdic['filterstr'] = ''
|
||||||
|
filterdic['userorgid'] = '${userorgid}$'
|
||||||
|
filterdic['userid'] = '${userid}$'
|
||||||
|
if filterjson:
|
||||||
|
dbf = DBFilter(filterjson)
|
||||||
|
conds = dbf.gen(ns)
|
||||||
|
if conds:
|
||||||
|
ns.update(dbf.consts)
|
||||||
|
conds = f' and {conds}'
|
||||||
|
filterdic['filterstr'] = conds
|
||||||
|
ac = ArgsConvert('[[', ']]')
|
||||||
|
vars = ac.findAllVariables(sql)
|
||||||
|
NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' }
|
||||||
|
filterdic.update(NameSpace)
|
||||||
|
sql = ac.convert(sql, filterdic)
|
||||||
|
|
||||||
|
debug(f'{sql=}')
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.sqlPaging(sql, ns)
|
||||||
|
return r
|
||||||
|
return {
|
||||||
|
"total":0,
|
||||||
|
"rows":[]
|
||||||
|
}
|
||||||
307
wwwroot/llmusage_accounting_failed/index.ui
Normal file
307
wwwroot/llmusage_accounting_failed/index.ui
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"height": "100%",
|
||||||
|
"width": "100%",
|
||||||
|
"padding": "8px",
|
||||||
|
"gap": "8px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "InlineForm",
|
||||||
|
"id": "filter_form",
|
||||||
|
"options": {
|
||||||
|
"css": "card",
|
||||||
|
"padding": "8px",
|
||||||
|
"submit_label": "查询",
|
||||||
|
"submit_css": "primary",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "handled",
|
||||||
|
"label": "处理状态",
|
||||||
|
"uitype": "code",
|
||||||
|
"cwidth": 10,
|
||||||
|
"codes": [
|
||||||
|
{"value": "", "text": "全部"},
|
||||||
|
{"value": "0", "text": "未处理"},
|
||||||
|
{"value": "1", "text": "已处理"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filter_llmid",
|
||||||
|
"label": "模型",
|
||||||
|
"uitype": "str",
|
||||||
|
"placeholder": "模型ID或名称",
|
||||||
|
"cwidth": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filter_userid",
|
||||||
|
"label": "用户",
|
||||||
|
"uitype": "str",
|
||||||
|
"placeholder": "用户ID",
|
||||||
|
"cwidth": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "failed_reason",
|
||||||
|
"label": "失败原因",
|
||||||
|
"uitype": "str",
|
||||||
|
"placeholder": "关键词",
|
||||||
|
"cwidth": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "submit",
|
||||||
|
"actiontype": "method",
|
||||||
|
"target": "llmusage_accounting_failed_tbl",
|
||||||
|
"method": "render"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"css": "card", "padding": "4px 8px", "cheight": 3
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"id": "btn_recover_usages",
|
||||||
|
"options": {
|
||||||
|
"label": "从IO文件恢复Usages",
|
||||||
|
"css": "primary"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "urldata",
|
||||||
|
"target": "msg_area",
|
||||||
|
"options": {
|
||||||
|
"url": "{{entire_url('./recover_usages.dspy')}}"
|
||||||
|
},
|
||||||
|
"mode": "replace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "method",
|
||||||
|
"target": "llmusage_accounting_failed_tbl",
|
||||||
|
"method": "render"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"id": "msg_area",
|
||||||
|
"options": {
|
||||||
|
"width": "100%", "css": "filler"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "llmusage_accounting_failed_tbl",
|
||||||
|
"widgettype": "Tabular",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
"title": "记账失败记录",
|
||||||
|
"css": "card",
|
||||||
|
"editable": {
|
||||||
|
"new_data_url": "{{entire_url('add_llmusage_accounting_failed.dspy')}}",
|
||||||
|
"delete_data_url": "{{entire_url('delete_llmusage_accounting_failed.dspy')}}",
|
||||||
|
"update_data_url": "{{entire_url('update_llmusage_accounting_failed.dspy')}}"
|
||||||
|
},
|
||||||
|
"data_url": "{{entire_url('./get_llmusage_accounting_failed.dspy')}}",
|
||||||
|
"data_method": "GET",
|
||||||
|
"data_params": {{json.dumps(params_kw, indent=4, ensure_ascii=False)}},
|
||||||
|
"row_options": {
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"alters": {
|
||||||
|
"handled": {
|
||||||
|
"uitype": "code",
|
||||||
|
"data": [
|
||||||
|
{"value": "0", "text": "未处理"},
|
||||||
|
{"value": "1", "text": "已处理"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editexclouded": ["id", "llmusageid", "failed_time"],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "llmusageid",
|
||||||
|
"title": "使用记录id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "使用记录id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "llmid",
|
||||||
|
"title": "模型",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "llmid",
|
||||||
|
"textField": "llmid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "llmage",
|
||||||
|
"table": "llm",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "name",
|
||||||
|
"valueField": "llmid",
|
||||||
|
"textField": "llmid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "模型"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userid",
|
||||||
|
"title": "用户",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "userid",
|
||||||
|
"textField": "userid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "users",
|
||||||
|
"tblvalue": "userid",
|
||||||
|
"tbltext": "username",
|
||||||
|
"valueField": "userid",
|
||||||
|
"textField": "userid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "用户"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userorgid",
|
||||||
|
"title": "机构",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "userorgid",
|
||||||
|
"textField": "userorgid_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "sage",
|
||||||
|
"table": "organization",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "orgname",
|
||||||
|
"valueField": "userorgid",
|
||||||
|
"textField": "userorgid_text"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "机构"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "use_time",
|
||||||
|
"title": "使用时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "使用时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amount",
|
||||||
|
"title": "交易金额",
|
||||||
|
"type": "double",
|
||||||
|
"length": 18,
|
||||||
|
"dec": 5,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "float",
|
||||||
|
"datatype": "double",
|
||||||
|
"label": "交易金额"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "failed_reason",
|
||||||
|
"title": "失败原因",
|
||||||
|
"type": "text",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "text",
|
||||||
|
"datatype": "text",
|
||||||
|
"label": "失败原因"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "failed_time",
|
||||||
|
"title": "失败时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "失败时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "retry_count",
|
||||||
|
"title": "重试次数",
|
||||||
|
"type": "int",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "int",
|
||||||
|
"datatype": "int",
|
||||||
|
"label": "重试次数"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled",
|
||||||
|
"title": "是否已处理",
|
||||||
|
"type": "str",
|
||||||
|
"length": 1,
|
||||||
|
"default": "0",
|
||||||
|
"cwidth": 4,
|
||||||
|
"uitype": "code",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "是否已处理",
|
||||||
|
"data": [
|
||||||
|
{"value": "0", "text": "未处理"},
|
||||||
|
{"value": "1", "text": "已处理"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled_time",
|
||||||
|
"title": "处理时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "处理时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "handled_note",
|
||||||
|
"title": "处理备注",
|
||||||
|
"type": "text",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "text",
|
||||||
|
"datatype": "text",
|
||||||
|
"label": "处理备注"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page_rows": 160,
|
||||||
|
"cache_limit": 5
|
||||||
|
},
|
||||||
|
"binds": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
136
wwwroot/llmusage_accounting_failed/recover_usages.dspy
Normal file
136
wwwroot/llmusage_accounting_failed/recover_usages.dspy
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
limit = int(ns.get('limit') or 200)
|
||||||
|
single_id = ns.get('id') or None
|
||||||
|
|
||||||
|
from ahserver.filestorage import FileStorage
|
||||||
|
import os
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
|
||||||
|
recovered = 0
|
||||||
|
failed = 0
|
||||||
|
skipped = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
if single_id:
|
||||||
|
sql = """select a.id, a.llmid, a.ioinfo, a.status, b.model
|
||||||
|
from llmusage a
|
||||||
|
left join llm b on a.llmid = b.id
|
||||||
|
where a.id = ${id}$"""
|
||||||
|
params = {'id': single_id}
|
||||||
|
else:
|
||||||
|
sql = """select a.id, a.llmid, a.ioinfo, a.status, b.model
|
||||||
|
from llmusage a
|
||||||
|
left join llm b on a.llmid = b.id
|
||||||
|
where a.usages is null
|
||||||
|
and a.status = 'SUCCEEDED'
|
||||||
|
order by a.use_date desc"""
|
||||||
|
params = {'page': 1, 'rows': limit}
|
||||||
|
|
||||||
|
recs = await sor.sqlExe(sql, params)
|
||||||
|
if isinstance(recs, dict):
|
||||||
|
rows = recs.get('rows', [])
|
||||||
|
else:
|
||||||
|
rows = recs if recs else []
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
return {
|
||||||
|
"widgettype": "Message",
|
||||||
|
"options": {
|
||||||
|
"title": "恢复Usages",
|
||||||
|
"cwidth": 20,
|
||||||
|
"cheight": 5,
|
||||||
|
"timeout": 5,
|
||||||
|
"message": "没有找到需要恢复的记录"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs = FileStorage()
|
||||||
|
|
||||||
|
for r in rows:
|
||||||
|
rid = r.id if hasattr(r, 'id') else r.get('id', '')
|
||||||
|
model = r.model if hasattr(r, 'model') else r.get('model', '')
|
||||||
|
ioinfo = r.ioinfo if hasattr(r, 'ioinfo') else r.get('ioinfo', None)
|
||||||
|
|
||||||
|
if not ioinfo:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
# ioinfo 可能是 JSON 内容,也可能是文件路径
|
||||||
|
io_data = None
|
||||||
|
if ioinfo.startswith('{') or ioinfo.startswith('"'):
|
||||||
|
# 直接是 JSON 内容
|
||||||
|
io_data = json.loads(ioinfo)
|
||||||
|
else:
|
||||||
|
# 文件路径
|
||||||
|
real_path = fs.realPath(ioinfo)
|
||||||
|
if not os.path.isfile(real_path):
|
||||||
|
errors.append(f'{rid}: 文件不存在')
|
||||||
|
failed += 1
|
||||||
|
continue
|
||||||
|
with open(real_path, 'r', encoding='utf-8') as f:
|
||||||
|
io_data = json.load(f)
|
||||||
|
|
||||||
|
outputs = io_data.get('output', [])
|
||||||
|
if not outputs:
|
||||||
|
errors.append(f'{rid}: output为空')
|
||||||
|
failed += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 从最后一条output开始倒序找usage
|
||||||
|
usage = None
|
||||||
|
for out in reversed(outputs):
|
||||||
|
if isinstance(out, dict) and out.get('usage'):
|
||||||
|
usage = out['usage']
|
||||||
|
break
|
||||||
|
|
||||||
|
if not usage:
|
||||||
|
errors.append(f'{rid}: output中未找到usage')
|
||||||
|
failed += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
usages_str = json.dumps(usage, ensure_ascii=False)
|
||||||
|
await sor.U('llmusage', {
|
||||||
|
'id': rid,
|
||||||
|
'usages': usages_str
|
||||||
|
})
|
||||||
|
recovered += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'recover_usages error for {rid}: {e}')
|
||||||
|
errors.append(f'{rid}: {e}')
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
exception(f'recover_usages error: {e}')
|
||||||
|
return {
|
||||||
|
"widgettype": "Error",
|
||||||
|
"options": {
|
||||||
|
"title": "恢复Usages失败",
|
||||||
|
"cwidth": 20,
|
||||||
|
"cheight": 5,
|
||||||
|
"timeout": 5,
|
||||||
|
"message": str(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total = recovered + failed + skipped
|
||||||
|
msg = f"处理 {total} 条: 恢复成功 {recovered}, 失败 {failed}, 跳过 {skipped}"
|
||||||
|
if errors:
|
||||||
|
msg += f"\n失败详情(前5条): {'; '.join(errors[:5])}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype": "Message",
|
||||||
|
"options": {
|
||||||
|
"title": "恢复Usages完成",
|
||||||
|
"cwidth": 30,
|
||||||
|
"cheight": 6,
|
||||||
|
"timeout": 8,
|
||||||
|
"message": msg
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
debug(f'model_estimate.dspy:{params_kw=}')
|
debug_params('model_estimate', params_kw)
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
dbname = get_module_dbname('llmage')
|
dbname = get_module_dbname('llmage')
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
|||||||
128
wwwroot/model_plaza.css
Normal file
128
wwwroot/model_plaza.css
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/* Model Plaza — 模型广场 */
|
||||||
|
|
||||||
|
/* Hide provider view initially */
|
||||||
|
#plaza_view_provider {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaza-header {
|
||||||
|
padding: 8px 16px 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View switcher buttons */
|
||||||
|
.plaza-view-switcher {
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaza-view-btn {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaza-view-btn.plaza-view-active {
|
||||||
|
border: 2px solid var(--sage-brand, #6366f1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left sidebar */
|
||||||
|
.plaza-sidebar {
|
||||||
|
border-right: 1px solid var(--sage-border, #e2e8f0);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaza-nav-btn {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
text-align: left;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaza-nav-btn:hover {
|
||||||
|
background-color: var(--sage-hover, rgba(99, 102, 241, 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode overrides for sidebar */
|
||||||
|
[data-theme="dark"] .plaza-sidebar {
|
||||||
|
border-right: 1px solid #334155;
|
||||||
|
background-color: #1E293B;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .plaza-nav-btn {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #CBD5E1;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .plaza-nav-btn:hover {
|
||||||
|
background-color: #334155;
|
||||||
|
color: #F1F5F9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card hover effects for model cards */
|
||||||
|
.plaza-card {
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaza-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category section headers */
|
||||||
|
.plaza-section-title {
|
||||||
|
padding: 12px 0 4px 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaza-section-title::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 4px;
|
||||||
|
height: 70%;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--sage-brand, #6366f1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth card grid */
|
||||||
|
.plaza-grid {
|
||||||
|
gap: 12px !important;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Model icon area */
|
||||||
|
.plaza-card .model-icon-row {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Description text */
|
||||||
|
.plaza-card .model-desc {
|
||||||
|
line-height: 1.5;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pricing display area */
|
||||||
|
.pricing-box {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid var(--sage-border, #e2e8f0);
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-text {
|
||||||
|
font-size: 0.85em;
|
||||||
|
line-height: 1.4;
|
||||||
|
opacity: 0.75;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .pricing-box {
|
||||||
|
border-top-color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .pricing-text {
|
||||||
|
color: #94A3B8;
|
||||||
|
}
|
||||||
122
wwwroot/model_plaza.ui
Normal file
122
wwwroot/model_plaza.ui
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{% set catelogs = get_llmcatelogs() %}
|
||||||
|
{% set providers = get_llms_sort_by_provider() %}
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"css": "filler",
|
||||||
|
"width": "100%"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"css": "plaza-header",
|
||||||
|
"width": "100%"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Title2",
|
||||||
|
"options": {
|
||||||
|
"otext": "模型广场",
|
||||||
|
"i18n": true,
|
||||||
|
"halign": "left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"otext": "探索和使用各类AI模型",
|
||||||
|
"i18n": true,
|
||||||
|
"halign": "left",
|
||||||
|
"wrap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"css": "plaza-view-switcher",
|
||||||
|
"cheight": 3
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"id": "btn_by_catelog",
|
||||||
|
"options": {
|
||||||
|
"label": "按分类",
|
||||||
|
"css": "plaza-view-btn plaza-view-active",
|
||||||
|
"cwidth": 15
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "script",
|
||||||
|
"target": "self",
|
||||||
|
"script": "document.getElementById('plaza_view_provider').style.display='none'; document.getElementById('plaza_view_catelog').style.display='flex'; var a=bricks.getWidgetById('btn_by_catelog',bricks.app); var b=bricks.getWidgetById('btn_by_provider',bricks.app); if(a)a.dom_element.classList.add('plaza-view-active'); if(b)b.dom_element.classList.remove('plaza-view-active');"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"id": "btn_by_provider",
|
||||||
|
"options": {
|
||||||
|
"label": "按供应商",
|
||||||
|
"css": "plaza-view-btn",
|
||||||
|
"cwidth": 15
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "script",
|
||||||
|
"target": "self",
|
||||||
|
"script": "document.getElementById('plaza_view_catelog').style.display='none'; document.getElementById('plaza_view_provider').style.display='flex'; var a=bricks.getWidgetById('btn_by_catelog',bricks.app); var b=bricks.getWidgetById('btn_by_provider',bricks.app); if(a)a.dom_element.classList.remove('plaza-view-active'); if(b)b.dom_element.classList.add('plaza-view-active');"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"id": "plaza_view_catelog",
|
||||||
|
"options": {
|
||||||
|
"css": "filler",
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"css": "filler",
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
"url": "{{entire_url('show_llms.ui')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"id": "plaza_view_provider",
|
||||||
|
"options": {
|
||||||
|
"css": "filler",
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "urlwidget",
|
||||||
|
"options": {
|
||||||
|
"css": "filler",
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
"url": "{{entire_url('show_llms_by_providers.ui')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,106 +1,84 @@
|
|||||||
{% set userorgid = get_userorgid() %}
|
{% set catelogs = get_llmcatelogs() %}
|
||||||
{
|
{
|
||||||
"widgettype":"VScrollPanel",
|
"widgettype":"HBox",
|
||||||
"options":{
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
"width":"100%",
|
"width":"100%",
|
||||||
"height":"100%"
|
"height":"100%"
|
||||||
},
|
},
|
||||||
"subwidgets":[
|
"subwidgets":[
|
||||||
{% for cate in get_llms_by_catelog() %}
|
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype":"VScrollPanel",
|
||||||
"options":{
|
"options":{
|
||||||
"width":"100%"
|
"cwidth":18,
|
||||||
|
"height":"100%",
|
||||||
|
"css":"plaza-sidebar"
|
||||||
},
|
},
|
||||||
"subwidgets":[
|
"subwidgets":[
|
||||||
{
|
{
|
||||||
"widgettype":"Title3",
|
"widgettype":"Button",
|
||||||
"options":{
|
"options":{
|
||||||
"wrap":true,
|
"label":"全部",
|
||||||
"halign": "left",
|
"css":"plaza-nav-btn",
|
||||||
"i18n": true,
|
|
||||||
"otext":"{{cate.catelogname}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype":"DynamicColumn",
|
|
||||||
"options":{
|
|
||||||
"css":"filler",
|
|
||||||
"width":"100%"
|
"width":"100%"
|
||||||
},
|
},
|
||||||
"subwidgets":[
|
"binds":[
|
||||||
{% for llm in cate.llms %}
|
{
|
||||||
{
|
"wid":"self",
|
||||||
"widgettype":"VScrollPanel",
|
"event":"click",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"app.plaza_cards_panel",
|
||||||
|
"mode":"replace",
|
||||||
"options":{
|
"options":{
|
||||||
"css":"card",
|
"url":"{{entire_url('show_llms_cards.ui')}}"
|
||||||
"bgcolor": "#def0f0",
|
}
|
||||||
"cwidth":20,
|
|
||||||
"cheight":12
|
|
||||||
},
|
|
||||||
"subwidgets":[
|
|
||||||
{
|
|
||||||
"widgettype":"HBox",
|
|
||||||
"options":{
|
|
||||||
"cheight":2
|
|
||||||
},
|
|
||||||
"subwidgets":[
|
|
||||||
{
|
|
||||||
"widgettype":"Svg",
|
|
||||||
"options":{
|
|
||||||
"rate":1.5,
|
|
||||||
"url":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype":"Title6",
|
|
||||||
"options":{
|
|
||||||
"text":"{{llm.name}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype":"Text",
|
|
||||||
"options":{
|
|
||||||
"text":{{json.dumps(llm.description, ensure_ascii=False)}},
|
|
||||||
"wrap":true,
|
|
||||||
"halign":"left"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"binds":[
|
|
||||||
{
|
|
||||||
"wid":"self",
|
|
||||||
"event":"click",
|
|
||||||
"actiontype":"urlwidget",
|
|
||||||
"target":"PopupWindow",
|
|
||||||
"popup_options":{
|
|
||||||
"title":"{{llm.name}}",
|
|
||||||
{% if int(params_kw._is_mobile) %}
|
|
||||||
"width": "100%",
|
|
||||||
"height": "100%"
|
|
||||||
{% else %}
|
|
||||||
"width": "40%",
|
|
||||||
"height":"85%"
|
|
||||||
{% endif %}
|
|
||||||
},
|
|
||||||
"options":{
|
|
||||||
"params":{
|
|
||||||
"id":"{{llm.id}}"
|
|
||||||
},
|
|
||||||
"url":"{{entire_url('./llm_dialog.ui')}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
{% if not loop.last %}, {% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
]
|
]
|
||||||
|
}{% for cat in catelogs %},
|
||||||
|
{
|
||||||
|
"widgettype":"Button",
|
||||||
|
"options":{
|
||||||
|
"label":"{{cat.name}}",
|
||||||
|
"css":"plaza-nav-btn",
|
||||||
|
"width":"100%"
|
||||||
|
},
|
||||||
|
"binds":[
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"click",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"app.plaza_cards_panel",
|
||||||
|
"mode":"replace",
|
||||||
|
"options":{
|
||||||
|
"url":"{{entire_url('show_llms_cards.ui')}}",
|
||||||
|
"params":{
|
||||||
|
"catelogid":"{{cat.id}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}{% endfor %}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"VBox",
|
||||||
|
"id":"plaza_cards_panel",
|
||||||
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
|
"cwidth":82,
|
||||||
|
"height":"100%"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"urlwidget",
|
||||||
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%",
|
||||||
|
"url":"{{entire_url('show_llms_cards.ui')}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{% if not loop.last %}, {% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,105 +1,84 @@
|
|||||||
{% set userorgid = get_userorgid() %}
|
{% set providers = get_llms_sort_by_provider() %}
|
||||||
{
|
{
|
||||||
"widgettype":"VScrollPanel",
|
"widgettype":"HBox",
|
||||||
"options":{
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
"width":"100%",
|
"width":"100%",
|
||||||
"height":"100%"
|
"height":"100%"
|
||||||
},
|
},
|
||||||
"subwidgets":[
|
"subwidgets":[
|
||||||
{% for p in get_llms_sort_by_provider() %}
|
|
||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype":"VScrollPanel",
|
||||||
"options":{
|
"options":{
|
||||||
"width":"100%"
|
"cwidth":18,
|
||||||
|
"height":"100%",
|
||||||
|
"css":"plaza-sidebar"
|
||||||
},
|
},
|
||||||
"subwidgets":[
|
"subwidgets":[
|
||||||
{
|
{
|
||||||
"widgettype":"Title3",
|
"widgettype":"Button",
|
||||||
"options":{
|
"options":{
|
||||||
"wrap":true,
|
"label":"全部",
|
||||||
"halign": "left",
|
"css":"plaza-nav-btn",
|
||||||
"text":"{{p.orgname}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype":"DynamicColumn",
|
|
||||||
"options":{
|
|
||||||
"css":"filler",
|
|
||||||
"width":"100%"
|
"width":"100%"
|
||||||
},
|
},
|
||||||
"subwidgets":[
|
"binds":[
|
||||||
{% for llm in p.llms %}
|
{
|
||||||
{
|
"wid":"self",
|
||||||
"widgettype":"VScrollPanel",
|
"event":"click",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"app.plaza_provider_panel",
|
||||||
|
"mode":"replace",
|
||||||
"options":{
|
"options":{
|
||||||
"css":"card",
|
"url":"{{entire_url('show_llms_cards_by_provider.ui')}}"
|
||||||
"bgcolor": "#def0f0",
|
}
|
||||||
"cwidth":20,
|
|
||||||
"cheight":12
|
|
||||||
},
|
|
||||||
"subwidgets":[
|
|
||||||
{
|
|
||||||
"widgettype":"HBox",
|
|
||||||
"options":{
|
|
||||||
"cheight":2
|
|
||||||
},
|
|
||||||
"subwidgets":[
|
|
||||||
{
|
|
||||||
"widgettype":"Svg",
|
|
||||||
"options":{
|
|
||||||
"rate":1.5,
|
|
||||||
"url":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype":"Title6",
|
|
||||||
"options":{
|
|
||||||
"text":"{{llm.name}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype":"Text",
|
|
||||||
"options":{
|
|
||||||
"text":{{json.dumps(llm.description, ensure_ascii=False)}},
|
|
||||||
"wrap":true,
|
|
||||||
"halign":"left"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"binds":[
|
|
||||||
{
|
|
||||||
"wid":"self",
|
|
||||||
"event":"click",
|
|
||||||
"actiontype":"urlwidget",
|
|
||||||
"target":"PopupWindow",
|
|
||||||
"popup_options":{
|
|
||||||
"title":"{{llm.name}}",
|
|
||||||
{% if int(params_kw._is_mobile) %}
|
|
||||||
"width": "100%",
|
|
||||||
"height": "100%"
|
|
||||||
{% else %}
|
|
||||||
"width": "40%",
|
|
||||||
"height":"85%"
|
|
||||||
{% endif %}
|
|
||||||
},
|
|
||||||
"options":{
|
|
||||||
"params":{
|
|
||||||
"id":"{{llm.id}}"
|
|
||||||
},
|
|
||||||
"url":"{{entire_url('./llm_dialog.ui')}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
{% if not loop.last %}, {% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
]
|
]
|
||||||
|
}{% for p in providers %},
|
||||||
|
{
|
||||||
|
"widgettype":"Button",
|
||||||
|
"options":{
|
||||||
|
"label":"{{p.orgname}}",
|
||||||
|
"css":"plaza-nav-btn",
|
||||||
|
"width":"100%"
|
||||||
|
},
|
||||||
|
"binds":[
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"click",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"app.plaza_provider_panel",
|
||||||
|
"mode":"replace",
|
||||||
|
"options":{
|
||||||
|
"url":"{{entire_url('show_llms_cards_by_provider.ui')}}",
|
||||||
|
"params":{
|
||||||
|
"providerid":"{{p.id}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}{% endfor %}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"VBox",
|
||||||
|
"id":"plaza_provider_panel",
|
||||||
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
|
"cwidth":82,
|
||||||
|
"height":"100%"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"urlwidget",
|
||||||
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%",
|
||||||
|
"url":"{{entire_url('show_llms_cards_by_provider.ui')}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{% if not loop.last %}, {% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
112
wwwroot/show_llms_cards.ui
Normal file
112
wwwroot/show_llms_cards.ui
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{% set catelogid = params_kw.get('catelogid', None) %}
|
||||||
|
{% set data = get_llms_by_catelog(catelogid=catelogid) %}
|
||||||
|
{% set ns = namespace(first=true) %}
|
||||||
|
{
|
||||||
|
"widgettype":"VScrollPanel",
|
||||||
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"DynamicColumn",
|
||||||
|
"options":{
|
||||||
|
"css":"plaza-grid",
|
||||||
|
"width":"100%",
|
||||||
|
"col_cwidth":25,
|
||||||
|
"col_cgap":1
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{% for cate in data %}
|
||||||
|
{% for llm in cate.llms %}
|
||||||
|
{% if not ns.first %},{% endif %}
|
||||||
|
{
|
||||||
|
"widgettype":"VScrollPanel",
|
||||||
|
"options":{
|
||||||
|
"css":"card plaza-card",
|
||||||
|
"cwidth":25,
|
||||||
|
"cheight":16
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"HBox",
|
||||||
|
"options":{
|
||||||
|
"cheight":2
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"Svg",
|
||||||
|
"options":{
|
||||||
|
"rate":1.5,
|
||||||
|
"url":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"Title6",
|
||||||
|
"options":{
|
||||||
|
"text":"{{llm.name}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"Text",
|
||||||
|
"options":{
|
||||||
|
"text":{{json.dumps(llm.description, ensure_ascii=False)}},
|
||||||
|
"wrap":true,
|
||||||
|
"halign":"left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"Filler",
|
||||||
|
"options":{
|
||||||
|
"css":"pricing-box"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{% for pricing_text in llm.pricing_display %}
|
||||||
|
{
|
||||||
|
"widgettype":"Text",
|
||||||
|
"options":{
|
||||||
|
"text":{{json.dumps(pricing_text, ensure_ascii=False)}},
|
||||||
|
"wrap":true,
|
||||||
|
"halign":"left",
|
||||||
|
"css":"pricing-text"
|
||||||
|
}
|
||||||
|
}{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"binds":[
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"click",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"PopupWindow",
|
||||||
|
"popup_options":{
|
||||||
|
"title":"{{llm.name}}",
|
||||||
|
{% if int(params_kw._is_mobile) %}
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%"
|
||||||
|
{% else %}
|
||||||
|
"width": "40%",
|
||||||
|
"height":"85%"
|
||||||
|
{% endif %}
|
||||||
|
},
|
||||||
|
"options":{
|
||||||
|
"params":{
|
||||||
|
"id":"{{llm.id}}"
|
||||||
|
},
|
||||||
|
"url":"{{entire_url('./llm_dialog.ui')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{% set ns.first = false %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
112
wwwroot/show_llms_cards_by_provider.ui
Normal file
112
wwwroot/show_llms_cards_by_provider.ui
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{% set providerid = params_kw.get('providerid', '') %}
|
||||||
|
{% set data = get_llms_sort_by_provider() %}
|
||||||
|
{% set ns = namespace(first=true) %}
|
||||||
|
{
|
||||||
|
"widgettype":"VScrollPanel",
|
||||||
|
"options":{
|
||||||
|
"css":"filler",
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"DynamicColumn",
|
||||||
|
"options":{
|
||||||
|
"css":"plaza-grid",
|
||||||
|
"width":"100%"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{% for p in data %}
|
||||||
|
{% if not providerid or p.id|string == providerid|string %}
|
||||||
|
{% for llm in p.llms %}
|
||||||
|
{% if not ns.first %},{% endif %}
|
||||||
|
{
|
||||||
|
"widgettype":"VScrollPanel",
|
||||||
|
"options":{
|
||||||
|
"css":"card plaza-card",
|
||||||
|
"cwidth":25,
|
||||||
|
"cheight":16
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"HBox",
|
||||||
|
"options":{
|
||||||
|
"cheight":2
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{
|
||||||
|
"widgettype":"Svg",
|
||||||
|
"options":{
|
||||||
|
"rate":1.5,
|
||||||
|
"url":"{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"Title6",
|
||||||
|
"options":{
|
||||||
|
"text":"{{llm.name}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"Text",
|
||||||
|
"options":{
|
||||||
|
"text":{{json.dumps(llm.description or '', ensure_ascii=False)}},
|
||||||
|
"wrap":true,
|
||||||
|
"halign":"left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype":"Filler",
|
||||||
|
"options":{
|
||||||
|
"css":"pricing-box"
|
||||||
|
},
|
||||||
|
"subwidgets":[
|
||||||
|
{% for pricing_text in llm.pricing_display %}
|
||||||
|
{
|
||||||
|
"widgettype":"Text",
|
||||||
|
"options":{
|
||||||
|
"text":{{json.dumps(pricing_text, ensure_ascii=False)}},
|
||||||
|
"wrap":true,
|
||||||
|
"halign":"left",
|
||||||
|
"css":"pricing-text"
|
||||||
|
}
|
||||||
|
}{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"binds":[
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"click",
|
||||||
|
"actiontype":"urlwidget",
|
||||||
|
"target":"PopupWindow",
|
||||||
|
"popup_options":{
|
||||||
|
"title":"{{llm.name}}",
|
||||||
|
{% if int(params_kw._is_mobile) %}
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%"
|
||||||
|
{% else %}
|
||||||
|
"width": "40%",
|
||||||
|
"height":"85%"
|
||||||
|
{% endif %}
|
||||||
|
},
|
||||||
|
"options":{
|
||||||
|
"params":{
|
||||||
|
"id":"{{llm.id}}"
|
||||||
|
},
|
||||||
|
"url":"{{entire_url('./llm_dialog.ui')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{% set ns.first = false %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -10,7 +10,6 @@
|
|||||||
"widgettype":"VScrollPanel",
|
"widgettype":"VScrollPanel",
|
||||||
"options":{
|
"options":{
|
||||||
"css":"card",
|
"css":"card",
|
||||||
"bgcolor": "#def0f0",
|
|
||||||
"cwidth":20,
|
"cwidth":20,
|
||||||
"cheight":12
|
"cheight":12
|
||||||
},
|
},
|
||||||
|
|||||||
49
wwwroot/stat_catelog_count.ui
Normal file
49
wwwroot/stat_catelog_count.ui
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{% set stats = get_llmage_stats(request) %}
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"padding": "20px",
|
||||||
|
"borderRadius": "12px",
|
||||||
|
"flex": "1",
|
||||||
|
"minHeight": "110px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"alignItems": "center",
|
||||||
|
"marginBottom": "12px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Svg",
|
||||||
|
"options": {
|
||||||
|
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#8B5CF6\" stroke-width=\"2\"><path d=\"M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z\"/></svg>",
|
||||||
|
"width": "24px",
|
||||||
|
"height": "24px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Filler"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "{{stats.catelog_count}}",
|
||||||
|
"fontSize": "32px",
|
||||||
|
"fontWeight": "700",
|
||||||
|
"lineHeight": "1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "模型分类",
|
||||||
|
"fontSize": "14px",
|
||||||
|
"marginTop": "4px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
49
wwwroot/stat_today_amount.ui
Normal file
49
wwwroot/stat_today_amount.ui
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{% set stats = get_llmage_stats(request) %}
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"padding": "20px",
|
||||||
|
"borderRadius": "12px",
|
||||||
|
"flex": "1",
|
||||||
|
"minHeight": "110px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"alignItems": "center",
|
||||||
|
"marginBottom": "12px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Svg",
|
||||||
|
"options": {
|
||||||
|
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F59E0B\" stroke-width=\"2\"><path d=\"M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>",
|
||||||
|
"width": "24px",
|
||||||
|
"height": "24px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Filler"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "¥{{'%.2f' % stats.today_amount}}",
|
||||||
|
"fontSize": "32px",
|
||||||
|
"fontWeight": "700",
|
||||||
|
"lineHeight": "1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "今日消费",
|
||||||
|
"fontSize": "14px",
|
||||||
|
"marginTop": "4px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
49
wwwroot/stat_today_calls.ui
Normal file
49
wwwroot/stat_today_calls.ui
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{% set stats = get_llmage_stats(request) %}
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"padding": "20px",
|
||||||
|
"borderRadius": "12px",
|
||||||
|
"flex": "1",
|
||||||
|
"minHeight": "110px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"alignItems": "center",
|
||||||
|
"marginBottom": "12px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Svg",
|
||||||
|
"options": {
|
||||||
|
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22C55E\" stroke-width=\"2\"><path d=\"M7.5 21L3 16.5m0 0L7.5 12M12 9v7.5m0 0l4.5-4.5M12 9l4.5 4.5m0 0L12 16.5\"/><path d=\"M21 12h-4.5M12 3v4.5m0 0L7.5 12\"/></svg>",
|
||||||
|
"width": "24px",
|
||||||
|
"height": "24px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Filler"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "{{stats.today_usage_count}}",
|
||||||
|
"fontSize": "32px",
|
||||||
|
"fontWeight": "700",
|
||||||
|
"lineHeight": "1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "今日调用",
|
||||||
|
"fontSize": "14px",
|
||||||
|
"marginTop": "4px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
49
wwwroot/stat_total_models.ui
Normal file
49
wwwroot/stat_total_models.ui
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{% set stats = get_llmage_stats(request) %}
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {
|
||||||
|
"padding": "20px",
|
||||||
|
"borderRadius": "12px",
|
||||||
|
"flex": "1",
|
||||||
|
"minHeight": "110px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"options": {
|
||||||
|
"alignItems": "center",
|
||||||
|
"marginBottom": "12px"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Svg",
|
||||||
|
"options": {
|
||||||
|
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\"><path d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z\"/></svg>",
|
||||||
|
"width": "24px",
|
||||||
|
"height": "24px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Filler"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "{{stats.total_models}}",
|
||||||
|
"fontSize": "32px",
|
||||||
|
"fontWeight": "700",
|
||||||
|
"lineHeight": "1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"text": "可用模型数",
|
||||||
|
"fontSize": "14px",
|
||||||
|
"marginTop": "4px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
debug(f'{params_kw=}')
|
debug_params('params_kw', params_kw)
|
||||||
lctype='文生文'
|
lctype='t2t'
|
||||||
if params_kw.off_peak:
|
if params_kw.off_peak:
|
||||||
off_peak = params_kw.off_peak
|
off_peak = params_kw.off_peak
|
||||||
if off_peak in [True, "Y" "y", 1, "1"]:
|
if off_peak in [True, "Y" "y", 1, "1"]:
|
||||||
@ -20,8 +20,9 @@ async with get_sor_context(env, 'llmage') as sor:
|
|||||||
sql = """select distinct a.* from llm a
|
sql = """select distinct a.* from llm a
|
||||||
join llm_api_map m on a.id = m.llmid
|
join llm_api_map m on a.id = m.llmid
|
||||||
join llmcatelog b on m.llmcatelogid = b.id
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
where b.name = ${lctype}$
|
where (b.id = ${lctype}$ OR b.name = ${lctype}$)
|
||||||
and a.model=${model}$"""
|
and a.model=${model}$
|
||||||
|
and a.status = 'published'"""
|
||||||
recs = await sor.sqlExe(sql, {
|
recs = await sor.sqlExe(sql, {
|
||||||
'lctype': lctype,
|
'lctype': lctype,
|
||||||
'model': params_kw.model or 'qwen3-max'
|
'model': params_kw.model or 'qwen3-max'
|
||||||
|
|||||||
74
wwwroot/v1/audio/speech/index.dspy
Normal file
74
wwwroot/v1/audio/speech/index.dspy
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# OpenAI-compatible Text-to-Speech API
|
||||||
|
# POST /v1/audio/speech
|
||||||
|
# Required params: model, catelogid, prompt (text to synthesize)
|
||||||
|
# Optional params: speaker (voice_id), speed, emotion
|
||||||
|
#
|
||||||
|
# Example request:
|
||||||
|
# {
|
||||||
|
# "model": "speech-2.6-turbo",
|
||||||
|
# "catelogid": "tts",
|
||||||
|
# "prompt": "你好,欢迎使用语音合成服务",
|
||||||
|
# "speaker": "female-tianmei",
|
||||||
|
# "speed": 1.0,
|
||||||
|
# "emotion": "happy"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Response (stream, hex audio chunks):
|
||||||
|
# {
|
||||||
|
# "status": "SUCCEEDED",
|
||||||
|
# "audio": "base64_encoded_audio_data"
|
||||||
|
# }
|
||||||
|
|
||||||
|
userid = await get_user()
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if userid is None:
|
||||||
|
debug('need login')
|
||||||
|
return openai_403()
|
||||||
|
|
||||||
|
# Validate required parameters
|
||||||
|
if not params_kw.model:
|
||||||
|
d = return_error('Missing required parameter: model')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.catelogid:
|
||||||
|
d = return_error('Missing required parameter: catelogid')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.prompt:
|
||||||
|
d = return_error('Missing required parameter: prompt (text to synthesize)')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
lctype = params_kw.catelogid
|
||||||
|
|
||||||
|
env = request._run_ns
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
# Look up llm by model name and catalog type through llm_api_map
|
||||||
|
sql = """select distinct a.* from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
|
where (b.id = ${lctype}$ OR b.name = ${lctype}$)
|
||||||
|
and a.model=${model}$
|
||||||
|
and a.status = 'published'"""
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
'lctype': lctype,
|
||||||
|
'model': params_kw.model
|
||||||
|
})
|
||||||
|
if len(recs) == 0:
|
||||||
|
debug(f'{params_kw.model=} not found for catalog {lctype}')
|
||||||
|
return openai_400()
|
||||||
|
params_kw.llmid = recs[0].id
|
||||||
|
|
||||||
|
debug(f'{params_kw.llmid=}')
|
||||||
|
|
||||||
|
# Check balance
|
||||||
|
f = await checkCustomerBalance(params_kw.llmid, userid, userorgid)
|
||||||
|
if not f:
|
||||||
|
debug(f'{userid=} balance not enough')
|
||||||
|
return openai_429()
|
||||||
|
|
||||||
|
# Generate task ID and attach to params
|
||||||
|
if not params_kw.transno:
|
||||||
|
params_kw.transno = getID()
|
||||||
|
|
||||||
|
# Call inference (TTS can be stream or sync depending on model)
|
||||||
|
return await inference(request, env=env)
|
||||||
71
wwwroot/v1/audio/transcriptions/index.dspy
Normal file
71
wwwroot/v1/audio/transcriptions/index.dspy
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# OpenAI-compatible Audio Transcription API (ASR)
|
||||||
|
# POST /v1/audio/transcriptions
|
||||||
|
# Required params: model, catelogid, audio_file (audio URL or base64)
|
||||||
|
# Optional params: language
|
||||||
|
#
|
||||||
|
# Example request:
|
||||||
|
# {
|
||||||
|
# "model": "qwen3-asr-flash",
|
||||||
|
# "catelogid": "asr",
|
||||||
|
# "audio_file": "https://example.com/audio.wav"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Response:
|
||||||
|
# {
|
||||||
|
# "text": "识别出的文本内容",
|
||||||
|
# "usage": { "duration_seconds": 5.2 }
|
||||||
|
# }
|
||||||
|
|
||||||
|
userid = await get_user()
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if userid is None:
|
||||||
|
debug('need login')
|
||||||
|
return openai_403()
|
||||||
|
|
||||||
|
# Validate required parameters
|
||||||
|
if not params_kw.model:
|
||||||
|
d = return_error('Missing required parameter: model')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.catelogid:
|
||||||
|
d = return_error('Missing required parameter: catelogid')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.audio_file:
|
||||||
|
d = return_error('Missing required parameter: audio_file')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
lctype = params_kw.catelogid
|
||||||
|
|
||||||
|
env = request._run_ns
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
# Look up llm by model name and catalog type through llm_api_map
|
||||||
|
sql = """select distinct a.* from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
|
where (b.id = ${lctype}$ OR b.name = ${lctype}$)
|
||||||
|
and a.model=${model}$
|
||||||
|
and a.status = 'published'"""
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
'lctype': lctype,
|
||||||
|
'model': params_kw.model
|
||||||
|
})
|
||||||
|
if len(recs) == 0:
|
||||||
|
debug(f'{params_kw.model=} not found for catalog {lctype}')
|
||||||
|
return openai_400()
|
||||||
|
params_kw.llmid = recs[0].id
|
||||||
|
|
||||||
|
debug(f'{params_kw.llmid=}')
|
||||||
|
|
||||||
|
# Check balance
|
||||||
|
f = await checkCustomerBalance(params_kw.llmid, userid, userorgid)
|
||||||
|
if not f:
|
||||||
|
debug(f'{userid=} balance not enough')
|
||||||
|
return openai_429()
|
||||||
|
|
||||||
|
# Generate task ID and attach to params
|
||||||
|
if not params_kw.transno:
|
||||||
|
params_kw.transno = getID()
|
||||||
|
|
||||||
|
# Call inference (ASR is synchronous)
|
||||||
|
return await inference(request, env=env)
|
||||||
@ -9,8 +9,8 @@ async def gen():
|
|||||||
async for l in f():
|
async for l in f():
|
||||||
yield l
|
yield l
|
||||||
|
|
||||||
debug(f'{params_kw=}')
|
debug_params('params_kw', params_kw)
|
||||||
lctype='文生文'
|
catelogid = params_kw.catelogid or 't2t'
|
||||||
if params_kw.off_peak:
|
if params_kw.off_peak:
|
||||||
off_peak = params_kw.off_peak
|
off_peak = params_kw.off_peak
|
||||||
if off_peak in [True, "Y" "y", 1, "1"]:
|
if off_peak in [True, "Y" "y", 1, "1"]:
|
||||||
@ -25,7 +25,7 @@ if userid is None:
|
|||||||
return openai_403()
|
return openai_403()
|
||||||
|
|
||||||
if not params_kw.prompt and not params_kw.messages:
|
if not params_kw.prompt and not params_kw.messages:
|
||||||
debug(f'not params_kw.prompt and not params_kw.messages,{params_kw=}')
|
debug(f'missing prompt and messages, model={params_kw.model}')
|
||||||
d = return_error('Missing need data(prompt or messages)')
|
d = return_error('Missing need data(prompt or messages)')
|
||||||
return json_response(d, status=400)
|
return json_response(d, status=400)
|
||||||
env = request._run_ns
|
env = request._run_ns
|
||||||
@ -33,10 +33,11 @@ async with get_sor_context(env, 'llmage') as sor:
|
|||||||
sql = """select distinct a.* from llm a
|
sql = """select distinct a.* from llm a
|
||||||
join llm_api_map m on a.id = m.llmid
|
join llm_api_map m on a.id = m.llmid
|
||||||
join llmcatelog b on m.llmcatelogid = b.id
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
where b.name = ${lctype}$
|
where (b.id = ${catelogid}$ OR b.name = ${catelogid}$)
|
||||||
and a.model=${model}$"""
|
and a.model=${model}$
|
||||||
|
and a.status = 'published'"""
|
||||||
recs = await sor.sqlExe(sql, {
|
recs = await sor.sqlExe(sql, {
|
||||||
'lctype': lctype,
|
'catelogid': catelogid,
|
||||||
'model': params_kw.model or 'qwen3-max'
|
'model': params_kw.model or 'qwen3-max'
|
||||||
})
|
})
|
||||||
if len(recs) == 0:
|
if len(recs) == 0:
|
||||||
|
|||||||
80
wwwroot/v1/image/generations/index.dspy
Normal file
80
wwwroot/v1/image/generations/index.dspy
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# OpenAI-compatible Image Generation API
|
||||||
|
# POST /v1/image/generations
|
||||||
|
# Required params: model, catelogid
|
||||||
|
# Optional params: prompt, image_url, n, size, style, quality, etc.
|
||||||
|
#
|
||||||
|
# Example request:
|
||||||
|
# {
|
||||||
|
# "model": "jimeng-4.0",
|
||||||
|
# "catelogid": "t2i",
|
||||||
|
# "prompt": "A beautiful sunset over the ocean",
|
||||||
|
# "size": "1024x1024",
|
||||||
|
# "n": 1
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Response format depends on the upstream model (sync returns image data, async returns task info)
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from functools import partial
|
||||||
|
from appPublic.log import debug
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from appPublic.timeUtils import curDateString, timestampstr
|
||||||
|
from sqlor.dbpools import get_sor_context
|
||||||
|
|
||||||
|
debug_params('params_kw', params_kw)
|
||||||
|
|
||||||
|
userid = await get_user()
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if userid is None:
|
||||||
|
debug('need login')
|
||||||
|
return openai_403()
|
||||||
|
|
||||||
|
# Validate required parameters
|
||||||
|
if not params_kw.model:
|
||||||
|
d = return_error('Missing required parameter: model')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.catelogid:
|
||||||
|
d = return_error('Missing required parameter: catelogid')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.prompt:
|
||||||
|
d = return_error('Missing required parameter: prompt')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
lctype = params_kw.catelogid
|
||||||
|
|
||||||
|
env = request._run_ns
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
# Look up llm by model name and catalog type through llm_api_map
|
||||||
|
sql = """select distinct a.* from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
|
where (b.id = ${lctype}$ OR b.name = ${lctype}$)
|
||||||
|
and a.model=${model}$
|
||||||
|
and a.status = 'published'"""
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
'lctype': lctype,
|
||||||
|
'model': params_kw.model
|
||||||
|
})
|
||||||
|
if len(recs) == 0:
|
||||||
|
debug(f'{params_kw.model=} not found for catalog {lctype}')
|
||||||
|
return openai_400()
|
||||||
|
params_kw.llmid = recs[0].id
|
||||||
|
|
||||||
|
debug(f'{params_kw.llmid=}')
|
||||||
|
|
||||||
|
# Check balance
|
||||||
|
f = await checkCustomerBalance(params_kw.llmid, userid, userorgid)
|
||||||
|
if not f:
|
||||||
|
debug(f'{userid=} balance not enough')
|
||||||
|
return openai_429()
|
||||||
|
|
||||||
|
# Generate task ID and attach to params
|
||||||
|
if not params_kw.transno:
|
||||||
|
params_kw.transno = getID()
|
||||||
|
|
||||||
|
# Call inference (image generation can be sync or async depending on model config)
|
||||||
|
return await inference(request, env=env)
|
||||||
23
wwwroot/v1/models/catelog.dspy
Normal file
23
wwwroot/v1/models/catelog.dspy
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# GET /v1/models/catelog
|
||||||
|
# List published models by catalog, optionally exclude one model
|
||||||
|
# Params: catelogid (required), exclude_id (optional)
|
||||||
|
catelogid = params_kw.catelogid
|
||||||
|
if not catelogid:
|
||||||
|
return json.dumps({'error': 'catelogid is required'})
|
||||||
|
exclude_id = params_kw.exclude_id or ''
|
||||||
|
dbname = get_module_dbname('llmage')
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
sql = """select distinct a.* from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
where m.llmcatelogid = ${catelogid}$ and a.status = 'published'
|
||||||
|
"""
|
||||||
|
ns = {'catelogid': catelogid}
|
||||||
|
if exclude_id:
|
||||||
|
sql += " and a.id != ${exclude_id}$"
|
||||||
|
ns['exclude_id'] = exclude_id
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
for r in recs.get('rows', []):
|
||||||
|
r.description = json.dumps(r.description)
|
||||||
|
return recs
|
||||||
|
return []
|
||||||
@ -6,9 +6,9 @@ def get_time_in_seconds(datestr):
|
|||||||
timestamp = dt_obj.timestamp()
|
timestamp = dt_obj.timestamp()
|
||||||
return timestamp
|
return timestamp
|
||||||
|
|
||||||
lctype=params_kw.lctype
|
catelogid = params_kw.catelogid
|
||||||
orderby=params_kw.orderby or 'model'
|
orderby = params_kw.orderby or 'model'
|
||||||
rets = await get_llms_by_catelog_to_customer(catelogid=lctype, orderby=orderby)
|
rets = await get_llms_by_catelog_to_customer(catelogid=catelogid, orderby=orderby)
|
||||||
ret = {
|
ret = {
|
||||||
"object": "list",
|
"object": "list",
|
||||||
"data": []
|
"data": []
|
||||||
|
|||||||
80
wwwroot/v1/music/generations/index.dspy
Normal file
80
wwwroot/v1/music/generations/index.dspy
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# OpenAI-compatible Music Generation API
|
||||||
|
# POST /v1/music/generations
|
||||||
|
# Required params: model, catelogid, prompt, lyrics
|
||||||
|
# Optional params: output_format, audio_setting
|
||||||
|
#
|
||||||
|
# Example request:
|
||||||
|
# {
|
||||||
|
# "model": "music-2.6",
|
||||||
|
# "catelogid": "music_gen",
|
||||||
|
# "prompt": "Pop music, happy, suitable for a sunny day",
|
||||||
|
# "lyrics": "[Intro]\n\n[Verse]\nWalking down the street\nFeeling the beat\n\n[Chorus]\nDancing in the sun\nHaving so much fun"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Response (sync for MiniMax):
|
||||||
|
# {
|
||||||
|
# "id": "luid_xxx",
|
||||||
|
# "object": "music.generation",
|
||||||
|
# "model": "music-2.6",
|
||||||
|
# "status": "SUCCEEDED",
|
||||||
|
# "audio": "https://...",
|
||||||
|
# "created": 1234567890
|
||||||
|
# }
|
||||||
|
|
||||||
|
userid = await get_user()
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if userid is None:
|
||||||
|
debug('need login')
|
||||||
|
return openai_403()
|
||||||
|
|
||||||
|
# Validate required parameters
|
||||||
|
if not params_kw.model:
|
||||||
|
d = return_error('Missing required parameter: model')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.catelogid:
|
||||||
|
d = return_error('Missing required parameter: catelogid')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.prompt:
|
||||||
|
d = return_error('Missing required parameter: prompt')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.lyrics:
|
||||||
|
d = return_error('Missing required parameter: lyrics')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
lctype = params_kw.catelogid
|
||||||
|
|
||||||
|
env = request._run_ns
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
# Look up llm by model name and catalog type through llm_api_map
|
||||||
|
sql = """select distinct a.* from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
|
where (b.id = ${lctype}$ OR b.name = ${lctype}$)
|
||||||
|
and a.model=${model}$
|
||||||
|
and a.status = 'published'"""
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
'lctype': lctype,
|
||||||
|
'model': params_kw.model
|
||||||
|
})
|
||||||
|
if len(recs) == 0:
|
||||||
|
debug(f'{params_kw.model=} not found for catalog {lctype}')
|
||||||
|
return openai_400()
|
||||||
|
params_kw.llmid = recs[0].id
|
||||||
|
|
||||||
|
debug(f'{params_kw.llmid=}')
|
||||||
|
|
||||||
|
# Check balance
|
||||||
|
f = await checkCustomerBalance(params_kw.llmid, userid, userorgid)
|
||||||
|
if not f:
|
||||||
|
debug(f'{userid=} balance not enough')
|
||||||
|
return openai_429()
|
||||||
|
|
||||||
|
# Generate task ID and attach to params
|
||||||
|
if not params_kw.transno:
|
||||||
|
params_kw.transno = getID()
|
||||||
|
|
||||||
|
# Call inference (music generation via MiniMax is synchronous)
|
||||||
|
return await inference(request, env=env)
|
||||||
34
wwwroot/v1/pricing/index.dspy
Normal file
34
wwwroot/v1/pricing/index.dspy
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# GET /llmage/v1/pricing
|
||||||
|
# Get model pricing display information
|
||||||
|
# Required params: model (model name, e.g. qwen3.7-max)
|
||||||
|
# Optional params: catelogid (default: t2t)
|
||||||
|
#
|
||||||
|
# Example: /llmage/v1/pricing?model=qwen3.7-max
|
||||||
|
# Returns: { "status": "ok", "data": { "display_text": "...", ... } }
|
||||||
|
|
||||||
|
model = params_kw.model
|
||||||
|
if not model:
|
||||||
|
return json.dumps({"status": "error", "message": "model parameter required"}, ensure_ascii=False)
|
||||||
|
|
||||||
|
catelogid = params_kw.catelogid or 't2t'
|
||||||
|
env = request._run_ns
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
sql = """select m.ppid from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
where a.model = ${model}$
|
||||||
|
and a.status = 'published'
|
||||||
|
and m.ppid is not null
|
||||||
|
and m.isdefaultcatelog = '1'
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql, {'model': model})
|
||||||
|
if len(recs) == 0:
|
||||||
|
return json.dumps({"status": "error", "message": f"model '{model}' not found or has no pricing"}, ensure_ascii=False)
|
||||||
|
ppid = recs[0].ppid
|
||||||
|
|
||||||
|
result = await env.get_pricing_display(ppid)
|
||||||
|
return json.dumps({"status": "ok", "data": result}, ensure_ascii=False, default=str)
|
||||||
|
except Exception as e:
|
||||||
|
exception(f'get pricing for {model} failed: {e}\n{format_exc()}')
|
||||||
|
return json.dumps({"status": "error", "message": str(e)}, ensure_ascii=False)
|
||||||
88
wwwroot/v1/video/generations/index.dspy
Normal file
88
wwwroot/v1/video/generations/index.dspy
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# OpenAI-compatible Video Generation API
|
||||||
|
# POST /v1/video/generations
|
||||||
|
# Required params: model, catelogid
|
||||||
|
# Optional params: prompt, image_url, duration, resolution, n, etc.
|
||||||
|
#
|
||||||
|
# Example request:
|
||||||
|
# {
|
||||||
|
# "model": "keling-2.1",
|
||||||
|
# "catelogid": "t2v",
|
||||||
|
# "prompt": "A beautiful sunset over the ocean",
|
||||||
|
# "duration": "5s",
|
||||||
|
# "resolution": "1080p"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Response (async task):
|
||||||
|
# {
|
||||||
|
# "id": "vid_xxx",
|
||||||
|
# "object": "video.generation",
|
||||||
|
# "model": "keling-2.1",
|
||||||
|
# "status": "submitted",
|
||||||
|
# "taskid": "task_xxx",
|
||||||
|
# "created": 1234567890
|
||||||
|
# }
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from functools import partial
|
||||||
|
from appPublic.log import debug
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from appPublic.timeUtils import curDateString, timestampstr
|
||||||
|
from sqlor.dbpools import get_sor_context
|
||||||
|
|
||||||
|
debug_params('params_kw', params_kw)
|
||||||
|
|
||||||
|
userid = await get_user()
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if userid is None:
|
||||||
|
debug('need login')
|
||||||
|
return openai_403()
|
||||||
|
|
||||||
|
# Validate required parameters
|
||||||
|
if not params_kw.model:
|
||||||
|
d = return_error('Missing required parameter: model')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.catelogid:
|
||||||
|
d = return_error('Missing required parameter: catelogid')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
if not params_kw.prompt:
|
||||||
|
d = return_error('Missing required parameter: prompt')
|
||||||
|
return json_response(d, status=400)
|
||||||
|
|
||||||
|
lctype = params_kw.catelogid
|
||||||
|
|
||||||
|
env = request._run_ns
|
||||||
|
async with get_sor_context(env, 'llmage') as sor:
|
||||||
|
# Look up llm by model name and catalog type through llm_api_map
|
||||||
|
sql = """select distinct a.* from llm a
|
||||||
|
join llm_api_map m on a.id = m.llmid
|
||||||
|
join llmcatelog b on m.llmcatelogid = b.id
|
||||||
|
where (b.id = ${lctype}$ OR b.name = ${lctype}$)
|
||||||
|
and a.model=${model}$
|
||||||
|
and a.status = 'published'"""
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
'lctype': lctype,
|
||||||
|
'model': params_kw.model
|
||||||
|
})
|
||||||
|
if len(recs) == 0:
|
||||||
|
debug(f'{params_kw.model=} not found for catalog {lctype}')
|
||||||
|
return openai_400()
|
||||||
|
params_kw.llmid = recs[0].id
|
||||||
|
|
||||||
|
debug(f'{params_kw.llmid=}')
|
||||||
|
|
||||||
|
# Check balance
|
||||||
|
f = await checkCustomerBalance(params_kw.llmid, userid, userorgid)
|
||||||
|
if not f:
|
||||||
|
debug(f'{userid=} balance not enough')
|
||||||
|
return openai_429()
|
||||||
|
|
||||||
|
# Generate task ID and attach to params
|
||||||
|
if not params_kw.transno:
|
||||||
|
params_kw.transno = getID()
|
||||||
|
|
||||||
|
# Call inference (video/image generation is typically async via callback)
|
||||||
|
return await inference(request, env=env)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
debug(f'{params_kw=}')
|
debug_params('params_kw', params_kw)
|
||||||
if params_kw.off_peak:
|
if params_kw.off_peak:
|
||||||
off_peak = params_kw.off_peak
|
off_peak = params_kw.off_peak
|
||||||
if off_peak in [True, "Y" "y", 1, "1"]:
|
if off_peak in [True, "Y" "y", 1, "1"]:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user