Skip to content

refactor: move the OpenAI-compatible files to the upstream layout - #356

Merged
eric8810 merged 15 commits into
masterfrom
refactor/move-the-openai-compatible-files-to-the-upstream-layout
Oct 27, 2026
Merged

eric8810 merged 15 commits into
masterfrom
refactor/move-the-openai-compatible-files-to-the-upstream-layout

Conversation

@cunninghamcard-bit

@cunninghamcard-bit cunninghamcard-bit commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

把 src/openai_compatible/ 按 @ai-sdk/openai-compatible@3.0.62 一个文件对一个文件重排(RFC-0046 §4.2),照上游建 chat/、completion/、embedding/、image/、utils/ 子目录。两个老测试程序和三个源文件里的单元测试并进厂商测试目录 src/openai_compatible/tests/(RFC-0046 §5)。函数体只改了路径和可见性。公开路径都不变:openai_compatible::completion、::embedding、::image 仍是公开模块,里面的模型类型路径照旧,crate 根和模块根的再导出也不动。测试数量和函数名前后一致;一个设 OPENAI_API_KEY 的老用例加了 #[serial](见“共享进程状态”)。叠在测试 PR #355 上。实施进度在 #328 跟踪。

已经测试分支 c4c73d0f 合上 master b69137eb,冲突按 master 的改动搬到新位置解决(config.rs 的请求 URL 和 user-agent 错误 cause 落到 provider.rs,convert.rs 的两处落到 chat/chat_language_model_options.rs,其余落到 chat、completion、embedding、image 各自的新文件);测试数前后仍相等(311 个:lib 294 个加留在 tests/ 的 17 个,名字多重集相同)。

一、体系现状(概览,测试 PR #355 头 85453d9d)

aimux_providers::openai_compatible::create_openai_compatible
                                                    src/openai_compatible/mod.rs:142
 └ build(preset、Vertex MaaS / xAI 也走这里)        src/openai_compatible/mod.rs:170
   └ OpenAICompatibleProvider::assemble             src/openai_compatible/mod.rs:237
     └ model_config → CompatModelConfig             src/openai_compatible/mod.rs:264 → config.rs:224
       ├ chat → OpenAICompatibleChatModel           src/openai_compatible/mod.rs:283 → chat.rs:194(do_generate)、:316(do_stream)
       │  ├ request_body → build_request_body       chat.rs:52 → convert.rs:183
       │  │  ├ validate_options                     convert.rs:162
       │  │  ├ convert_messages                     convert.rs:407
       │  │  └ prepare_function_tools               convert.rs:74
       │  ├ CompatModelConfig::request_headers / http_request
       │  │                                         config.rs:274、:301
       │  ├ parse_finish_reason                     convert.rs:718
       │  └ ChatSettings::convert_usage → usage_from_raw
       │                                            config.rs:138 → chat.rs:111
       ├ completion → OpenAICompatibleCompletionModel
       │                                            src/openai_compatible/mod.rs:291 → completion.rs:395
       │  └ completion_prompt、usage、metadata       completion.rs:206、:334、:324
       ├ embedding → OpenAICompatibleEmbeddingModel src/openai_compatible/mod.rs:300 → embedding.rs:104
       │  └ embedding_options                       embedding.rs:33
       └ image → OpenAICompatibleImageModel         src/openai_compatible/mod.rs:309 → image.rs:107
ProviderDiscovery::list_models → list_models_once   src/openai_compatible/mod.rs:361 → chat.rs:719

上游同一条路:createOpenAICompatible(openai-compatible-provider.ts)用 getCommonModelConfig 给每个模型一份 { provider, url, headers, fetch },建 OpenAICompatibleChatLanguageModel(chat/)、-CompletionLanguageModel(completion/)、-EmbeddingModel(embedding/)、-ImageModel(image/);消息转换、工具准备、finish reason、usage、metadata extractor、选项各在 chat/ 下自己的文件,toCamelCase 在 utils/,错误结构在 openai-compatible-error.ts。aimux 把这些放在 7 个文件里:mod.rs(provider)、config.rs(模型配置、聊天设置、metadata extractor、错误结构)、convert.rs(getArgs、消息转换、工具、finish reason、toCamelCase)、chat.rs、completion.rs、embedding.rs、image.rs、types.rs。

测试:#355 的新用例在 src/openai_compatible/tests/(266 个);chat.rs、config.rs、convert.rs 里还有 2 + 3 + 1 个单元测试;老用例在两个独立测试程序 tests/openai_compatible_factory_test.rs(11 个)、tests/openai_compatible_upstream_e2e_test.rs(11 个)。

二、本 PR 改动(细节)

aimux_providers::openai_compatible::create_openai_compatible
                                                    src/openai_compatible/provider.rs:119     ← openai-compatible-provider.ts
 └ build                                            src/openai_compatible/provider.rs:147
   └ OpenAICompatibleProvider::assemble             src/openai_compatible/provider.rs:214
     └ model_config → CompatModelConfig             src/openai_compatible/provider.rs:241 → :392   ← getCommonModelConfig
       ├ chat → OpenAICompatibleChatModel           src/openai_compatible/provider.rs:260 → chat/chat_language_model.rs:489、:611   ← chat/openai-compatible-chat-language-model.ts
       │  ├ request_body → build_request_body       chat/chat_language_model.rs:168 → :227
       │  │  ├ validate_options                     chat/chat_language_model_options.rs:32    ← chat/openai-compatible-chat-language-model-options.ts
       │  │  ├ convert_messages                     chat/convert_to_chat_messages.rs:42       ← chat/convert-to-openai-compatible-chat-messages.ts
       │  │  └ prepare_function_tools               chat/prepare_tools.rs:45                  ← chat/openai-compatible-prepare-tools.ts
       │  ├ CompatModelConfig::request_headers / http_request
       │  │                                         provider.rs:442、:469
       │  ├ parse_finish_reason                     chat/map_finish_reason.rs:9               ← chat/map-openai-compatible-finish-reason.ts
       │  └ ChatSettings::convert_usage → usage_from_raw
       │                                            chat/chat_language_model.rs:63 → chat/convert_chat_usage.rs:48   ← chat/convert-openai-compatible-chat-usage.ts
       ├ completion → OpenAICompatibleCompletionModel
       │                                            provider.rs:268 → completion/completion_language_model.rs:297   ← completion/openai-compatible-completion-language-model.ts
       │  ├ completion_prompt                       completion/convert_to_completion_prompt.rs:7   ← completion/convert-to-openai-compatible-completion-prompt.ts
       │  ├ usage                                   completion/convert_completion_usage.rs:7       ← completion/convert-openai-compatible-completion-usage.ts
       │  └ metadata                                completion/get_response_metadata.rs:7          ← completion/get-response-metadata.ts
       ├ embedding → OpenAICompatibleEmbeddingModel provider.rs:277 → embedding/embedding_model.rs:49   ← embedding/openai-compatible-embedding-model.ts
       │  └ embedding_options                       embedding/embedding_model_options.rs:12   ← embedding/openai-compatible-embedding-model-options.ts
       └ image → OpenAICompatibleImageModel         provider.rs:286 → image/image_model.rs:107   ← image/openai-compatible-image-model.ts
ProviderDiscovery::list_models → list_models_once   provider.rs:338 → :556
现在 搬到 上游
mod.rs 的模块文档、mod、pub use mod.rs(只有模块声明和导出) index.ts;internal/index.ts 也指这里
mod.rs 的设置、create_openai_compatible、build、Assembly、OpenAICompatibleProvider 和它的 Provider / ProviderDiscovery 实现 provider.rs openai-compatible-provider.ts
config.rs 的 ResolveBaseUrl、ResolveFetchHeaders、BaseUrl、CompatModelConfig、RequestHeaders、FetchUserAgent provider.rs getCommonModelConfig(同一个文件)
chat.rs 的 list_models_once provider.rs(和 Mistral 一样,发现放在 provider 旁边) aimux 独有
config.rs 的 ErrorStructure、default_error_structure error.rs openai-compatible-error.ts
config.rs 的 ConvertUsage、SupportedUrlsFn、ChatSettings;convert.rs 的 ChatBodySpec、RequestBodyResult、namespace、resolve_metadata_key、build_request_body;chat.rs 的模型、流和其余函数 chat/chat_language_model.rs chat/openai-compatible-chat-language-model.ts(OpenAICompatibleChatConfig、getArgs 都在这个文件);chat/get-response-metadata.ts 在 aimux 是就地组装的,也指这里
convert.rs 的 SCHEMA_KEYS、option_string、validate_options chat/chat_language_model_options.rs chat/openai-compatible-chat-language-model-options.ts
chat.rs 的 convert_usage、usage_from_raw chat/convert_chat_usage.rs chat/convert-openai-compatible-chat-usage.ts
convert.rs 的 MessageSpec 到 convert_assistant_message、signature_string chat/convert_to_chat_messages.rs chat/convert-to-openai-compatible-chat-messages.ts
convert.rs 的 parse_finish_reason chat/map_finish_reason.rs chat/map-openai-compatible-finish-reason.ts;completion 的同名文件内容一样,aimux 只有一份,也指这里
config.rs 的 MetadataExtractor、StreamMetadataExtractor chat/metadata_extractor.rs chat/openai-compatible-metadata-extractor.ts
convert.rs 的 PreparedTools 到 prepare_function_tools chat/prepare_tools.rs chat/openai-compatible-prepare-tools.ts
types.rs chat/api_types.rs(原样) chat/openai-compatible-api-types.ts
completion.rs 除下面三个函数 completion/completion_language_model.rs completion/openai-compatible-completion-language-model.ts;-options.ts 在 aimux 是 arguments 里就地检查的,也指这里
completion.rs 的 completion_prompt、usage、metadata completion/convert_to_completion_prompt.rs、convert_completion_usage.rs、get_response_metadata.rs 三个同名上游文件
embedding.rs 除 embedding_options embedding/embedding_model.rs embedding/openai-compatible-embedding-model.ts
embedding.rs 的 embedding_options embedding/embedding_model_options.rs embedding/openai-compatible-embedding-model-options.ts
image.rs image/image_model.rs image/openai-compatible-image-model.ts;-options.ts、-settings.ts 在 aimux 没有单独的代码,指这里
convert.rs 的 to_camel_case utils/to_camel_case.rs utils/to-camel-case.ts
  • 公开路径:没有变化。completion、embedding、image 三个 pub mod 保留,各自的 mod.rs 再导出模型类型,openai_compatible::completion::OpenAICompatibleCompletionModel 这类路径照旧;chat 仍是 pub(crate),config、convert、types 原来就是 crate 内部模块。
  • 可见性:子目录里的模型类型的 config 字段(chat、completion、embedding、image)和 CompletionConfig 从 pub(super) 改为 pub(in crate::openai_compatible),test: port the upstream OpenAI-compatible tests #355 的测试仍能读;stream_error 同样放宽,completion 经 chat 的再导出用它。拆到兄弟文件后要互相调用的私有函数改为 pub(super):SCHEMA_KEYS、option_string、validate_options、completion_prompt、usage、metadata、embedding_options,以及 CompletionResponse 和它的 id、created、model 字段。chat/mod.rs 把 chat_language_model、convert_to_chat_messages、prepare_tools 声明为 pub(super) mod,测试直接用。
  • crate 内的调用方:BaseUrl、ChatSettings、CompatModelConfig、ResolveBaseUrl、ResolveFetchHeaders 由 openai_compatible/mod.rs 以 pub(crate) use 导出,decision_support.rs、runtime_decision.rs、preset.rs、vertex/partner_provider.rs 的 use crate::openai_compatible::config::… 改成 use crate::openai_compatible::…(只改 import)。chat::usage_from_raw、chat::chat_test_stream、completion::OpenAICompatibleCompletionModel 的路径不变。
  • 测试:
    • tests/openai_compatible_factory_test.rs(createOpenAICompatible 对录制的 AI SDK 请求回放)整个放进 tests/provider.rs 末尾的 recorded_fixtures 模块,自带的 mock 和 fixture 读取不动。
    • tests/openai_compatible_upstream_e2e_test.rs 按被测模型拆进 tests/chat/chat_language_model.rs(7 个)、tests/completion/completion_language_model.rs(1 个)、tests/embedding/embedding_model.rs(1 个)、tests/image/image_model.rs(2 个),各在一个 upstream_e2e 模块里;共用的 provider、options、chat_response 等放到 tests/support.rs,mock_fetch 改用 crate::test_support::mock_fetch。
    • 三个单元测试模块并到被测项所在文件的测试里:config.rs 的 3 个(ChatSettings::for_model)和 chat.rs 的 2 个进 tests/chat/chat_language_model.rs,convert.rs 的 1 个进 tests/utils/to_camel_case.rs。
    • 每节前一行注释写明原来的位置,老文件头的说明改成普通注释。test: port the upstream OpenAI-compatible tests #355 测试里的 config::…、convert::… import 改到新路径。断言都没改。
  • 留在 tests/ 的:tests/openai_compatible_test.rs(625 个)是经 create_provider 跑 preset 的矩阵,测的是 preset,按 RFC-0046 §4.3 随 preset 那一批进 preset/tests/,这里不动。
  • layout.toml 的 [openai_compatible] 25 个 rust 字段改到新路径。scripts/check_provider_boundaries.sh 的规则 4b(共享包里不出现 "groq" / "deepseek")跳过 tests/ 目录:老的回放用例用的是以 groq 为名录制的 fixture,这是测试数据,不是按厂商分支。

共享进程状态:老测试原来各是单独的测试程序,现在和 #355 的用例同在 lib 的一个测试程序里。只有老的 chat_without_an_api_key_sends_no_authorization 动环境变量:它把 OPENAI_API_KEY 设成一个不该被读到的值,证明兼容层不读它;lib 里 default_providers.rs 的 an_invalid_env_base_url_fails_each_call_not_creation(#[serial])也设这个变量,两个同时跑会互相恢复对方的值,所以给它加了 #[serial],函数体不变。原来就有的 a_lazy_base_url_is_evaluated_per_request_and_its_error_fails_the_request 设 AIMUX_TEST_COMPAT_LAZY_BASE_URL,本来就是 #[serial]。没有全局计数器;every_fixture_is_replayed 读 fixtures/aisdk/openai-compatible/,路径按 CARGO_MANIFEST_DIR 算,位置不影响。

验证

  • cargo test -p aimux-providers --lib 加原来两个 --test:前后都是 428 个测试(lib 406 + 11 + 11),函数名多重集相同;openai_compatible::tests:: 下 test: port the upstream OpenAI-compatible tests #355 的 266 个完整路径不变,openai_compatible 以外的 lib 测试路径不变。lib 测试程序连跑 5 次,428 个全过。
  • cargo +1.99 clippy -p aimux-providers --lib --tests -- -D warnings、cargo +1.99 check -p aimux-providers --lib --tests --all-features、RUSTDOCFLAGS="-D warnings" cargo +1.99 doc -p aimux-providers --no-deps、cargo fmt --all -- --check、scripts/check_provider_boundaries.sh、cargo +1.99 check --workspace、cargo test -p aimux-providers 通过。

Type of change

  • Refactor (no behavior change)

Checklist

  • Tests pass locally
  • Commit messages follow Conventional Commits

Generated by Claude Code

cunninghamcard-bit and others added 4 commits October 20, 2026 14:36
… error

A streamed SSE event whose data is not JSON yielded `JsonParse` with only
the parser's message. The AI SDK's `parseJsonEventStream` reads each event
with `safeParseJSON`, whose `JSONParseError` reads
`JSON parsing failed: Text: <text>.\nError message: <message>`
(`@ai-sdk/provider` src/errors/json-parse-error.ts:16-19). Found while
porting the `@ai-sdk/openai-compatible` 3.0.62 tests (`doStream › should
handle unparsable stream parts`, chat and completion); every stream read
through `create_event_source_response_handler` now reports it the same way.
Well-formed JSON of the wrong shape stays `InvalidResponseData`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
Ports the tests of @ai-sdk/openai-compatible 3.0.62 (RFC-0045) into
aimux-providers/src/openai_compatible/tests/, one Rust file per upstream
test file, one mod per describe level and an `/// upstream:` marker on
every test: 266 tests for 266 of the 268 upstream cases. The other 2 are
ts-only in aimux-providers/layout.toml (`[openai_compatible]`): they read
the settings handed to mocked model constructors, which aimux's
completion, embedding and image models never read.

Upstream fixtures are copied unchanged into chat/fixtures/, the four
`toMatchSnapshot` values become chat/snapshots/*.json. The model `config`
fields (and `CompletionConfig`) are widened to `pub(super)` so the tests
can read the configuration a model was built with. Boundary rule 4c lets
the in-crate tests name `crate::test_support`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
Old tests in openai_compatible_upstream_e2e_test.rs and
openai_compatible_factory_test.rs that cover part of an upstream case get a
`/// partial:` line naming the case and the file with its full port (20
lines). None of them covers a whole case, so none gets an `/// upstream:`
marker. Test bodies are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
Split src/openai_compatible/ into one Rust file per file of
@ai-sdk/openai-compatible 3.0.62 (RFC-0046 §4.2), with the upstream
chat/, completion/, embedding/, image/ and utils/ subdirectories:
provider.rs (settings, create_openai_compatible, build, the provider
and getCommonModelConfig's CompatModelConfig), error.rs, and under
chat/ the model, options, usage, message conversion, finish reason,
metadata extractor, tool preparation and API types. Function bodies
change only in paths and visibility; no public path changes, and the
crate-internal callers import from openai_compatible's root instead
of the removed config module.

Fold tests/openai_compatible_factory_test.rs,
tests/openai_compatible_upstream_e2e_test.rs and the inline unit tests
of chat.rs, config.rs and convert.rs into src/openai_compatible/tests/
without changing assertions. The factory test that sets
OPENAI_API_KEY now runs #[serial], since it shares the lib test binary
with the default-providers test that sets the same variable.

Point layout.toml at the new paths, and let boundary rule 4b skip the
vendor tests, whose recorded fixtures use a provider named groq.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW
cunninghamcard-bit and others added 3 commits October 24, 2026 10:05
…ai-sdk-parse-errors

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
…-errors into test/port-the-upstream-openai-compatible-tests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW
@cunninghamcard-bit
cunninghamcard-bit marked this pull request as ready for review October 24, 2026 11:29

@eric8810 eric8810 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenAI-compatible layout refactor:294→294 tests,move-only,公开路径/re-export 保留。Approve。

… deepseek

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW

Copy link
Copy Markdown
Contributor Author

批准之后推了 e41d394f,只改边界脚本:按 eric 在 #370 的意见,规则 4b 不再整体跳过 tests/,只跳过 openai/tests/evaluation.rs 和 openai_compatible/tests/provider.rs 两个文件。这几行和 #370 的 e4535b1c 完全相同,两个 PR 先后合进 master 时这里不冲突。


Generated by Claude Code

claude and others added 6 commits October 25, 2026 08:41
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW
layout.toml: master's [mistral] table (#295) and this PR's
[openai_compatible] table are both kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW
…eam-openai-compatible-tests

# Conflicts:
#	aimux-providers/layout.toml
…mpatible-tests' into refactor/move-the-openai-compatible-files-to-the-upstream-layout

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW
…to-the-upstream-layout

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW
@cunninghamcard-bit
cunninghamcard-bit changed the base branch from test/port-the-upstream-openai-compatible-tests to master October 25, 2026 15:01

Copy link
Copy Markdown
Contributor Author

#355 已 squash 合入 master,本 PR 的 base 已改为 master。

项目 状态
合入 master 5157fdc 合并提交 880f2e1,以 #355 的分支头 99a807b 为合并基,无冲突
diff 是否变化 master..HEAD 与之前批准的 99a807ba..70b36c58 的 --stat 完全一致,仍是纯移动
用例数 2302,与 master 相同
本地检查 fmt、边界脚本、clippy(1.99,-D warnings)、文档、全部测试均通过

Generated by Claude Code

@eric8810 eric8810 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the post-approval commits on this head: only additive tests/docs or tightened boundary checks; no weakening or behavior regression. Re-approving the current head.

@eric8810
eric8810 merged commit c35acb2 into master Oct 27, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants