Skip to content

feat: Handle error events in OpenAI response streaming#10844

Merged
edwinjosechittilappilly merged 26 commits intomainfrom
responses-api-error-log
Dec 12, 2025
Merged

feat: Handle error events in OpenAI response streaming#10844
edwinjosechittilappilly merged 26 commits intomainfrom
responses-api-error-log

Conversation

@edwinjosechittilappilly
Copy link
Collaborator

@edwinjosechittilappilly edwinjosechittilappilly commented Dec 2, 2025

Adds error event handling to the OpenAI response stream, yielding an OpenAI-compatible error response and stopping the stream on error. Includes a test to verify error propagation during streaming.

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling in OpenAI-compatible streaming responses. Errors are now properly captured, logged, and reported with descriptive messages, with the stream terminated appropriately.

✏️ Tip: You can customize this high-level summary in your review settings.

Adds error event handling to the OpenAI response stream, yielding an OpenAI-compatible error response and stopping the stream on error. Includes a test to verify error propagation during streaming.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The PR adds error event handling to the OpenAI streaming response handler. When an error event is detected, the code extracts the error message, logs it, constructs an OpenAI-compatible error response, yields it to the client, and terminates the stream. A comprehensive test validates this error propagation behavior.

Changes

Cohort / File(s) Summary
OpenAI streaming error handling
src/backend/base/langflow/api/v1/openai_responses.py
Added error event handling block that detects error event types, extracts and logs error messages, constructs OpenAI-like error responses with type "processing_error", yields the error to the client, and terminates the stream
Error handling test suite
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
New test module validating OpenAI-compatible streaming error propagation; includes async test with mocked flow retrieval, simulated error event, and assertions on HTTP response and streamed error payload format

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Straightforward error handling logic with clear intent and minimal control flow changes
  • Single error event handling block with standard pattern (check type, log, construct response, yield, return)
  • Test file is well-structured with appropriate mocking and assertions

Possibly related PRs

  • #10078: Modifies OpenAI streaming response handling in the same file (openai_responses.py) with overlapping event processing logic

Suggested labels

lgtm

Suggested reviewers

  • phact
  • ogabrielluiz

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Test Quality And Coverage ⚠️ Warning Test covers error response structure and HTTP streaming but lacks success case coverage, logging validation, and has unresolved Ruff code quality violations including unused mocks and missing init.py. Add missing init.py, fix Ruff violations (remove unused mocks, combine patches, use underscore parameters), add success case tests, validate error logging is called, and test error message extraction edge cases.
Test File Naming And Structure ⚠️ Warning Test file follows backend test naming conventions (test_*.py) with proper pytest structure, descriptive names, and comprehensive error condition coverage, but has critical structural and code quality issues. Create missing init.py files in src/backend/base/langflow/tests/api/ and api/v1/. Refactor nested with blocks into parenthesized context managers. Remove unused mock_run_flow variable and rename unused parameters to _args/_kwargs.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Test Coverage For New Implementations ✅ Passed PR includes dedicated test file that verifies error event handling, message extraction, response type, and streaming behavior for new error handling in OpenAI response streaming.
Excessive Mock Usage Warning ✅ Passed Test uses 3 mocks which is reasonable for streaming API endpoint testing. Two primary mocks are necessary for isolation; one is unused but does not constitute excessive usage.
Title check ✅ Passed The title directly describes the main change: adding error event handling to the OpenAI response streaming path, which is the core modification across both the implementation and test files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch responses-api-error-log

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the enhancement New feature or request label Dec 2, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 2, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 3, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 3, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 10, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 10, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 10, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 10, 2025
@edwinjosechittilappilly
Copy link
Collaborator Author

Sample Output Structure:

Response(id=None, created_at=None, error=ResponseError(code=None, message='Error running graph: Error building Component Language Model: \n\nDummy', type='processing_error'), incomplete_details=None, instructions=None, metadata=None, model=None, object=None, output=None, parallel_tool_calls=None, temperature=None, tool_choice=None, tools=None, top_p=None, background=None, conversation=None, max_output_tokens=None, max_tool_calls=None, previous_response_id=None, prompt=None, prompt_cache_key=None, reasoning=None, safety_identifier=None, service_tier=None, status=None, text=None, top_logprobs=None, truncation=None, usage=None, user=None)

@edwinjosechittilappilly edwinjosechittilappilly marked this pull request as ready for review December 10, 2025 17:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py (1)

83-87: Shorten and move event: error assertion comment to fix E501 and clarify intent

The inline comment makes this line exceed 120 characters and is a bit hard to scan. Moving it above the assertion both fixes E501 and clarifies why we’re checking for the absence of event: error:

-                # Verify we got the error event in the stream
-                assert (
-                    "event: error" not in content
-                )  # OpenAI format doesn't use event: error for the data payload itself usually, but let's check the data
+                # Verify we got the error event in the stream via the data payload, not an `event: error` line.
+                assert "event: error" not in content

This matches the intended OpenAI-compatible behavior and satisfies the linter.

🧹 Nitpick comments (2)
src/backend/base/langflow/api/v1/openai_responses.py (1)

168-177: Streaming error-event behavior is correct; align generic exception path JSON formatting

The new event_type == "error" handling does the right thing: extracts a message, logs it, builds an OpenAI-style error via create_openai_error, yields it as JSON in a data: line, and terminates the stream. One small consistency improvement is to JSON-serialize the error response in the generic except path as well so clients never see a Python dict repr in the stream.

You can mirror the new behavior with a minimal change:

             except Exception as e:  # noqa: BLE001
                 logger.error(f"Error in stream generator: {e}")
                 error_response = create_openai_error(
                     message=str(e),
                     type_="processing_error",
                 )
-                yield f"data: {error_response}\n\n"
+                yield f"data: {json.dumps(error_response)}\n\n"

This keeps all streaming errors consistently JSON-encoded for clients.

Also applies to: 390-396

src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py (1)

15-19: Tighten test docstring to satisfy Ruff D205

Ruff treats the current multi-line docstring as summary + description without a blank line. Collapsing it into a single-line summary is simplest and keeps the intent clear:

-@pytest.mark.asyncio
-async def test_openai_response_stream_error_handling(client):
-    """Test that errors during streaming are correctly propagated to the client
-    as OpenAI-compatible error responses.
-    """
+@pytest.mark.asyncio
+async def test_openai_response_stream_error_handling(client):
+    """Test that errors during streaming are propagated as OpenAI-compatible error responses."""

This removes the D205 warning without changing behavior.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 840ec0f and acc113d.

📒 Files selected for processing (2)
  • src/backend/base/langflow/api/v1/openai_responses.py (1 hunks)
  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/backend/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)

src/backend/**/*.py: Use FastAPI async patterns with await for async operations in component execution methods
Use asyncio.create_task() for background tasks and implement proper cleanup with try/except for asyncio.CancelledError
Use queue.put_nowait() for non-blocking queue operations and asyncio.wait_for() with timeouts for controlled get operations

Files:

  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
  • src/backend/base/langflow/api/v1/openai_responses.py
**/test_*.py

📄 CodeRabbit inference engine (Custom checks)

**/test_*.py: Review test files for excessive use of mocks that may indicate poor test design - check if tests have too many mock objects that obscure what's actually being tested
Warn when mocks are used instead of testing real behavior and interactions, and suggest using real objects or test doubles when mocks become excessive
Ensure mocks are used appropriately for external dependencies only, not for core logic
Backend test files should follow the naming convention test_*.py with proper pytest structure
Test files should have descriptive test function names that explain what is being tested
Tests should be organized logically with proper setup and teardown
Consider including edge cases and error conditions for comprehensive test coverage
Verify tests cover both positive and negative scenarios where appropriate
For async functions in backend tests, ensure proper async testing patterns are used with pytest
For API endpoints, verify both success and error response testing

Files:

  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
src/backend/base/langflow/api/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)

Backend API endpoints should be organized by version (v1/, v2/) under src/backend/base/langflow/api/ with specific modules for features (chat.py, flows.py, users.py, etc.)

Files:

  • src/backend/base/langflow/api/v1/openai_responses.py
🧠 Learnings (5)
📚 Learning: 2025-11-24T19:47:28.997Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-24T19:47:28.997Z
Learning: Applies to src/backend/tests/**/*.py : Test Langflow REST API endpoints using the `client` fixture with appropriate HTTP methods (GET, POST, etc.), headers (logged_in_headers), and payload validation

Applied to files:

  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
📚 Learning: 2025-11-24T19:47:28.997Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-24T19:47:28.997Z
Learning: Applies to src/backend/tests/**/*.py : Use `monkeypatch` fixture to mock internal functions for testing error handling scenarios; validate error status codes and error message content in responses

Applied to files:

  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
📚 Learning: 2025-11-24T19:47:28.997Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-24T19:47:28.997Z
Learning: Applies to src/backend/tests/**/*.py : Test real-time event streaming endpoints by consuming NDJSON lines using `response.aiter_lines()`, parsing JSON, and validating event structure and job_id consistency

Applied to files:

  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
📚 Learning: 2025-11-24T19:47:28.997Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-24T19:47:28.997Z
Learning: Applies to src/backend/tests/**/*.py : Test both sync and async code paths, mock external dependencies appropriately, test error handling and edge cases, validate input/output behavior, and test component initialization and configuration

Applied to files:

  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
📚 Learning: 2025-11-24T19:46:09.104Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-11-24T19:46:09.104Z
Learning: Applies to src/backend/base/langflow/api/**/*.py : Backend API endpoints should be organized by version (v1/, v2/) under `src/backend/base/langflow/api/` with specific modules for features (chat.py, flows.py, users.py, etc.)

Applied to files:

  • src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py
🪛 GitHub Actions: Ruff Style Check
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py

[error] 1-1: INP001 File is part of an implicit namespace package. Add an init.py.

🪛 GitHub Check: Ruff Style Check (3.13)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py

[failure] 86-86: Ruff (E501)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:86:121: E501 Line too long (121 > 120)


[failure] 66-66: Ruff (ARG001)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:66:52: ARG001 Unused function argument: kwargs


[failure] 66-66: Ruff (ARG001)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:66:44: ARG001 Unused function argument: args


[failure] 51-51: Ruff (F841)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:51:78: F841 Local variable mock_run_flow is assigned to but never used


[failure] 51-59: Ruff (SIM117)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:51:9: SIM117 Use a single with statement with multiple contexts instead of nested with statements


[failure] 17-19: Ruff (D205)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:17:5: D205 1 blank line required between summary line and description


[failure] 1-1: Ruff (INP001)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:1:1: INP001 File src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py is part of an implicit namespace package. Add an __init__.py.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
  • GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
  • GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
  • GitHub Check: Test Starter Templates
  • GitHub Check: Lint Backend / Run Mypy (3.12)
  • GitHub Check: Lint Backend / Run Mypy (3.11)
  • GitHub Check: Lint Backend / Run Mypy (3.13)
  • GitHub Check: Run Frontend Unit Tests / Frontend Jest Unit Tests
  • GitHub Check: Lint Backend / Run Mypy (3.10)
  • GitHub Check: Run Backend Tests / LFX Tests - Python 3.10
  • GitHub Check: Run Frontend Tests / Determine Test Suites and Shard Distribution
  • GitHub Check: Test Docker Images / Test docker images
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: Update Component Index
  • GitHub Check: Update Starter Projects
🔇 Additional comments (1)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py (1)

9-12: Client fixture setup is straightforward

Creating the app via create_app() and wrapping it in a TestClient per test is a clean, idiomatic pattern here; no changes needed.

@@ -0,0 +1,96 @@
import json
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add __init__.py to make this tests package explicit (Ruff INP001)

Ruff reports this file as part of an implicit namespace package. To satisfy INP001 and keep tools happy, add an (optionally empty) __init__.py alongside this module, e.g.:

# src/backend/base/langflow/tests/api/v1/__init__.py
"""
Make the tests.api.v1 package explicit for tooling.
"""

No changes are needed in test_openai_responses_error.py itself, but this extra file will unblock the Ruff check.

🧰 Tools
🪛 GitHub Actions: Ruff Style Check

[error] 1-1: INP001 File is part of an implicit namespace package. Add an init.py.

🪛 GitHub Check: Ruff Style Check (3.13)

[failure] 1-1: Ruff (INP001)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:1:1: INP001 File src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py is part of an implicit namespace package. Add an __init__.py.

🤖 Prompt for AI Agents
In src/backend/base/langflow/tests/api/v1 around lines 1-1, Ruff reports this
module lives in an implicit namespace package; create
src/backend/base/langflow/tests/api/v1/__init__.py (can be empty or contain a
short module docstring) to make the package explicit so the INP001 check passes
and no changes to test_openai_responses_error.py are required.

Comment on lines +44 to +71
with patch("langflow.api.v1.openai_responses.get_flow_by_id_or_endpoint_name") as mock_get_flow:
# Setup mock flow
mock_flow = MagicMock()
mock_flow.data = {"nodes": [{"data": {"type": "ChatInput"}}, {"data": {"type": "ChatOutput"}}]}
mock_get_flow.return_value = mock_flow

# Mock run_flow_generator to emit an error event
with patch("langflow.api.v1.openai_responses.run_flow_generator") as mock_run_flow:
# We need to simulate the event manager queue behavior
# The run_flow_generator in the actual code puts events into the event_manager
# which puts them into the queue.

# Instead of mocking the complex event manager interaction, we can mock
# consume_and_yield to yield our simulated error event

with patch("langflow.api.v1.openai_responses.consume_and_yield") as mock_consume:
# Simulate an error event from the queue
error_event = json.dumps({"event": "error", "data": {"error": "Simulated streaming error"}}).encode(
"utf-8"
)

# Yield error event then None to end stream
async def event_generator(*args, **kwargs):
yield error_event
yield None

mock_consume.side_effect = event_generator

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Resolve Ruff F841/ARG001/SIM117 by simplifying patches and marking unused args

You can eliminate the unused mock_run_flow, unused args/kwargs, and nested with blocks in one go by combining contexts and using underscore-prefixed parameters:

-    # Mock the flow execution to simulate an error during streaming
-    with patch("langflow.api.v1.openai_responses.get_flow_by_id_or_endpoint_name") as mock_get_flow:
-        # Setup mock flow
-        mock_flow = MagicMock()
-        mock_flow.data = {"nodes": [{"data": {"type": "ChatInput"}}, {"data": {"type": "ChatOutput"}}]}
-        mock_get_flow.return_value = mock_flow
-
-        # Mock run_flow_generator to emit an error event
-        with patch("langflow.api.v1.openai_responses.run_flow_generator") as mock_run_flow:
-            # We need to simulate the event manager queue behavior
-            # The run_flow_generator in the actual code puts events into the event_manager
-            # which puts them into the queue.
-
-            # Instead of mocking the complex event manager interaction, we can mock
-            # consume_and_yield to yield our simulated error event
-
-            with patch("langflow.api.v1.openai_responses.consume_and_yield") as mock_consume:
-                # Simulate an error event from the queue
-                error_event = json.dumps({"event": "error", "data": {"error": "Simulated streaming error"}}).encode(
-                    "utf-8"
-                )
-
-                # Yield error event then None to end stream
-                async def event_generator(*args, **kwargs):
-                    yield error_event
-                    yield None
-
-                mock_consume.side_effect = event_generator
+    # Mock the flow execution to simulate an error during streaming
+    with (
+        patch("langflow.api.v1.openai_responses.get_flow_by_id_or_endpoint_name") as mock_get_flow,
+        patch("langflow.api.v1.openai_responses.run_flow_generator"),
+        patch("langflow.api.v1.openai_responses.consume_and_yield") as mock_consume,
+    ):
+        # Setup mock flow
+        mock_flow = MagicMock()
+        mock_flow.data = {"nodes": [{"data": {"type": "ChatInput"}}, {"data": {"type": "ChatOutput"}}]}
+        mock_get_flow.return_value = mock_flow
+
+        # Simulate an error event from the queue
+        error_event = json.dumps({"event": "error", "data": {"error": "Simulated streaming error"}}).encode("utf-8")
+
+        # Yield error event then None to end stream
+        async def event_generator(*_args, **_kwargs):
+            yield error_event
+            yield None
+
+        mock_consume.side_effect = event_generator

This addresses Ruff F841 (unused mock_run_flow), ARG001 (unused args/kwargs), and SIM117 (nested with), while keeping the test logic the same.

🧰 Tools
🪛 GitHub Check: Ruff Style Check (3.13)

[failure] 66-66: Ruff (ARG001)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:66:52: ARG001 Unused function argument: kwargs


[failure] 66-66: Ruff (ARG001)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:66:44: ARG001 Unused function argument: args


[failure] 51-51: Ruff (F841)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:51:78: F841 Local variable mock_run_flow is assigned to but never used


[failure] 51-59: Ruff (SIM117)
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py:51:9: SIM117 Use a single with statement with multiple contexts instead of nested with statements

🤖 Prompt for AI Agents
src/backend/base/langflow/tests/api/v1/test_openai_responses_error.py around
lines 44 to 71: simplify the test patches by combining the patch contexts into a
single with and remove the unused mock_run_flow variable and nested with blocks;
replace the async event_generator signature to accept underscore-prefixed unused
params (e.g., async def event_generator(*_args, **_kwargs):) or no params if
none are used, yield the error_event then None, and set mock_consume.side_effect
= event_generator, thus eliminating Ruff F841/ARG001/SIM117 warnings while
keeping the same simulated error stream behavior.

@github-actions github-actions bot added the lgtm This PR has been approved by a maintainer label Dec 12, 2025
@ogabrielluiz ogabrielluiz changed the title feat:Handle error events in OpenAI response streaming feat: Handle error events in OpenAI response streaming Dec 12, 2025
@edwinjosechittilappilly edwinjosechittilappilly added this pull request to the merge queue Dec 12, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Dec 12, 2025
Merged via the queue into main with commit 3a41259 Dec 12, 2025
46 of 51 checks passed
@edwinjosechittilappilly edwinjosechittilappilly deleted the responses-api-error-log branch December 12, 2025 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants