e15e3b455f
Project CI / Repository checks (push) Successful in 3m12s
Project CI / Frontend tests (push) Successful in 3m21s
Project CI / Backend tests (push) Successful in 7m42s
Project CI / Native shell tests (push) Successful in 20m14s
Project CI / Frontend tests (pull_request) Successful in 50m43s
Project CI / Repository checks (pull_request) Successful in 20m21s
Project CI / Backend tests (pull_request) Successful in 6m18s
Project CI / Native shell tests (pull_request) Successful in 18m19s
## 背景与问题 AGC(AI 游戏创作智能体 App)Direct Codex 模式在携带图片工具结果等大上下文调用 LLM 时,`/api/llm/responses` 与 `/api/llm/chat/completions` 会命中 Axum 默认 **2 MiB** 请求体上限,直接被 413 拒绝,大上下文任务无法执行;同时 Codex app-server 的 failed turn 未把上游 413 映射为稳定错误分类,前端只能显示笼统的“其他错误”,无法引导用户处理。 ## 改动内容 **1. api-server(server-rs)** - 新增常量 `LLM_REQUEST_MAX_BODY_BYTES = 32 MiB`,作为两个 LLM 代理路由的正式请求体上限; - 两个路由显式配置 `DefaultBodyLimit::max(32 MiB)`,避免 Axum 默认 2 MiB 提前拒绝;handler 内保留超限检查,超过 32 MiB 仍返回 `413 PAYLOAD_TOO_LARGE`; - 补充回归测试:>2 MiB 大上下文请求不再命中默认限制;超过 32 MiB 仍返回 413。 **2. AGC 壳(apps/ai-game-creator-shell)** - `codex_app_server` 错误映射:上游/连接层 HTTP 413、`PAYLOAD_TOO_LARGE`、`provider request too large` 等统一映射为稳定分类 `codex-app-server-error:request-too-large`,不再落入 `other` 或误判为权限/安全策略错误; - 前端新增用户可见文案“模型请求体过大,请减少参考图或上下文后重试”,并补充单测断言。 **3. 文档** - 【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md 补充“2026-09-06 AGC LLM 代理请求体合同”:明确 32 MiB 上限、必须显式配置 `DefaultBodyLimit`、413 映射规则与用户文案。 ## 验证 - `cargo test -p api-server llm_routes_accept_large_context_bodies_beyond_axum_default` 通过 - `cargo test -p api-server llm_responses_rejects_bodies_above_explicit_limit` 通过 - `cargo test ... ai-game-creator-shell ... codex_app_server_failed_turn_maps_request_too_large_details` 通过 - `npm run test -- apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts`(27 通过) 全量 workspace 测试与 CI 门禁待推送后由 CI 覆盖。 Reviewed-on: #295 Reviewed-by: 段舒康 <kdletters@qq.com> Co-authored-by: 董羽秦 <suzmii@qq.com> Co-committed-by: 董羽秦 <suzmii@qq.com>
77 lines
2.4 KiB
Rust
77 lines
2.4 KiB
Rust
use axum::{
|
|
Router,
|
|
extract::DefaultBodyLimit,
|
|
middleware,
|
|
routing::{get, post},
|
|
};
|
|
|
|
use crate::{
|
|
auth::require_bearer_auth,
|
|
llm::{
|
|
LLM_REQUEST_MAX_BODY_BYTES, list_llm_models, proxy_llm_chat_completions,
|
|
proxy_llm_responses,
|
|
},
|
|
state::AppState,
|
|
volcengine_speech::{
|
|
get_volcengine_speech_config, stream_volcengine_asr, stream_volcengine_tts_bidirection,
|
|
stream_volcengine_tts_sse,
|
|
},
|
|
};
|
|
|
|
pub fn router(state: AppState) -> Router<AppState> {
|
|
Router::new()
|
|
.route(
|
|
"/api/llm/models",
|
|
get(list_llm_models).route_layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
require_bearer_auth,
|
|
)),
|
|
)
|
|
.route(
|
|
"/api/llm/chat/completions",
|
|
post(proxy_llm_chat_completions)
|
|
.route_layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
require_bearer_auth,
|
|
))
|
|
.layer(DefaultBodyLimit::max(LLM_REQUEST_MAX_BODY_BYTES)),
|
|
)
|
|
.route(
|
|
"/api/llm/responses",
|
|
post(proxy_llm_responses)
|
|
.route_layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
require_bearer_auth,
|
|
))
|
|
.layer(DefaultBodyLimit::max(LLM_REQUEST_MAX_BODY_BYTES)),
|
|
)
|
|
.route(
|
|
"/api/speech/volcengine/config",
|
|
get(get_volcengine_speech_config).route_layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
require_bearer_auth,
|
|
)),
|
|
)
|
|
.route(
|
|
"/api/speech/volcengine/asr/stream",
|
|
get(stream_volcengine_asr).route_layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
require_bearer_auth,
|
|
)),
|
|
)
|
|
.route(
|
|
"/api/speech/volcengine/tts/bidirection",
|
|
get(stream_volcengine_tts_bidirection).route_layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
require_bearer_auth,
|
|
)),
|
|
)
|
|
.route(
|
|
"/api/speech/volcengine/tts/sse",
|
|
post(stream_volcengine_tts_sse).route_layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
require_bearer_auth,
|
|
)),
|
|
)
|
|
}
|