From e119dc74ae869d2dfd2898301f71e9e5306c7d5a Mon Sep 17 00:00:00 2001 From: mxyhi Date: Sun, 22 Mar 2026 20:39:26 +0800 Subject: [PATCH] feat(codex): support explicit OpenAI Responses API config (#1235) * feat: support explicit OpenAI Responses API config Co-authored-by: Codex * Update backend/packages/harness/deerflow/config/model_config.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Codex Co-authored-by: Willem Jiang Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 10 ++++++ backend/CLAUDE.md | 1 + backend/README.md | 9 ++++++ backend/docs/CONFIGURATION.md | 13 ++++++++ .../harness/deerflow/config/model_config.py | 8 +++++ backend/tests/test_model_config.py | 30 ++++++++++++++++++ backend/tests/test_model_factory.py | 31 +++++++++++++++++++ config.example.yaml | 12 ++++++- 8 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 backend/tests/test_model_config.py diff --git a/README.md b/README.md index d54a021..2a0085f 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,20 @@ DeerFlow has newly integrated the intelligent search and crawling toolset indepe model: google/gemini-2.5-flash-preview api_key: $OPENAI_API_KEY # OpenRouter still uses the OpenAI-compatible field name here base_url: https://openrouter.ai/api/v1 + + - name: gpt-5-responses + display_name: GPT-5 (Responses API) + use: langchain_openai:ChatOpenAI + model: gpt-5 + api_key: $OPENAI_API_KEY + use_responses_api: true + output_version: responses/v1 ``` OpenRouter and similar OpenAI-compatible gateways should be configured with `langchain_openai:ChatOpenAI` plus `base_url`. If you prefer a provider-specific environment variable name, point `api_key` at that variable explicitly (for example `api_key: $OPENROUTER_API_KEY`). + To route OpenAI models through `/v1/responses`, keep using `langchain_openai:ChatOpenAI` and set `use_responses_api: true` with `output_version: responses/v1`. + 4. **Set API keys for your configured model(s)** Choose one of the following methods: diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index afe4a03..d6b275c 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -181,6 +181,7 @@ Configuration priority: 4. `config.yaml` in parent directory (project root - **recommended location**) Config values starting with `$` are resolved as environment variables (e.g., `$OPENAI_API_KEY`). +`ModelConfig` also declares `use_responses_api` and `output_version` so OpenAI `/v1/responses` can be enabled explicitly while still using `langchain_openai:ChatOpenAI`. **Extensions Configuration** (`extensions_config.json`): diff --git a/backend/README.md b/backend/README.md index df38e14..5ceaaff 100644 --- a/backend/README.md +++ b/backend/README.md @@ -169,6 +169,15 @@ models: api_key: $OPENAI_API_KEY supports_thinking: false supports_vision: true + + - name: gpt-5-responses + display_name: GPT-5 (Responses API) + use: langchain_openai:ChatOpenAI + model: gpt-5 + api_key: $OPENAI_API_KEY + use_responses_api: true + output_version: responses/v1 + supports_vision: true ``` Set your API keys: diff --git a/backend/docs/CONFIGURATION.md b/backend/docs/CONFIGURATION.md index d461e54..e522e2d 100644 --- a/backend/docs/CONFIGURATION.md +++ b/backend/docs/CONFIGURATION.md @@ -38,6 +38,19 @@ models: - DeepSeek (`langchain_deepseek:ChatDeepSeek`) - Any LangChain-compatible provider +To use OpenAI's `/v1/responses` endpoint with LangChain, keep using `langchain_openai:ChatOpenAI` and set: + +```yaml +models: + - name: gpt-5-responses + display_name: GPT-5 (Responses API) + use: langchain_openai:ChatOpenAI + model: gpt-5 + api_key: $OPENAI_API_KEY + use_responses_api: true + output_version: responses/v1 +``` + For OpenAI-compatible gateways (for example Novita or OpenRouter), keep using `langchain_openai:ChatOpenAI` and set `base_url`: ```yaml diff --git a/backend/packages/harness/deerflow/config/model_config.py b/backend/packages/harness/deerflow/config/model_config.py index ca0344e..cb6a8b6 100644 --- a/backend/packages/harness/deerflow/config/model_config.py +++ b/backend/packages/harness/deerflow/config/model_config.py @@ -13,6 +13,14 @@ class ModelConfig(BaseModel): ) model: str = Field(..., description="Model name") model_config = ConfigDict(extra="allow") + use_responses_api: bool | None = Field( + default=None, + description="Whether to route OpenAI ChatOpenAI calls through the /v1/responses API", + ) + output_version: str | None = Field( + default=None, + description="Structured output version for OpenAI responses content, e.g. responses/v1", + ) supports_thinking: bool = Field(default_factory=lambda: False, description="Whether the model supports thinking") supports_reasoning_effort: bool = Field(default_factory=lambda: False, description="Whether the model supports reasoning effort") when_thinking_enabled: dict | None = Field( diff --git a/backend/tests/test_model_config.py b/backend/tests/test_model_config.py new file mode 100644 index 0000000..91f8e70 --- /dev/null +++ b/backend/tests/test_model_config.py @@ -0,0 +1,30 @@ +from deerflow.config.model_config import ModelConfig + + +def _make_model(**overrides) -> ModelConfig: + return ModelConfig( + name="openai-responses", + display_name="OpenAI Responses", + description=None, + use="langchain_openai:ChatOpenAI", + model="gpt-5", + **overrides, + ) + + +def test_responses_api_fields_are_declared_in_model_schema(): + assert "use_responses_api" in ModelConfig.model_fields + assert "output_version" in ModelConfig.model_fields + + +def test_responses_api_fields_round_trip_in_model_dump(): + config = _make_model( + api_key="$OPENAI_API_KEY", + use_responses_api=True, + output_version="responses/v1", + ) + + dumped = config.model_dump(exclude_none=True) + + assert dumped["use_responses_api"] is True + assert dumped["output_version"] == "responses/v1" diff --git a/backend/tests/test_model_factory.py b/backend/tests/test_model_factory.py index 98027c1..ab91f0f 100644 --- a/backend/tests/test_model_factory.py +++ b/backend/tests/test_model_factory.py @@ -498,3 +498,34 @@ def test_openai_compatible_provider_multiple_models(monkeypatch): # Create second model factory_module.create_chat_model(name="minimax-m2.5-highspeed") assert captured.get("model") == "MiniMax-M2.5-highspeed" + + +def test_openai_responses_api_settings_are_passed_to_chatopenai(monkeypatch): + model = ModelConfig( + name="gpt-5-responses", + display_name="GPT-5 Responses", + description=None, + use="langchain_openai:ChatOpenAI", + model="gpt-5", + api_key="test-key", + use_responses_api=True, + output_version="responses/v1", + supports_thinking=False, + supports_vision=True, + ) + cfg = _make_app_config([model]) + _patch_factory(monkeypatch, cfg) + + captured: dict = {} + + class CapturingModel(FakeChatModel): + def __init__(self, **kwargs): + captured.update(kwargs) + BaseChatModel.__init__(self, **kwargs) + + monkeypatch.setattr(factory_module, "resolve_class", lambda path, base: CapturingModel) + + factory_module.create_chat_model(name="gpt-5-responses") + + assert captured.get("use_responses_api") is True + assert captured.get("output_version") == "responses/v1" diff --git a/config.example.yaml b/config.example.yaml index afdbded..0d79cf7 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -12,7 +12,7 @@ # ============================================================================ # Bump this number when the config schema changes. # Run `make config-upgrade` to merge new fields into your local config.yaml. -config_version: 2 +config_version: 3 # ============================================================================ # Models Configuration @@ -45,6 +45,16 @@ models: # temperature: 0.7 # supports_vision: true # Enable vision support for view_image tool + # Example: OpenAI Responses API model + # - name: gpt-5-responses + # display_name: GPT-5 (Responses API) + # use: langchain_openai:ChatOpenAI + # model: gpt-5 + # api_key: $OPENAI_API_KEY + # use_responses_api: true + # output_version: responses/v1 + # supports_vision: true + # Example: Anthropic Claude model # - name: claude-3-5-sonnet # display_name: Claude 3.5 Sonnet