Skip to content

Show partial trace on run failure instead of generic error#1352

Open
chiang-daniel wants to merge 9 commits intomainfrom
KIL-435/improved-errors
Open

Show partial trace on run failure instead of generic error#1352
chiang-daniel wants to merge 9 commits intomainfrom
KIL-435/improved-errors

Conversation

@chiang-daniel
Copy link
Copy Markdown
Contributor

@chiang-daniel chiang-daniel commented Apr 27, 2026

What does this PR do?

When a task run fails, users see a generic failure with no context about what happened before the crash. They would have to dig through the server logs to understand what went wrong, kinda bad UX.

In this PR we introduced a shared ErrorWithTrace type to store additional info.

  • Add new ErrorWithTrace
  • Refactor adapter to preserve some messages across exceptions via a new KilnRunError.
  • Update FastAPI exception to handle error with trace.
  • Add new ErrorWithTrace component.
image

Checklists

  • Tests have been run locally and passed
  • New tests have been added to any work in /lib

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Walkthrough

Adds structured error tracing: a new ErrorWithTrace model and KilnRunError, adapter changes to capture partial message traces on failures, server handler returning ErrorWithTrace for 500s, frontend detection/UI to display traces, and extensive tests across adapters, server, and UI.

Changes

Cohort / File(s) Summary
Frontend Schema & Types
app/web_ui/src/lib/api_schema.d.ts, app/web_ui/src/lib/types.ts
Adds ErrorWithTrace OpenAPI schema and exports ErrorWithTrace type alias.
Frontend Error UI
app/web_ui/src/lib/ui/error_with_trace.svelte, app/web_ui/src/lib/ui/error_with_trace.test.ts
New Svelte component to render error message, optional message trace, and troubleshooting steps; tests for conditional rendering and props.
Run Page Integration
app/web_ui/src/routes/(app)/run/+page.svelte, app/web_ui/src/routes/(app)/run/error_with_trace_detection.ts, app/web_ui/src/routes/(app)/run/run_page_errors.test.ts
Introduces error_with_trace state, type-guard to detect ErrorWithTrace responses, suppresses output focus/scroll when present, and renders error component; tests validate detection behavior.
Core Error Models & Utilities
libs/core/kiln_ai/adapters/errors.py, libs/core/kiln_ai/adapters/test_errors.py
Adds Pydantic ErrorWithTrace, KilnRunError exception, _safe_str and format_error_message utilities; tests verify formatting and model round-trips.
Base Adapter & Hooking
libs/core/kiln_ai/adapters/model_adapters/base_adapter.py, .../test_base_adapter_errors.py
Updates BaseAdapter _run signature to accept a caller-owned messages list, centralizes try/catch to wrap failures in KilnRunError, and adds _messages_to_trace hook; tests cover wrapping, trace capture, and pass-through.
LiteLLM Adapter Changes
libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py, .../test_litellm_adapter*.py
Switches to in-place mutation of caller messages, adds normalization hook _messages_to_trace, and adds/updates error-path tests (e.g., RateLimitError wrapping).
MCP Adapter Updates
libs/core/kiln_ai/adapters/model_adapters/mcp_adapter.py, .../test_mcp_adapter.py
Adds messages param for signature compatibility, wraps execution/validation errors into KilnRunError with partial_trace=None; tests validate wrapping behavior.
Tests & Mocks Updated
libs/core/.../test_*.py (multiple files)
Adjusts many test/mocks to accept the new messages param and updates assertions to expect KilnRunError wrapping and .original preservation across various modules (base adapter, structured output, saving, prompt builders, datamodel).
Server Error Handling & API
libs/server/kiln_server/custom_errors.py, libs/server/kiln_server/run_api.py, .../test_custom_error.py, .../test_run_api_errors.py
Adds FastAPI handler that logs original exception and returns ErrorWithTrace JSON for KilnRunError (500); documents 500 response schema for /run; integration tests validate error-path wiring and response shapes.

Sequence Diagram

sequenceDiagram
    participant Client as Client/UI
    participant Server as FastAPI Server
    participant Adapter as Model Adapter
    participant Model as AI Model

    Client->>Server: POST /run (task request)
    Server->>Adapter: adapter.invoke(input)
    Adapter->>Adapter: create caller-owned messages list
    Adapter->>Adapter: _run(input, messages)  -- mutates messages
    Adapter->>Model: send chat completion / tool calls
    Note over Adapter,Model: messages accumulate system/user/assistant/tool entries

    alt Success
        Model-->>Adapter: response
        Adapter->>Server: RunOutput (+ optional trace)
        Server-->>Client: 200 OK (result)
    else Failure
        Model-->>Adapter: raises exception
        Adapter->>Adapter: catch exception, convert messages -> partial_trace
        Adapter->>Adapter: raise KilnRunError(partial_trace, original)
        Server->>Server: custom handler catches KilnRunError
        Server->>Client: 500 ErrorWithTrace JSON (message, error_type, trace)
        Client->>Client: detect ErrorWithTrace shape -> render error_with_trace UI
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • sfierro
  • leonardmq
  • scosman

Poem

🐰
I hopped through logs and caught a trace,
Breadcrumbs of messages left in place.
When runs go boom mid-chatty race,
I show the bits that led to that case.
Sip carrot tea — errors now have grace.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main goal of the PR: showing a partial trace when a run fails instead of a generic error.
Description check ✅ Passed The description covers the motivation, the approach (ErrorWithTrace type, KilnRunError wrapper, FastAPI handler, UI component), and confirms tests were run and added. However, it is missing the CLA checkbox confirmation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch KIL-435/improved-errors

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.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a structured error handling system for adapter runs, enabling the UI to display partial conversation traces when a failure occurs. It adds an ErrorWithTrace schema, a KilnRunError exception wrapper, and a new Svelte component for rendering these traces. The BaseAdapter and its implementations (LiteLLM, MCP) were updated to capture and propagate partial traces during runtime exceptions. Feedback suggests refining the error message mapping in format_user_message to avoid losing specific debugging context (like tool names) and troubleshooting links, as well as ensuring consistency in adapter method signatures within test mocks.

Comment thread libs/core/kiln_ai/adapters/errors.py Outdated
Comment thread libs/core/kiln_ai/adapters/errors.py Outdated
Comment thread libs/core/kiln_ai/adapters/model_adapters/test_base_adapter.py Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 27, 2026

📊 Coverage Report

Overall Coverage: 45%

Diff: origin/main...HEAD

No lines with coverage information in this diff.


@chiang-daniel chiang-daniel marked this pull request as ready for review April 27, 2026 22:42
Copy link
Copy Markdown
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: 5

🧹 Nitpick comments (7)
app/web_ui/src/routes/(app)/run/+page.svelte (1)

259-284: Optional: consider scrolling to the error block on failure.

When error_with_trace is set, output_section is not bound, so scroll_to_output_if_needed() is skipped (the finally block only scrolls when response is truthy). For long input forms, the run-failed UI may render below the fold without auto-scroll, requiring users to manually scroll to discover the error. Consider binding output_section (or a similar ref) on the error branch and calling scroll_to_output_if_needed() in finally when either response or error_with_trace is set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/run/+page.svelte around lines 259 - 284, The
error branch doesn't bind output_section so scroll_to_output_if_needed() isn't
invoked when error_with_trace is set; update the template so the error branch
(where error_with_trace is truthy) also binds the same output_section (or a new
element ref) and ensure the finally block that calls
scroll_to_output_if_needed() runs when either response or error_with_trace is
set (check response || error_with_trace) so the page auto-scrolls to the error
block; refer to error_with_trace, output_section, scroll_to_output_if_needed(),
and response to locate and change the relevant template and finally logic.
app/web_ui/src/lib/ui/error_with_trace.test.ts (1)

40-122: LGTM!

Good coverage across the visible behaviour: trace render gating (present/null/empty), troubleshooting steps pass-through, default vs. custom error_title, and error.message rendering. The ResizeObserver polyfill in beforeAll is a sensible workaround for jsdom's missing API used by the underlying output.svelte.

Optional nit: the as ErrorWithTraceType["trace"] cast at line 43 hints at a small type drift between Trace and ErrorWithTraceType["trace"]. If they're meant to be the same shape, aligning the types in $lib/types would let the assignment work without the cast.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/ui/error_with_trace.test.ts` around lines 40 - 122, The
test uses a type cast `as ErrorWithTraceType["trace"]` for SAMPLE_TRACE
indicating a type mismatch between the sample fixture and the declared error
trace type; update the definitions in $lib/types so the trace shape used by
SAMPLE_TRACE (and by makeError) and ErrorWithTraceType["trace"] are identical
(e.g., unify/export the same Trace interface and reference it from
ErrorWithTraceType), then remove the unnecessary cast in the test (line with
SAMPLE_TRACE) and ensure imports use the unified Trace/ErrorWithTraceType
symbols.
libs/core/kiln_ai/adapters/errors.py (1)

53-66: Minor: the non-string guard is effectively unreachable.

In CPython, str(exc) always returns a str or raises (which the outer try already handles). The if not isinstance(result, str) branch is defensive paranoia that won't be exercised. Harmless, but worth a comment if you want to keep it; otherwise it can be dropped to simplify.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/adapters/errors.py` around lines 53 - 66, The non-string
guard in _safe_str is unreachable in CPython because str(exc) either returns a
str or raises; remove the redundant `if not isinstance(result, str): return
_GENERIC_FALLBACK_MESSAGE` branch to simplify the function while keeping the
existing try/except and empty-string handling; update any tests if they relied
on that defensive branch and ensure _GENERIC_FALLBACK_MESSAGE remains used only
in the exception-from-str case.
libs/server/kiln_server/test_run_api_errors.py (1)

44-49: Pass path as str for consistency.

Project is constructed with a pathlib.Path here, while test_litellm_adapter_errors.py (and most existing fixtures in this repo) use str(project_path). Pydantic v2 will likely coerce, but the inconsistency is unnecessary.

♻️ Proposed change
-    project = Project(name="Test Project", path=project_path)
+    project = Project(name="Test Project", path=str(project_path))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/server/kiln_server/test_run_api_errors.py` around lines 44 - 49, The
fixture task_setup constructs project_path as a pathlib.Path but passes it
directly to Project; change the Project instantiation (Project(name="Test
Project", path=...)) to pass path=str(project_path) for consistency with other
tests (e.g., test_litellm_adapter_errors.py) and then call
project.save_to_file() as before so the path is a string rather than a Path
object.
libs/core/kiln_ai/adapters/model_adapters/test_base_adapter_errors.py (1)

111-233: LGTM — comprehensive coverage of the new wrapping contract.

The suite exercises the important invariants: identity passthrough for already-wrapped errors, __cause__ preservation, partial_trace populated/None timing, and the absorption of _messages_to_trace failures so the original exception still surfaces. Note that no @pytest.mark.asyncio markers are used here while test_litellm_adapter_errors.py uses them explicitly — this only works if asyncio_mode = "auto" is set in pytest config; consider aligning the style across the two new files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/adapters/model_adapters/test_base_adapter_errors.py` around
lines 111 - 233, The tests in this file rely on pytest's asyncio auto-detection
but other test files use explicit `@pytest.mark.asyncio`; make the style
consistent by adding `@pytest.mark.asyncio` to each async test function (e.g.,
test_raises_kiln_run_error_when_run_throws,
test_partial_trace_populated_when_messages_extended,
test_partial_trace_none_when_failure_before_any_messages,
test_existing_kiln_run_error_passes_through, test_cause_chain_preserved,
test_format_error_message_applied_for_known_exception,
test_format_error_message_applied_for_litellm_rate_limit,
test_messages_to_trace_failure_does_not_swallow_original_exception,
test_messages_to_trace_hook_called) or alternatively ensure pytest.ini /
pyproject.toml explicitly sets asyncio_mode = "auto"; pick one approach and
apply it consistently across this test file to match
test_litellm_adapter_errors.py.
libs/core/kiln_ai/adapters/model_adapters/base_adapter.py (1)

319-330: save_to_file() failures get wrapped with a misleading "successful" trace.

If run.save_to_file() raises (disk full, permission error, validation error in the saved model, etc.), it falls into the generic except Exception arm at line 334. The user then sees:

  • message = "An unexpected error occurred." (because format_error_message doesn't recognize OSError/PermissionError)
  • partial_trace = the complete trace from a fully successful model run

That's confusing — the run itself succeeded; only persistence failed. Consider letting persistence errors escape unwrapped (they're not "run failures with a trace to preserve"), e.g.:

♻️ Suggested split
-            run = self.generate_run(
-                input,
-                input_source,
-                run_output,
-                usage,
-                run_output.trace,
-                parent_task_run,
-            )
-
-            # Save the run if configured to do so, and we have a path to save to
-            if (
-                self.base_adapter_config.allow_saving
-                and Config.shared().autosave_runs
-                and self.task.path is not None
-            ):
-                run.save_to_file()
-            else:
-                # Clear the ID to indicate it's not persisted
-                run.id = None
-
-            return run, run_output
+            run = self.generate_run(
+                input,
+                input_source,
+                run_output,
+                usage,
+                run_output.trace,
+                parent_task_run,
+            )
         except KilnRunError:
             raise
         except Exception as e:
             ...
+
+        # Persistence errors are not run failures — let them surface unwrapped
+        # so callers don't see a "successful" trace alongside a generic error.
+        if (
+            self.base_adapter_config.allow_saving
+            and Config.shared().autosave_runs
+            and self.task.path is not None
+        ):
+            run.save_to_file()
+        else:
+            run.id = None
+
+        return run, run_output
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/adapters/model_adapters/base_adapter.py` around lines 319 -
330, The persistence step (run.save_to_file()) is currently inside the same
exception-handling flow that treats any error as a run failure and returns the
full run trace; instead, isolate persistence errors so they don't get wrapped as
"run failures." Wrap only the save step in its own try/except: when
base_adapter_config.allow_saving && Config.shared().autosave_runs &&
self.task.path is not None, call run.save_to_file() inside a small try block
that either (a) lets IO/Permission/validation exceptions propagate (preferred)
or (b) catches them and raises a more specific persistence exception/log entry
(not the generic format_error_message path). Ensure run.id is only cleared when
you intentionally treat the run as non-persisted (keep the existing run/id and
run_output return semantics on a successful run; do not replace the run trace
with an unrelated "unexpected error" message from format_error_message).
libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py (1)

240-248: Type-alias trick is pragmatic but worth a brief alias-safety note.

Aliasing messages_internal = messages and using # type: ignore[assignment] to widen the element type is fine because the mutations after this only ever go through messages_internal — and crucially, you never call messages_internal = ... (rebinding). If a future change ever does messages_internal = something_new, the caller's reference goes stale and the partial trace is lost on exception. The docstring on the abstract _run (base_adapter.py line 531-535) already calls this out, but consider mirroring a one-liner here as a local reminder, since this is the only file that performs the widening.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py` around lines
240 - 248, Add a short alias-safety comment above the widening assignment so
future maintainers know why we do messages_internal:
list[ChatCompletionMessageIncludingLiteLLM] = messages  # type:
ignore[assignment] and must not rebind messages_internal; explain that mutations
are safe because we never reassign the variable (rebinding would detach the
caller's reference and lose the partial trace on exception) and reference the
abstract _run behavior in base_adapter.py for context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter_errors.py`:
- Around line 41-51: The test uses a hyphenated model name which doesn't match
the registry; update the KilnAgentRunConfigProperties in LiteLlmConfig so
model_name uses the underscored registry name "gpt_4o_mini" (replace
"gpt-4o-mini" with "gpt_4o_mini") so that builtin_model_from resolves to the
OpenAI provider and the adapter's self.model_provider() path and rate-limit
error handling are exercised.

In `@libs/core/kiln_ai/adapters/model_adapters/test_saving_adapter_results.py`:
- Around line 13-15: The test double for the async adapter method _run currently
declares messages=None which weakens the test contract; change the signature of
the mock methods (the async def _run functions referenced by _run and the second
occurrence) to require messages as a positional/keyword argument (remove the
default None) so it matches the production adapter signature (async def
_run(self, input: InputType, messages, **kwargs) -> tuple[RunOutput, Usage |
None]) and update any test callers to pass messages explicitly.

In `@libs/core/kiln_ai/adapters/model_adapters/test_structured_output.py`:
- Around line 100-102: The mock _run method currently declares messages=None
which relaxes the adapter contract; change the signature of async def _run(self,
input: str, messages, **kwargs) -> tuple[RunOutput, Usage | None]: (remove the
default None) so messages is required, and update any local test
instantiations/calls to pass a messages argument consistent with the adapter
(e.g., a list of Message objects or the expected type); ensure the _run
implementation and any type hints still match RunOutput/Usage return types.

In `@libs/core/kiln_ai/adapters/test_prompt_builders.py`:
- Around line 63-65: The test mock's async method _run currently declares
messages=None which weakens the signature; update the mock method _run in
test_prompt_builders.py to match BaseAdapter._run by making the messages
parameter required (remove the default) and ensure its type annotation matches
the original (keep InputType, Return type tuple[RunOutput, Usage | None] and any
kwarg passthrough), so test signature drift is caught early; locate and edit the
async def _run(...) function to remove the messages default and align the
parameter types with BaseAdapter._run.

In `@libs/server/kiln_server/run_api.py`:
- Around line 382-388: The OpenAPI 500 response currently documents only
ErrorWithTrace but the global fallback in custom_errors.py (the handler that
returns {"message": message, "raw_error": str(exc)} for
non-KilnRunError/httpx.TimeoutException) can return a different shape; update
the 500 response declaration in run_api.py to document both shapes (use an
OpenAPI oneOf/union): keep ErrorWithTrace and add a small schema/model (e.g.,
FallbackError or GenericError with "message": str and "raw_error": str) and
reference both in the responses mapping for 500 so generated clients match
runtime responses.

---

Nitpick comments:
In `@app/web_ui/src/lib/ui/error_with_trace.test.ts`:
- Around line 40-122: The test uses a type cast `as ErrorWithTraceType["trace"]`
for SAMPLE_TRACE indicating a type mismatch between the sample fixture and the
declared error trace type; update the definitions in $lib/types so the trace
shape used by SAMPLE_TRACE (and by makeError) and ErrorWithTraceType["trace"]
are identical (e.g., unify/export the same Trace interface and reference it from
ErrorWithTraceType), then remove the unnecessary cast in the test (line with
SAMPLE_TRACE) and ensure imports use the unified Trace/ErrorWithTraceType
symbols.

In `@app/web_ui/src/routes/`(app)/run/+page.svelte:
- Around line 259-284: The error branch doesn't bind output_section so
scroll_to_output_if_needed() isn't invoked when error_with_trace is set; update
the template so the error branch (where error_with_trace is truthy) also binds
the same output_section (or a new element ref) and ensure the finally block that
calls scroll_to_output_if_needed() runs when either response or error_with_trace
is set (check response || error_with_trace) so the page auto-scrolls to the
error block; refer to error_with_trace, output_section,
scroll_to_output_if_needed(), and response to locate and change the relevant
template and finally logic.

In `@libs/core/kiln_ai/adapters/errors.py`:
- Around line 53-66: The non-string guard in _safe_str is unreachable in CPython
because str(exc) either returns a str or raises; remove the redundant `if not
isinstance(result, str): return _GENERIC_FALLBACK_MESSAGE` branch to simplify
the function while keeping the existing try/except and empty-string handling;
update any tests if they relied on that defensive branch and ensure
_GENERIC_FALLBACK_MESSAGE remains used only in the exception-from-str case.

In `@libs/core/kiln_ai/adapters/model_adapters/base_adapter.py`:
- Around line 319-330: The persistence step (run.save_to_file()) is currently
inside the same exception-handling flow that treats any error as a run failure
and returns the full run trace; instead, isolate persistence errors so they
don't get wrapped as "run failures." Wrap only the save step in its own
try/except: when base_adapter_config.allow_saving &&
Config.shared().autosave_runs && self.task.path is not None, call
run.save_to_file() inside a small try block that either (a) lets
IO/Permission/validation exceptions propagate (preferred) or (b) catches them
and raises a more specific persistence exception/log entry (not the generic
format_error_message path). Ensure run.id is only cleared when you intentionally
treat the run as non-persisted (keep the existing run/id and run_output return
semantics on a successful run; do not replace the run trace with an unrelated
"unexpected error" message from format_error_message).

In `@libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py`:
- Around line 240-248: Add a short alias-safety comment above the widening
assignment so future maintainers know why we do messages_internal:
list[ChatCompletionMessageIncludingLiteLLM] = messages  # type:
ignore[assignment] and must not rebind messages_internal; explain that mutations
are safe because we never reassign the variable (rebinding would detach the
caller's reference and lose the partial trace on exception) and reference the
abstract _run behavior in base_adapter.py for context.

In `@libs/core/kiln_ai/adapters/model_adapters/test_base_adapter_errors.py`:
- Around line 111-233: The tests in this file rely on pytest's asyncio
auto-detection but other test files use explicit `@pytest.mark.asyncio`; make the
style consistent by adding `@pytest.mark.asyncio` to each async test function
(e.g., test_raises_kiln_run_error_when_run_throws,
test_partial_trace_populated_when_messages_extended,
test_partial_trace_none_when_failure_before_any_messages,
test_existing_kiln_run_error_passes_through, test_cause_chain_preserved,
test_format_error_message_applied_for_known_exception,
test_format_error_message_applied_for_litellm_rate_limit,
test_messages_to_trace_failure_does_not_swallow_original_exception,
test_messages_to_trace_hook_called) or alternatively ensure pytest.ini /
pyproject.toml explicitly sets asyncio_mode = "auto"; pick one approach and
apply it consistently across this test file to match
test_litellm_adapter_errors.py.

In `@libs/server/kiln_server/test_run_api_errors.py`:
- Around line 44-49: The fixture task_setup constructs project_path as a
pathlib.Path but passes it directly to Project; change the Project instantiation
(Project(name="Test Project", path=...)) to pass path=str(project_path) for
consistency with other tests (e.g., test_litellm_adapter_errors.py) and then
call project.save_to_file() as before so the path is a string rather than a Path
object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ce058e69-4c43-4496-9596-9e81d597cb66

📥 Commits

Reviewing files that changed from the base of the PR and between 2e1018b and ff5f7dd.

📒 Files selected for processing (25)
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/types.ts
  • app/web_ui/src/lib/ui/error_with_trace.svelte
  • app/web_ui/src/lib/ui/error_with_trace.test.ts
  • app/web_ui/src/routes/(app)/run/+page.svelte
  • app/web_ui/src/routes/(app)/run/error_with_trace_detection.ts
  • app/web_ui/src/routes/(app)/run/run_page_errors.test.ts
  • libs/core/kiln_ai/adapters/errors.py
  • libs/core/kiln_ai/adapters/model_adapters/base_adapter.py
  • libs/core/kiln_ai/adapters/model_adapters/litellm_adapter.py
  • libs/core/kiln_ai/adapters/model_adapters/mcp_adapter.py
  • libs/core/kiln_ai/adapters/model_adapters/test_base_adapter.py
  • libs/core/kiln_ai/adapters/model_adapters/test_base_adapter_errors.py
  • libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter.py
  • libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter_errors.py
  • libs/core/kiln_ai/adapters/model_adapters/test_mcp_adapter.py
  • libs/core/kiln_ai/adapters/model_adapters/test_saving_adapter_results.py
  • libs/core/kiln_ai/adapters/model_adapters/test_structured_output.py
  • libs/core/kiln_ai/adapters/test_errors.py
  • libs/core/kiln_ai/adapters/test_prompt_builders.py
  • libs/core/kiln_ai/datamodel/test_basemodel.py
  • libs/server/kiln_server/custom_errors.py
  • libs/server/kiln_server/run_api.py
  • libs/server/kiln_server/test_custom_error.py
  • libs/server/kiln_server/test_run_api_errors.py

Comment thread libs/core/kiln_ai/adapters/model_adapters/test_structured_output.py
Comment thread libs/core/kiln_ai/adapters/test_prompt_builders.py
Comment thread libs/server/kiln_server/run_api.py
# exception thrown from inside `_run` (or the post-processing that
# follows). `_run` must mutate this list in place (extend/append,
# or `list[:] = ...`) and never rebind the local name.
messages: list[ChatCompletionMessageParam] = []
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Allocate the messages list in the caller and pass it into _run() by reference. _run() appends to it as the conversation builds up.

If _run() throws, we still have the list with whatever was added before the crash. That's how we preserve the partial trace in case of failure.

run.id = None

return run, run_output
except KilnRunError:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Wraps any failure from _run() in KilnRunError with the trace attached.

trace: list[ChatCompletionMessageParam] | None = None


class KilnRunError(Exception):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

New error type.

Copy link
Copy Markdown
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/core/kiln_ai/adapters/model_adapters/test_structured_output.py (1)

121-138: ⚠️ Potential issue | 🟡 Minor

Narrow this branch to the wrapped error type.

pytest.raises(Exception) lets the new KilnRunError path regress silently. Hoist the existing KilnRunError import above this branch and assert it here instead of accepting any failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/adapters/model_adapters/test_structured_output.py` around
lines 121 - 138, The test currently uses pytest.raises(Exception) for the first
failing adapter case which can hide the new KilnRunError path; import
KilnRunError (from kiln_ai.adapters.errors) at the top of this test block and
change the pytest.raises(Exception) around adapter = MockAdapter(task,
response={"setup": "asdf"}) / await adapter.invoke(...) to
pytest.raises(KilnRunError) so the test explicitly expects the wrapped
KilnRunError raised by MockAdapter.invoke and fails if a different exception
type is produced; locate the assertions around MockAdapter, task, and
adapter.invoke to make this replacement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@libs/core/kiln_ai/adapters/model_adapters/test_structured_output.py`:
- Around line 121-138: The test currently uses pytest.raises(Exception) for the
first failing adapter case which can hide the new KilnRunError path; import
KilnRunError (from kiln_ai.adapters.errors) at the top of this test block and
change the pytest.raises(Exception) around adapter = MockAdapter(task,
response={"setup": "asdf"}) / await adapter.invoke(...) to
pytest.raises(KilnRunError) so the test explicitly expects the wrapped
KilnRunError raised by MockAdapter.invoke and fails if a different exception
type is produced; locate the assertions around MockAdapter, task, and
adapter.invoke to make this replacement.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cbe57152-2c34-40bb-812d-79b77115100a

📥 Commits

Reviewing files that changed from the base of the PR and between ff5f7dd and 7e881ce.

📒 Files selected for processing (4)
  • libs/core/kiln_ai/adapters/model_adapters/test_litellm_adapter_errors.py
  • libs/core/kiln_ai/adapters/model_adapters/test_saving_adapter_results.py
  • libs/core/kiln_ai/adapters/model_adapters/test_structured_output.py
  • libs/core/kiln_ai/adapters/test_prompt_builders.py
✅ Files skipped from review due to trivial changes (1)
  • libs/core/kiln_ai/adapters/model_adapters/test_saving_adapter_results.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/core/kiln_ai/adapters/test_prompt_builders.py

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.

1 participant