Merge pull request 'replace all apimart call with vectorengine counterpart' (#62) from feat/deprecate-apimart-vectorengine into master
Reviewed-on: #62
This commit was merged in pull request #62.
This commit is contained in:
@@ -194,7 +194,7 @@ cargo test -p api-server app --manifest-path server-rs/Cargo.toml
|
||||
|
||||
后续建议继续拆分:
|
||||
|
||||
- `match3d`: `draft.rs`、`background_and_cover.rs`、`material_sheet.rs`、`apimart_image.rs`。
|
||||
- `match3d`: `draft.rs`、`background_and_cover.rs`、`material_sheet.rs`。
|
||||
- `puzzle`: `session_form.rs`、`draft_compile.rs`、`image_provider.rs`、`errors.rs`。
|
||||
- `custom_world`: `publish_gate.rs`、`foundation_job.rs`、`foundation_assets.rs`、`errors.rs`。
|
||||
- `square_hole`: `config.rs`、`errors.rs`。
|
||||
|
||||
@@ -215,7 +215,7 @@ Handler 主要在 `story.rs`、`combat.rs`、`runtime_inventory.rs`:
|
||||
| Square Hole 图片重生成 | OpenAI/VectorEngine GPT image helper | URL 下载或 base64/data URL 解码 | `LegacyAssetPrefix::SquareHoleAssets` | 方洞作品图片槽位相关 kind | profile/work + image slot | 调用方包裹 | 生成成功但入库失败保留 Data URL 回包 |
|
||||
| Custom World 场景/封面 | VectorEngine GPT image 2 / OpenAI helper | URL 下载或 base64 解码 | `LegacyAssetPrefix::CustomWorldScenes` 等 | scene/cover/opening storyboard | `custom_world_profile` 或 profile/landmark/scene slot | `custom_world_ai.rs` 调用方包裹 | entity/scene 生成存在 LLM fallback;资产持久化失败按当前错误口径返回 |
|
||||
| Puzzle 图片 | GPT image 2 generations/edits | 无参考图 JSON 创建;有参考图 multipart 编辑;base64/URL 结果归一 | `LegacyAssetPrefix::PuzzleAssets` | puzzle level/background/generated image,另有 `puzzle_background_music` | puzzle profile/run/level slot | `puzzle.rs` 调用方包裹 | connectivity 可按既有规则跳过部分计费;运行态 fallback 保持原逻辑 |
|
||||
| Match3D 图片 | APIMart/VectorEngine/OpenAI image helper | 下载、切图、透明化、校准后入库 | `LegacyAssetPrefix::Match3DAssets` | cover/background/item material sheet,音频 kind 另列 | match3d profile/session slot | `match3d.rs` 调用方包裹 | 新草稿不回退 Rodin/GLB;部分连接错误按现有计费跳过规则处理 |
|
||||
| Match3D 图片 | VectorEngine/OpenAI image helper | 下载、切图、透明化、校准后入库 | `LegacyAssetPrefix::Match3DAssets` | cover/background/item material sheet,音频 kind 另列 | match3d profile/session slot | `match3d.rs` 调用方包裹 | 新草稿不回退 Rodin/GLB;部分连接错误按现有计费跳过规则处理 |
|
||||
| Visual Novel 音频 | VectorEngine Suno/Vidu | 任务提交后按 task publish 下载音频 | 视觉小说/creation audio scope | `visual_novel_music`、`visual_novel_ambient_sound` | `visual_novel_scene` + scene id + `music`/`ambient_sound` | `vector_engine_audio_generation.rs` 调用方包裹 | 上游/下载失败显式错误,不混入图片 Adapter |
|
||||
| 通用音频 | VectorEngine Suno/Vidu | 同上 | creation audio scope | background_music/sound_effect 由调用方目标指定 | creation target entity/slot | 调用方包裹 | 不与 VN 场景语义混用 |
|
||||
| 视频 Opening CG | Ark/火山视频 + storyboard | 先生 storyboard,再图生视频,下载 remote video | Custom World 相关 prefix | `custom_world_opening_cg_storyboard`、`custom_world_opening_cg_video` | `custom_world_profile` + opening cg slots | `execute_billable_asset_operation_with_cost` 固定点数 | 配置缺失/超时显式错误,不应静默降级 |
|
||||
|
||||
@@ -190,8 +190,8 @@ npm run check:server-rs-ddd
|
||||
|
||||
## 外部服务与资产
|
||||
|
||||
- LLM:`GENARRATIVE_LLM_*`,创意 Agent 另用 `APIMART_BASE_URL` / `APIMART_API_KEY`。
|
||||
- 图片生成:VectorEngine `gpt-image-2` 图片 provider 归属 `platform-image`,密钥只在后端环境变量中;`api-server` 内的 `openai_image_generation.rs` 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。实际外部生成运行记录统一落 `tracking_event`,`event_key = external_generation_run`,metadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、provider task id 和结果摘要,不再写回过时的 `ai_task`。APIMart 只保留给创意 Agent `gpt-5` Responses 文本 / 多模态链路;DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。VectorEngine `/v1/images/generations` 和 `/v1/images/edits` 上游 POST 使用 `libcurl` 发送;`reqwest` 只保留给参考图 URL 下载和响应中图片 URL 下载。`/v1/images/edits` 的 multipart 参考图必须作为 libcurl 文件上传 part 发送,字段名为 `image`,实现上使用 `Form::buffer(file_name, bytes)` 并设置 `Content-Type`;不能只用 `contents(...).filename(...)`,否则上游会把请求转码为缺少图片并返回 `image is required`。`request_send` 阶段的 curl timeout / connect error 按可重试传输错误处理,最多尝试 5 次,并使用指数退避加短抖动;排障时优先看 `attempt`、`max_attempts`、`retry_delay_ms`、`reference_image_bytes_total` 和 `request_params`,不要把 `SendRequest` 当成上游业务错误。
|
||||
- LLM:通用 LLM 门面继续使用 `GENARRATIVE_LLM_*`;创意 Agent `gpt-5` Responses / Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible client,`api-server` 会把未带 `/v1` 的 VectorEngine base URL 规范化到 `/v1` 后请求 `/responses`。`APIMART_BASE_URL` / `APIMART_API_KEY` 只作为历史残留,不再作为创意 Agent gpt-5 客户端来源;后续排障时优先确认 VectorEngine `/v1/models`、`/v1/chat/completions` 和 `/v1/responses` 可用性。
|
||||
- 图片生成:VectorEngine `gpt-image-2` 图片 provider 归属 `platform-image`,密钥只在后端环境变量中;`api-server` 内的 `openai_image_generation.rs` 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。实际外部生成运行记录统一落 `tracking_event`,`event_key = external_generation_run`,metadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、provider task id 和结果摘要,不再写回过时的 `ai_task`。DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。VectorEngine `/v1/images/generations` 和 `/v1/images/edits` 上游 POST 使用 `libcurl` 发送;`reqwest` 只保留给参考图 URL 下载和响应中图片 URL 下载。`/v1/images/edits` 的 multipart 参考图必须作为 libcurl 文件上传 part 发送,字段名为 `image`,实现上使用 `Form::buffer(file_name, bytes)` 并设置 `Content-Type`;不能只用 `contents(...).filename(...)`,否则上游会把请求转码为缺少图片并返回 `image is required`。`request_send` 阶段的 curl timeout / connect error 按可重试传输错误处理,最多尝试 5 次,并使用指数退避加短抖动;排障时优先看 `attempt`、`max_attempts`、`retry_delay_ms`、`reference_image_bytes_total` 和 `request_params`,不要把 `SendRequest` 当成上游业务错误。
|
||||
- Match3D 物品 sheet:关卡整图完成后走 VectorEngine `/v1/images/edits` multipart `image`,模型为 `gpt-image-2`,`2K 1:1` 输出 `10*10` spritesheet;物品 sheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG,并把透明整图写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。后端优先按透明 alpha 连通域从该 sheet 识别真实素材矩形并持久化 20 个物品、每个 5 个形态;识别数量不足时才回退 `10*10` 固定网格。通用系列素材图集的行列索引按每行 2 个物品计算,必须落在 `1..=10`,难度只决定运行态加载 3 / 9 / 15 / 20 种。
|
||||
- Match3D UI spritesheet 和背景派生图:关卡整图作为参考图并发生成 `1K 1:1` UI spritesheet 与 `1K 9:16` 背景图,模型均为 `gpt-image-2`。UI spritesheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。
|
||||
- Match3D 1:1 容器 UI:VectorEngine `/v1/images/edits` multipart 参考图。该容器参考图是后端生图协议输入,必须通过 `include_bytes!` 随 `api-server` 编译进二进制,避免 API 单独发布或运行目录缺少 `public/` 时生成失败。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 本地开发验证与生产运维
|
||||
# 本地开发验证与生产运维
|
||||
|
||||
更新时间:`2026-06-09`
|
||||
|
||||
@@ -321,8 +321,9 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
|
||||
- `GENARRATIVE_SPACETIME_TOKEN`
|
||||
- `GENARRATIVE_DATABASE_BACKUP_*`
|
||||
- `GENARRATIVE_LLM_*`
|
||||
- `APIMART_*`
|
||||
- `VECTOR_ENGINE_*`
|
||||
- ~~`APIMART_*`~~(已弃用,LLM 文本调用统一迁移到 VectorEngine)
|
||||
- `APIMART_*`(历史残留,创意 Agent LLM 已迁移到 VectorEngine)
|
||||
- `HYPER3D_*`
|
||||
- `VOLCENGINE_SPEECH_*`
|
||||
- `DASHSCOPE_*`
|
||||
@@ -332,6 +333,14 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
|
||||
|
||||
结构化创作 / RPG 的 Responses JSON 链路默认不打开 `web_search`;本地和生产如需联网增强,必须显式配置 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=true` 或 `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=true`。如果上游未开通工具,Responses 可能先吐自然语言再返回 `ToolNotOpen`,这类报错应按工具不可用排查,不要先当成 JSON 解析 bug。
|
||||
|
||||
创意 Agent `gpt-5` 文本链路已从 APIMart 切到 VectorEngine:`api-server` 读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀用于 Responses 协议。排查或切换密钥后,可在本地运行:
|
||||
|
||||
```bash
|
||||
node scripts/test-ve-llm.mjs
|
||||
```
|
||||
|
||||
该脚本读取仓库根目录 `.env.secrets.local` 中的 `VECTOR_ENGINE_BASE_URL` 和 `VECTOR_ENGINE_API_KEY`,依次探测 `/v1/models`、`/v1/chat/completions`、`/v1/responses`、`gpt-5` Chat Completions 和基础 JSON 输出能力;脚本只输出 HTTP 状态、耗时、模型和截断摘要,不应打印密钥。若 `.env.secrets.local` 不存在,先补本地 secrets 文件再运行,不要把 secrets 提交进仓库。
|
||||
|
||||
### 手机验证码短信
|
||||
|
||||
手机验证码发送走阿里云普通短信 `SendSms`,验证码由 `module-auth` 在当前 `api-server` 进程内生成、哈希存储和校验,不再调用阿里云托管验证码的 `SendSmsVerifyCode` / `CheckSmsVerifyCode`。因此 `api-server` 重启后,已发送但未校验的验证码会失效。
|
||||
|
||||
163
scripts/test-ve-llm.mjs
Normal file
163
scripts/test-ve-llm.mjs
Normal file
@@ -0,0 +1,163 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = resolve(__dirname, '..');
|
||||
|
||||
function loadEnv(path) {
|
||||
const content = readFileSync(path, 'utf-8');
|
||||
const env = {};
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const eqIndex = trimmed.indexOf('=');
|
||||
if (eqIndex === -1) continue;
|
||||
const key = trimmed.slice(0, eqIndex).trim();
|
||||
let value = trimmed.slice(eqIndex + 1).trim();
|
||||
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
env[key] = value;
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
const env = loadEnv(resolve(root, '.env.secrets.local'));
|
||||
const BASE = env.VECTOR_ENGINE_BASE_URL?.replace(/\/+$/, '') || 'https://api.vectorengine.cn';
|
||||
const KEY = env.VECTOR_ENGINE_API_KEY || '';
|
||||
|
||||
if (!KEY) {
|
||||
console.error('未找到 VECTOR_ENGINE_API_KEY');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const TIMEOUT_MS = 60_000;
|
||||
|
||||
async function test(name, method, path, body = null) {
|
||||
const url = `${BASE}${path}`;
|
||||
const start = Date.now();
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
const options = { method, headers, signal: controller.signal };
|
||||
if (body) options.body = JSON.stringify(body);
|
||||
|
||||
const resp = await fetch(url, options);
|
||||
clearTimeout(timer);
|
||||
const elapsed = Date.now() - start;
|
||||
const text = await resp.text();
|
||||
let json = null;
|
||||
try { json = JSON.parse(text); } catch {}
|
||||
|
||||
if (resp.ok) {
|
||||
const model = json?.model || json?.data?.[0]?.id || '?';
|
||||
const summary = json?.choices?.[0] ? `choices[0]: ${json.choices[0].message?.content?.slice(0, 80)}` :
|
||||
json?.output_text ? `output_text: ${json.output_text.slice(0, 80)}` :
|
||||
json?.data ? `${json.data.length} models` : JSON.stringify(json).slice(0, 120);
|
||||
return { ok: true, elapsed, code: resp.status, model, summary };
|
||||
} else {
|
||||
const errMsg = json?.error?.message || json?.message || text.slice(0, 200);
|
||||
return { ok: false, elapsed, code: resp.status, error: errMsg };
|
||||
}
|
||||
} catch (e) {
|
||||
const elapsed = Date.now() - start;
|
||||
return { ok: false, elapsed, code: 0, error: e.name === 'AbortError' ? `超时(${TIMEOUT_MS / 1000}s)` : e.message };
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`VectorEngine LLM 能力探测`);
|
||||
console.log(`目标: ${BASE}\n`);
|
||||
|
||||
const tests = [
|
||||
// 1. 探测 /v1/models - 基础连通性 + 列出可用模型
|
||||
{ name: 'GET /v1/models (列出可用模型)', method: 'GET', path: '/v1/models' },
|
||||
|
||||
// 2. Chat Completions - 最标准协议,项目已有 LlmProvider::OpenAiCompatible 支持
|
||||
{
|
||||
name: 'POST /v1/chat/completions (Chat)',
|
||||
method: 'POST',
|
||||
path: '/v1/chat/completions',
|
||||
body: {
|
||||
model: 'gpt-4o',
|
||||
messages: [{ role: 'user', content: '回复 ok,不要解释' }],
|
||||
max_tokens: 10,
|
||||
},
|
||||
},
|
||||
|
||||
// 3. Responses - Apimart 当前使用的协议
|
||||
{
|
||||
name: 'POST /v1/responses (Responses)',
|
||||
method: 'POST',
|
||||
path: '/v1/responses',
|
||||
body: {
|
||||
model: 'gpt-4o',
|
||||
input: [
|
||||
{ role: 'user', content: [{ type: 'input_text', text: '回复 ok,不要解释' }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// 4. 测试 gpt-5 (creative_agent 模型)
|
||||
{
|
||||
name: 'POST /v1/chat/completions (gpt-5, Chat)',
|
||||
method: 'POST',
|
||||
path: '/v1/chat/completions',
|
||||
body: {
|
||||
model: 'gpt-5',
|
||||
messages: [{ role: 'user', content: '回复 ok' }],
|
||||
max_tokens: 10,
|
||||
},
|
||||
},
|
||||
|
||||
// 5. 抓大鹅生成需要的 JSON 输出能力验证
|
||||
{
|
||||
name: 'POST /v1/chat/completions (JSON 输出: 抓大鹅物品)',
|
||||
method: 'POST',
|
||||
path: '/v1/chat/completions',
|
||||
body: {
|
||||
model: 'gpt-4o',
|
||||
messages: [
|
||||
{ role: 'system', content: '你是抓大鹅游戏编辑,只返回 JSON。' },
|
||||
{ role: 'user', content: '题材:水果。请生成 JSON:{"gameName":"水果切切乐","items":[{"name":"苹果","itemSize":"中"},{"name":"西瓜","itemSize":"大"}]}' },
|
||||
],
|
||||
max_tokens: 200,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let pass = 0;
|
||||
let fail = 0;
|
||||
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
const t = tests[i];
|
||||
console.log(`[${i + 1}/${tests.length}] ${t.name}`);
|
||||
const result = await test(t.name, t.method, t.path, t.body);
|
||||
|
||||
if (result.ok) {
|
||||
console.log(` ✅ HTTP ${result.code} ${result.elapsed}ms model: ${result.model}`);
|
||||
console.log(` ${result.summary}`);
|
||||
pass++;
|
||||
} else {
|
||||
const codeStr = result.code === 0 ? 'NET' : `HTTP ${result.code}`;
|
||||
console.log(` ❌ ${codeStr} ${result.elapsed}ms ${result.error}`);
|
||||
fail++;
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log(`=== 结果: ${pass}/${tests.length} 通过, ${fail}/${tests.length} 失败 ===`);
|
||||
|
||||
// 结论
|
||||
if (pass >= 3) {
|
||||
console.log('\n✅ VectorEngine 支持 LLM 文本调用,可替代 Apimart。');
|
||||
console.log(' 将 .env.secrets.local 中 APIMART_BASE_URL 改为 VectorEngine 地址即可。');
|
||||
} else if (pass <= 1) {
|
||||
console.log('\n❌ VectorEngine 不支持 LLM 文本调用。');
|
||||
} else {
|
||||
console.log('\n⚠️ 部分支持,需进一步评估。');
|
||||
}
|
||||
@@ -133,9 +133,10 @@ pub struct AppConfig {
|
||||
pub dashscope_reference_image_model: String,
|
||||
pub dashscope_cover_image_model: String,
|
||||
pub dashscope_image_request_timeout_ms: u64,
|
||||
pub apimart_base_url: String,
|
||||
pub apimart_api_key: Option<String>,
|
||||
pub apimart_image_request_timeout_ms: u64,
|
||||
// 中文注释:Apimart 已于 2026-06 弃用,LLM 文本调用统一迁移到 VectorEngine(同时支持 Chat Completions / Responses 协议)。
|
||||
// pub apimart_base_url: String,
|
||||
// pub apimart_api_key: Option<String>,
|
||||
// pub apimart_image_request_timeout_ms: u64,
|
||||
pub vector_engine_base_url: String,
|
||||
pub vector_engine_api_key: Option<String>,
|
||||
pub vector_engine_image_request_timeout_ms: u64,
|
||||
@@ -293,9 +294,9 @@ impl Default for AppConfig {
|
||||
dashscope_reference_image_model: String::new(),
|
||||
dashscope_cover_image_model: String::new(),
|
||||
dashscope_image_request_timeout_ms: 150_000,
|
||||
apimart_base_url: String::new(),
|
||||
apimart_api_key: None,
|
||||
apimart_image_request_timeout_ms: 180_000,
|
||||
// apimart_base_url: String::new(),
|
||||
// apimart_api_key: None,
|
||||
// apimart_image_request_timeout_ms: 180_000,
|
||||
vector_engine_base_url: String::new(),
|
||||
vector_engine_api_key: None,
|
||||
vector_engine_image_request_timeout_ms: DEFAULT_VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS,
|
||||
@@ -791,17 +792,17 @@ impl AppConfig {
|
||||
config.dashscope_image_request_timeout_ms = dashscope_image_request_timeout_ms;
|
||||
}
|
||||
|
||||
if let Some(apimart_base_url) = read_first_non_empty_env(&["APIMART_BASE_URL"]) {
|
||||
config.apimart_base_url = apimart_base_url;
|
||||
}
|
||||
|
||||
config.apimart_api_key = read_first_non_empty_env(&["APIMART_API_KEY"]);
|
||||
|
||||
if let Some(apimart_image_request_timeout_ms) =
|
||||
read_first_positive_u64_env(&["APIMART_IMAGE_REQUEST_TIMEOUT_MS"])
|
||||
{
|
||||
config.apimart_image_request_timeout_ms = apimart_image_request_timeout_ms;
|
||||
}
|
||||
// 中文注释:Apimart 已于 2026-06 弃用,LLM 文本调用统一迁移到 VectorEngine。
|
||||
// 保留以下历史加载代码,后续删除:
|
||||
// if let Some(apimart_base_url) = read_first_non_empty_env(&["APIMART_BASE_URL"]) {
|
||||
// config.apimart_base_url = apimart_base_url;
|
||||
// }
|
||||
// config.apimart_api_key = read_first_non_empty_env(&["APIMART_API_KEY"]);
|
||||
// if let Some(apimart_image_request_timeout_ms) =
|
||||
// read_first_positive_u64_env(&["APIMART_IMAGE_REQUEST_TIMEOUT_MS"])
|
||||
// {
|
||||
// config.apimart_image_request_timeout_ms = apimart_image_request_timeout_ms;
|
||||
// }
|
||||
|
||||
if let Some(vector_engine_base_url) = read_first_non_empty_env(&["VECTOR_ENGINE_BASE_URL"])
|
||||
{
|
||||
@@ -1189,7 +1190,7 @@ mod tests {
|
||||
|
||||
assert!(config.llm_model.is_empty());
|
||||
assert!(config.llm_base_url.is_empty());
|
||||
assert!(config.apimart_base_url.is_empty());
|
||||
// assert!(config.apimart_base_url.is_empty());
|
||||
assert!(config.vector_engine_base_url.is_empty());
|
||||
assert!(config.ark_character_video_base_url.is_empty());
|
||||
assert_eq!(config.hyper3d_base_url, "https://api.hyper3d.com/api/v2");
|
||||
@@ -1285,11 +1286,11 @@ mod tests {
|
||||
assert_eq!(config.llm_provider, LlmProvider::OpenAiCompatible);
|
||||
assert_eq!(config.llm_base_url, "https://llm.internal.example/v1");
|
||||
assert_eq!(config.llm_model, "internal-text-model");
|
||||
assert_eq!(
|
||||
config.apimart_base_url,
|
||||
"https://responses.internal.example/v1"
|
||||
);
|
||||
assert_eq!(config.apimart_image_request_timeout_ms, 190_000);
|
||||
// assert_eq!(
|
||||
// config.apimart_base_url,
|
||||
// "https://responses.internal.example/v1"
|
||||
// );
|
||||
// assert_eq!(config.apimart_image_request_timeout_ms, 190_000);
|
||||
assert_eq!(
|
||||
config.vector_engine_base_url,
|
||||
"https://vector.internal.example"
|
||||
|
||||
@@ -1378,8 +1378,9 @@ fn build_llm_client(config: &AppConfig) -> Result<Option<LlmClient>, AppStateIni
|
||||
fn build_creative_agent_gpt5_client(
|
||||
config: &AppConfig,
|
||||
) -> Result<Option<LlmClient>, AppStateInitError> {
|
||||
// 中文注释:Apimart 已于 2026-06 弃用,LLM 文本调用统一迁移到 VectorEngine。
|
||||
let Some(api_key) = config
|
||||
.apimart_api_key
|
||||
.vector_engine_api_key
|
||||
.as_ref()
|
||||
.map(|value| value.trim())
|
||||
.filter(|value| !value.is_empty())
|
||||
@@ -1387,9 +1388,15 @@ fn build_creative_agent_gpt5_client(
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let base_url = if config.vector_engine_base_url.ends_with("/v1") {
|
||||
config.vector_engine_base_url.clone()
|
||||
} else {
|
||||
format!("{}/v1", config.vector_engine_base_url.trim_end_matches('/'))
|
||||
};
|
||||
|
||||
let llm_config = LlmConfig::new(
|
||||
LlmProvider::OpenAiCompatible,
|
||||
config.apimart_base_url.clone(),
|
||||
base_url,
|
||||
api_key.to_string(),
|
||||
platform_agent::CREATIVE_AGENT_GPT5_MODEL.to_string(),
|
||||
config.llm_request_timeout_ms,
|
||||
@@ -1512,11 +1519,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_state_builds_creative_agent_gpt5_client_from_apimart_settings() {
|
||||
fn app_state_builds_creative_agent_gpt5_client_from_vector_engine_settings() {
|
||||
let mut config = AppConfig::default();
|
||||
config.llm_api_key = None;
|
||||
config.apimart_base_url = "https://api.apimart.test/v1".to_string();
|
||||
config.apimart_api_key = Some("apimart-key".to_string());
|
||||
config.vector_engine_base_url = "https://api.vectorengine.test".to_string();
|
||||
config.vector_engine_api_key = Some("ve-key".to_string());
|
||||
|
||||
let state = AppState::new(config).expect("state should build");
|
||||
let client = state
|
||||
@@ -1529,7 +1536,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
client.config().responses_url(),
|
||||
"https://api.apimart.test/v1/responses"
|
||||
"https://api.vectorengine.test/v1/responses"
|
||||
);
|
||||
assert!(client.config().official_fallback());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user