Skip to content

UN-2946 [FEAT] Prompt Studio lookups bridge, executor hook, and IDE wiring (OSS side)#1929

Open
chandrasekharan-zipstack wants to merge 56 commits intomainfrom
feat/lookups-v2
Open

UN-2946 [FEAT] Prompt Studio lookups bridge, executor hook, and IDE wiring (OSS side)#1929
chandrasekharan-zipstack wants to merge 56 commits intomainfrom
feat/lookups-v2

Conversation

@chandrasekharan-zipstack
Copy link
Copy Markdown
Contributor

What

OSS-side glue for Lookups V2 — a cloud-only Prompt Studio feature that runs an LLM-powered post-extraction enrichment over a prompt's structured output. This PR is the OSS half: a try-import bridge module, executor hook for non-SP runs, IDE wiring for the Raw/Enriched tabs and gating modals, and a few small schema extensions.

The cloud-side PR (where the feature actually lives) is on the unstract-cloud repo, same branch name, same ticket: https://github.com/Zipstack/unstract-cloud/pull/1463.

Why

The cloud plugin needs to integrate with extraction (executor), with the per-prompt output flow (prompt_studio_helper, prompt_studio_output_manager_v2), with export (prompt_studio_registry_helper), and with the IDE UI (Raw/Enriched tabs, gate modal, dirty-seed banner). All of that has to live in OSS — but OSS must never import cloud schema directly. This PR is that boundary.

How

  • One bridge modulebackend/prompt_studio/lookup_utils.py try-imports the cloud plugin and exposes ~10 stable, opaque functions. Every function is a no-op in OSS. Callers don't know cloud key names or model shapes.
  • Executor hooklegacy_executor._run_lookup_enrichment resolves the worker plugin via ExecutorPluginLoader.get("lookup-enrichment") and runs after each prompt's extraction. Skips with a workflow-log entry when the source prompt extracted nothing.
  • Pre-flight blocks — multi-var lookups in non-SP mode return a 400 from fetch_response / bulk_fetch_response, scoped to the prompt set being run.
  • Export gatelookup-validation action on CustomToolView returns the validation buckets that the FE modal renders.
  • Staleness signalCustomTool.last_exported_at (new field) plus is_lookup_dirty in check_deployment_usage powers the "needs re-export" banner.
  • Usage extensionUsage model adds a few cloud-extensible enum choices (reference_type, llm_usage_reason="lookup", status, error_message, execution_time_ms).

Reviewer aid

A condensed one-page primer for human reviewers — system shape, data model, execution diagrams, gates, smoke checklist — is at lookups-v2-reviewer-refresher.html (attached, same file on both PRs). Read it before opening the diff.

The full KB lives in the reviewer's vault at ~/Documents/Obsidian Vault/zipstuff/lookups/.

Main files to review (sorted by significance)

The bridge — central to the OSS↔cloud contract

  1. backend/prompt_studio/lookup_utils.py — every cloud call funnels through here. Tiny, opaque, no-op in OSS.

Executor — non-SP entry
2. workers/executor/executors/legacy_executor.py_run_lookup_enrichment dispatches the worker plugin; new "skip-with-log" branch when extraction yields None.
3. workers/executor/executor_tool_shim.pystream_log routing (confirms the user-facing path for the new skip log).

Prompt Studio integration
4. backend/prompt_studio/prompt_studio_core_v2/views.py — multi-var pre-flight, lookup-validation action, is_lookup_dirty in check_deployment_usage.
5. backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py — three call sites that attach lookup_config to per-prompt output / tool settings.
6. backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py — export-time validation + per-prompt config attach.
7. backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py — persists lookup output via the bridge after enrichment.
8. backend/prompt_studio/prompt_studio_core_v2/models.py — adds CustomTool.last_exported_at.

Usage / observability
9. backend/usage_v2/models.py — extensible LLM_USAGE_REASON_CHOICES / REFERENCE_TYPE_CHOICES via try-import; new fields for lookup attribution.

Frontend wiring
10. frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx — Raw vs Enriched tabs.
11. frontend/src/components/custom-tools/header/Header.jsx — wires useLookupExportGate (try-imported, no-op in OSS).
12. frontend/src/components/custom-tools/tool-ide/ToolIde.jsx — wires useLookupDirtySeed.
13. frontend/src/components/custom-tools/combined-output/{CombinedOutput,JsonView}.jsx — combined-output enrichment passthrough.

Can this PR break any existing features?

No production breakage expected. All cloud calls flow through lookup_utils and degrade to None / [] / {ok: True} in OSS, so existing flows behave identically when no lookup is assigned or when the cloud plugin isn't installed.

Watch areas:

  • The new _run_lookup_enrichment runs after every prompt in the non-SP path. When lookup_config is None (no assignment) it returns immediately — should be invisible to non-lookup users.
  • metadata["lookup_outputs"] is a new key on prompt output. FE consumers reflecting the full metadata shape need to ignore unknown keys.
  • Usage table now sees rows with reference_type="lookup_version"; dashboards should treat these as additional rows.
  • The new pre-commit-formatted legacy_executor.py skip log uses shim.stream_log which routes through LogPublisher to the workflow log UI — verified path in executor_tool_shim.py.

Database Migrations

  • usage_v2/0005_usage_reason_ref_created_idx
  • usage_v2/0006_alter_usage_llm_usage_reason_and_more
  • prompt_studio_core_v2/0007_customtool_last_exported_at

Env Config

None new.

Relevant Docs

  • KB: ~/Documents/Obsidian Vault/zipstuff/lookups/.
  • Reviewer refresher (attached): lookups-v2-reviewer-refresher.html.

Related Issues or PRs

Dependencies Versions

No new dependencies.

Notes on Testing

Smoke checklist in the reviewer refresher. The OSS-side hooks and bridge can be exercised against a cloud build; in pure OSS, every lookup-related code path is exercised but no-ops out via LOOKUPS_AVAILABLE = False.

Screenshots

UI screenshots live on the cloud PR (the OSS-side FE changes are wiring only).

Checklist

I have read and understood the Contribution Guidelines.

chandrasekharan-zipstack and others added 29 commits April 22, 2026 19:58
…prompt list endpoint

- Add CustomToolListSerializer for the list action to avoid N+1 queries
  (profile lookups, prompt fetching, coverage calculation per tool)
- Add ToolStudioPromptListSerializer with only prompt_id, prompt_key,
  enforce_type, sequence_number
- Add GET /prompt-studio/prompt/?tool_id={uuid} list endpoint
- List action uses select_related and Subquery annotation for prompt_count
- Detail endpoint unchanged (still uses full CustomToolSerializer)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add lookup-studio plugin detection with dynamic import
- Add PromptStudioPopoverContent for hover submenu (Projects / Look-Ups)
  following the same Popover pattern as HITL and Platform Settings
- Register lookups/* route in useMainAppRoutes.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ofile_manager

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on enrichment

- Add lookup_config export in prompt_studio_helper and registry_helper
  via cloud plugin guard (try/except ImportError)
- Store raw output in PromptStudioOutputManager, enriched in cloud
  LookupOutputResult — preserving both for UI tab display
- Add LookupEnrichmentProtocol and plugin call in post-extraction
  pipeline using ExecutorPluginLoader (no-op in OSS)
- Track lookup LLM usage via standard metrics pipeline
  (usage_kwargs with run_id/execution_id, capture_metrics)
- Move webhook postprocessing from answer_prompt to pipeline
- Frontend: dynamic plugin imports for LookupMenuItem, LookupIndicator,
  LookupOutputTabs in prompt cards; fetch lookup outputs on page load
- Add scroll-to-prompt support via query param in DocumentParser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…age reason

- Extract get_lookup_config() to prompt_studio/lookup_utils.py, replacing
  4 identical try/except ImportError blocks across prompt_studio_helper
  and prompt_studio_registry_helper
- Add LOOKUP to LLMUsageReason choices (was missing, causing invalid
  choice on usage records from lookup enrichment LLM calls)
- Migration: usage_v2/0004_add_lookup_usage_reason

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store prompt/completion/total token counts from the most recent
complete() call on the LLM object itself, making usage data
queryable without relying on the Audit pipeline roundtrip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hook methods

Move lookup result-application logic to the cloud plugin, matching
the challenge plugin pattern where the plugin owns metadata mutation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…port fallback

Add reusable extraction_complete/extraction_error callback tasks to the
ide_callback worker, replacing the need for Django-based celery workers
for text extraction. Add ExtractionAPIClient for internal API calls.
Add polling fallback to WebSocket transport for local dev reliability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace inline DRAFT lookup check with pluggable cloud-only hook.
Uses try/except ImportError pattern — zero lookup code in OSS.
Collects all DRAFT lookups in one pass with markdown-linked error messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Consolidate cloud imports into lookup_utils.py with persist_lookup_output() and validate_lookups_for_export() wrappers
- Fix LookupEnrichmentProtocol.run() return type to None matching challenge/evaluation pattern
- Revert logger.info to logger.debug in websocket_views.py
- Eliminate duplicated LookupOutputTabs ternary with renderWithLookupWrapper helper
- Move lookups menu constants from SideNavBar.jsx to cloud plugin
- Harden DocumentParser.jsx scrollTo with UUID validation and fix useEffect dependency
- Revert SocketContext transport to ["websocket"]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move Prompt Studio / Look-Ups navigation from a hover popover on the
sidebar into a Segmented control within the ToolNavBar. CustomTools
dynamically imports LookupList from the plugin and renders tabs when
available, falling back to projects-only view in OSS mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from eager per-call Audit HTTP push to a deferred batch write
pattern for adapter usage. LLM/embedding calls stash records in-memory;
the executor flushes them into ExecutionResult metadata; the Celery task
batch-writes via a new internal endpoint.

Adds 5 nullable columns to Usage (reference_id, reference_type,
execution_time_ms, status, error_message) and a composite index for
lookup dashboard queries. Extensible choice lists allow cloud plugins
to register additional usage reasons and reference types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…izer

Bridge function in lookup_utils.py lets cloud plugins enrich
PromptStudioOutputSerializer with lookup data (enriched output,
lookup name). Enables real-time lookup results via WebSocket
without page refresh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… metadata passthrough

- Wire usage_kwargs_extra from lookup config into LLM usage_kwargs for execution observability
- Add error handling around enricher.run() with explicit ERROR usage records
- Generic passthrough of _usage_kwargs into usage records for arbitrary metadata (e.g. reference_id)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hooks

Add dynamic import of getEnrichedCopyText so the copy button copies
enriched lookup output when the Enriched tab is active. Applied to
both single-pass and multi-profile output paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…modified_at fix

- Add /prompt-studio/<pk>/lookup-validation/ endpoint backing the FE
  Export/Deploy gate; multi-var block check accepts prompt_ids so a single
  prompt run isn't blocked by an unrelated multi-var lookup.
- Add /prompt-output/latest-by-keys/ endpoint that returns the most recent
  raw output per prompt_key for the test panel's "Use Latest Outputs"
  helper.
- Fix prompt output modified_at not refreshing on re-runs (QuerySet.update
  bypasses auto_now); set timezone.now() explicitly in the update args.
- lookup_utils: bridge get_lookup_validation_for_tool and
  get_multi_var_lookups_for_tool with prompt_ids scoping.
- Header wires useLookupExportGate via try-import (no-op stub in OSS).
- TokenUsage treats all-null Usage rows as empty.
- CombinedOutput / JsonView build enriched dict from
  metadata.lookup_outputs to back the Raw|Enriched output toggle.
- .gitignore: widen docker/compose.*.yaml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ab on back

- ProfileInfoBar: swap Row/Col for plain flex-wrap div — kills Ant Row
  negative-margin quirk that overlapped wrapped pills in combined output.
- CustomTools: honor location.state.activeTab so back navigation from
  lookup detail lands on the Look-Ups tab instead of defaulting to Projects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces nullable last_exported_at on CustomTool (populated on first
successful export) so staleness checks can compare against downstream
mutations without a data backfill. NULL is treated as "unknown" and
suppresses the lookup-dirty flag to avoid false alarms on pre-feature
projects. Adds the get_latest_lookup_mutation_for_tool bridge in
lookup_utils so OSS stays decoupled from the cloud plugin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When enricher.run() raises, surface a user-visible ERROR log line in
the workflow execution log alongside the existing usage record. Keeps
lookup failures observable next to the other pre/post lookup lines we
already emit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Loads useLookupDirtySeed (server-side is_lookup_dirty) and
useLookupExportGate from the cloud plugin via dynamic imports so the
reminder banner reflects lookup changes across page reloads and the
banner's Export flow goes through the same validation modal as the
main buttons. Also adds a titleAdornment slot on ToolNavBar for
rendering the onboarding tooltip and relaxes EmptyState.text to accept
nodes for the tagline + link composition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… state

- Delegate production lookup enrichment to LookupEnrichment.run_with_metrics
  so the executor and the IDE test path share LLM construction, error
  handling, and usage-record emission.
- Let ExecutionLogs callers pass an arbitrary backRouteState via location
  state so nested UI restore (e.g. a sub-tab) no longer needs special
  casing in this component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bucket of hardening fixes driven by a staff-level PR re-review:

- Org-scope latest_outputs_by_keys (was cross-tenant readable via raw
  .objects.filter() that bypassed OrganizationFilterBackend).
- Hide lookup payload shape from OSS: three new opaque bridge helpers
  (get_original_value_if_enriched, attach_combined_output_enrichment,
  extract_prompt_output_enrichment) replace direct reads of
  metadata["lookup_outputs"] / _lookup_outputs / lookup_outputs in
  output_manager_helper, CombinedOutput.jsx, and usePromptOutput.js.
- Split usage_v2 index into a new 0005 migration that uses
  AddIndexConcurrently + atomic=False so prod doesn't lock the billing
  table during build.
- Delete stale workers/tests/test_usage.py that imported the removed
  UsageHelper module.
- SDK1 LLM gains public get_last_usage_record() so downstream code
  stops reaching into _pending_usage across plugin boundaries.
- legacy_executor stamps metadata["lookup_errors"][prompt] on a failed
  lookup outcome for dashboards that surface partial-failure runs.
- extraction_client docstring notes the cloud-only endpoint contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Module-level probe in prompt_studio/lookup_utils.py — swap per-function
  try/except ImportError for a single LOOKUPS_AVAILABLE flag. Add
  attach_lookup_config / attach_lookup_configs_to_tool_settings helpers
  so the direct metadata["lookup_errors"] write and the
  lookup_config key stamping both route through the bridge.
- Reject org=None in UsageBatchCreateView (usage_v2/internal_views.py).
- Lift useLookupExportGate to a single mount in ToolIde.jsx; thread
  checkLookups down into custom-tools/header/Header.jsx (eliminates
  the double modal-portal risk).
- Delete the direct metadata["lookup_errors"] write from
  workers/executor/executors/legacy_executor.py — flat summary is now
  stamped by LookupEnrichment.write_lookup_error in the cloud plugin.
  Replace hardcoded "lookup_llm" metrics key with lookup_cls.METRICS_KEY.
- Trim boilerplate comments across CombinedOutput, PromptCardItems,
  PromptOutput, usePromptOutput, prompt-card/Header, CustomToolsHelper,
  SideNavBar — keep the why-comments, drop the what-comments.
OSS counterpart to the cloud-side data-model change. Wires the prompt
studio runtime to the new wire shape, surfaces lookup runnability state
in the prompt card, and adds the usage_v2 enum entries the cloud side
records against (lookup as an LLM usage reason, lookup_version as a
reference type).

Partially working — known follow-up:
TODO: rework lookup input UX. The current variable-mapping flow is awkward
(separate rows, manual prompt selection per variable); needs a redesign that
mirrors how users actually compose a lookup template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bring in:
- modified_at auto-bump fix (BaseModelQuerySet/BaseModelManager + save() override)
- legacy_executor: tool-context warning, backtick log formatting, organization
  IDs threaded into ExecutorToolShim
- prompt-service log streaming + markdown rendering
- minio bucket-listing scope fix and CustomMarkdown URL-safety helper

Conflict resolutions:
- workers/executor/executors/legacy_executor.py: combine HEAD's _usage_records
  field and typed annotations with main's _execution_id/_organization_id
  context fields and tool-context warning. Keep both the usage flush after
  challenge and main's backticked stream_log message.

Drop redundant explicit modified_at workarounds now that BaseModelQuerySet
auto-bumps modified_at on QuerySet.update():
- prompt_studio_output_manager_v2/output_manager_helper.py: remove the
  "modified_at": timezone.now() entry passed to PromptStudioOutputManager
  .objects.filter(...).update(**args), and the now-unused timezone import.
When a configured lookup runs but extraction returned None for the source
prompt, _run_lookup_enrichment used to fall through silently — leaving
users wondering why enrichment didn't appear. Stream a one-line workflow
log via shim.stream_log so the skip is visible alongside other tool-run
events.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds lookup/enrichment plumbing with OSS-safe fallbacks, lookup validation and export staleness tracking, buffered/batched usage reporting with schema migrations and internal bulk API, extraction completion/error callbacks and client, worker-side usage buffering/flush, and multiple frontend integrations for lookup UI and enriched output.

Changes

Cohort / File(s) Summary
Lookup utilities & prompt-studio core
backend/prompt_studio/lookup_utils.py, backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py, backend/prompt_studio/prompt_studio_core_v2/views.py, backend/prompt_studio/prompt_studio_core_v2/urls.py, backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py
Adds lookup_utils (OSS-safe wrappers vs cloud implementations); injects lookup configs into prompt/tool payloads and exports; adds lookup-validation endpoint; gates execution for multi-variable lookups; records CustomTool.last_exported_at on export and checks lookup staleness.
Prompt-studio output & enrichment
backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py, .../serializers.py, .../views.py, .../urls.py
Detects/stashes lookup enrichment in prompt outputs, persists lookup payloads (safe DB error handling), avoids per-prompt N+1 when building combined outputs, injects opaque enrichment into combined responses, enriches serializer output with error safety, and adds latest_outputs_by_keys endpoint.
Prompt-studio DB field & migration
backend/prompt_studio/prompt_studio_core_v2/models.py, .../migrations/0007_customtool_last_exported_at.py
Adds nullable CustomTool.last_exported_at DateTimeField and migration.
Prompt list serializers & routing
backend/prompt_studio/prompt_studio_v2/serializers.py, .../views.py, .../urls.py
Adds ToolStudioPromptListSerializer, switches ToolStudioPromptView to use it for list action, and wires a prompt/ list route.
Usage model, migrations & internal bulk API
backend/usage_v2/models.py, backend/usage_v2/migrations/0004_usage_metrics_fields.py, .../0005_usage_reason_ref_created_idx.py, backend/usage_v2/internal_urls.py, backend/usage_v2/internal_views.py
Expands Usage schema (reference fields, execution_time_ms, status, error_message), replaces enum with extensible choices, adds migrations (including concurrent index), and exposes internal /batch/ endpoint for bulk-create usage.
Worker executor & lookup enrichment protocol
workers/executor/executors/legacy_executor.py, workers/executor/executors/answer_prompt.py, workers/executor/executors/plugins/protocols.py
Centralizes per-execution usage collection (_usage_records), buffers/preserves usage on failure, integrates lookup-enrichment and webhook postprocessing into per-prompt flow, and adds LookupEnrichmentProtocol.
Usage helper removal & task flush
workers/executor/executors/usage.py (deleted), workers/executor/tasks.py
Deletes old UsageHelper; Celery task now reads usage_records from execution result metadata and calls UsageAPIClient.bulk_create_usage to persist them (errors logged).
Usage client & worker integration
workers/shared/clients/usage_client.py, workers/shared/clients/extraction_client.py
Fixes BaseUsageResponse.is_success; adds UsageAPIClient.bulk_create_usage; adds ExtractionAPIClient with mark_extraction_complete and mark_extraction_error.
IDE callback extraction tasks
workers/ide_callback/tasks.py
Adds extraction_complete and extraction_error Celery tasks that persist extraction results/errors and optionally emit websocket events.
SDK usage buffering & helpers
unstract/sdk1/src/unstract/sdk1/llm.py, .../usage_handler.py, .../utils/common.py, .../execution/orchestrator.py
Defers immediate pushes by buffering _pending_usage with accessors (get_last_usage, get_last_usage_record, flush_pending_usage); usage/embedding handlers buffer records and flush when called; execution failure metadata now preserves elapsed and usage_records.
Frontend: lookup plugin & enriched UI
frontend/src/components/.../CombinedOutput.jsx, JsonView.jsx, PromptOutput.jsx, PromptCard/*, ToolIde.jsx, CustomTools.jsx, useMainAppRoutes.js, usePromptOutput.js, CustomToolsHelper.js, SideNavBar.jsx, ToolNavBar.jsx, Header.jsx, ListOfTools.jsx, DocumentParser.jsx
Wires optional lookup-studio plugin (dynamic imports with OSS fallbacks): enriched vs raw views, lookup indicators/menus, pre-export validation gates, lookup-dirty seeding, dynamic routes, and multiple UI integrations and hooks.
Frontend: UI tweaks, propTypes & layout
frontend/src/components/.../ProfileInfoBar.jsx, .../ProfileInfoBar.css, TokenUsage.jsx, EmptyState.jsx, RunAllPrompts.jsx, PromptCardItems.jsx, ExecutionLogs.jsx
Layout refactor for ProfileInfoBar, CSS updates, broadened token/null handling, EmptyState.text now PropTypes.node, small UI/prop changes and dynamic imports.
Prompt studio output URL
backend/prompt_studio/prompt_studio_output_manager_v2/urls.py
Adds prompt-output/latest-by-keys/ route.
Tests removed
workers/tests/test_usage.py
Removes tests tied to deleted UsageHelper and its formatting/validation behaviors.
Repo config
.gitignore
Ignores docker/compose.*.yaml (was single-file) and un-ignores docker/compose.debug.yaml.

Sequence Diagram(s)

sequenceDiagram
    participant Executor as Worker Executor
    participant Orchestrator as Orchestrator/Task
    participant WorkerTask as Celery Task
    participant UsageClient as UsageAPIClient
    participant Backend as Backend API
    participant DB as Database

    Executor->>Orchestrator: run execution, collect _usage_records
    Orchestrator->>WorkerTask: return ExecutionResult (metadata includes usage_records)
    WorkerTask->>UsageClient: bulk_create_usage(usage_records, organization_id)
    UsageClient->>Backend: POST /v1/usage/batch/ (records)
    Backend->>DB: INSERT usage rows
    DB-->>Backend: OK
    Backend-->>UsageClient: 200 { created: N } / success
    UsageClient-->>WorkerTask: success/failure
    WorkerTask-->>Orchestrator: log result / continue
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.55% 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 describes the main objective: OSS-side implementation of Lookups V2 bridge, executor hook, and IDE wiring.
Description check ✅ Passed The description is comprehensive and well-structured, covering What, Why, How, risk assessment, migrations, related issues, and testing notes. All major required sections are present and filled out.
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 feat/lookups-v2

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Comment thread workers/executor/executors/legacy_executor.py Outdated
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: 1

♻️ Duplicate comments (10)
workers/executor/tasks.py (1)

113-130: ⚠️ Potential issue | 🟠 Major

Usage flush failure is still non-recoverable (records can be dropped).

When bulk_create_usage fails, this path only logs and continues. That still allows silent billing-row loss on transient outages.

Proposed fix
             if not ok:
                 logger.error(
                     "bulk_create_usage returned failure for %d records "
                     "(run_id=%s organization_id=%s)",
                     len(usage_records),
                     context.run_id,
                     context.organization_id,
                 )
+                raise ConnectionError("bulk_create_usage returned failure")
         except Exception:
             logger.error(
                 "Failed to flush %d usage records for run_id=%s organization_id=%s",
                 len(usage_records),
                 context.run_id,
                 context.organization_id,
                 exc_info=True,
             )
+            raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workers/executor/tasks.py` around lines 113 - 130, The current flush path in
tasks.py logs failures from bulk_create_usage but swallows them, risking dropped
billing rows; modify the failure handling inside the block that calls
bulk_create_usage so that on a non-ok result or on exception you propagate an
error to the caller (either by re-raising the caught exception or raising a
RecoverableError) instead of only logging, so the orchestration can retry; keep
the existing logger.error messages (including run_id and organization_id) but
follow them with a raise to fail the task and preserve usage_records/context for
retry handling.
backend/usage_v2/internal_views.py (1)

164-193: ⚠️ Potential issue | 🟠 Major

Validate record shape before building Usage objects.

Current permissive defaults (usage_type, adapter_instance_id, model_name, etc.) can mask producer bugs, and one malformed row can fail the entire batch write.

Proposed fix sketch
         usage_objects = []
-        for r in records:
+        for i, r in enumerate(records):
+            if not isinstance(r, dict):
+                return JsonResponse(
+                    {"success": False, "error": f"Invalid record at index {i}"},
+                    status=status.HTTP_400_BAD_REQUEST,
+                )
+            missing = [k for k in ("usage_type", "adapter_instance_id", "model_name") if k not in r]
+            if missing:
+                return JsonResponse(
+                    {
+                        "success": False,
+                        "error": f"Missing required keys at index {i}: {', '.join(missing)}",
+                    },
+                    status=status.HTTP_400_BAD_REQUEST,
+                )
             usage_objects.append(
                 Usage(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/usage_v2/internal_views.py` around lines 164 - 193, The loop that
constructs Usage objects from records (the for r in records -> Usage(...) block)
is too permissive and can mask producer bugs; add explicit validation before
building each Usage: verify required fields (e.g., workflow_id, execution_id,
usage_type, adapter_instance_id or run_id as applicable) are present and of the
expected type, coerce/validate numeric fields (embedding_tokens, prompt_tokens,
completion_tokens, total_tokens as ints; cost_in_dollars as float), ensure
model_name is non-empty when usage_type == "llm", and enforce the
llm_usage_reason None/coercion rule; for records that fail validation, log a
clear error including the offending record and either skip the record or
collect/report them so the batch write isn't aborted by a single malformed row,
then only append validated Usage instances to usage_objects.
unstract/sdk1/src/unstract/sdk1/usage_handler.py (1)

108-112: ⚠️ Potential issue | 🟠 Major

Guard token_counter before dereference in embedding callback.

token_counter is nullable, but Line 111 and Line 154 assume it is always present. This can raise AttributeError in production callback flows and skip usage persistence.

Proposed fix
         ):
             if self.embed_model is None:
                 return
+            if self.token_counter is None:
+                logger.warning(
+                    "Embedding usage callback invoked without token_counter; skipping usage record"
+                )
+                return
             model_name = self.embed_model.model_name
             embedding_tokens = self.token_counter.total_embedding_token_count
@@
-            self.token_counter.reset_counts()
+            self.token_counter.reset_counts()

Also applies to: 154-154

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

In `@unstract/sdk1/src/unstract/sdk1/usage_handler.py` around lines 108 - 112, The
code dereferences self.token_counter (accessing total_embedding_token_count)
without checking for None, which can raise AttributeError and skip usage
persistence; fix by guarding token_counter in the embedding callback(s): check
if self.token_counter is not None before reading
self.token_counter.total_embedding_token_count (use 0 or an appropriate default
when None) and apply the same guard wherever total_embedding_token_count is
accessed (e.g., the lines referencing self.token_counter at the embedding
callback and at the later usage around line 154); ensure stream_log and any
usage persistence code use the guarded/ default value instead of assuming
token_counter exists.
workers/ide_callback/tasks.py (2)

589-590: ⚠️ Potential issue | 🟡 Minor

Normalize extracted_text when present-but-None.

At Line 590, len(extracted_text) can still fail if data["extracted_text"] is explicitly None.

Proposed fix
-        extracted_text = (result_dict.get("data") or {}).get("extracted_text", "")
+        data = result_dict.get("data") or {}
+        extracted_text = data.get("extracted_text") or ""
         token_count = len(extracted_text) // 4
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workers/ide_callback/tasks.py` around lines 589 - 590, The code assigns
extracted_text = (result_dict.get("data") or {}).get("extracted_text", "") but
if data["extracted_text"] exists and is None len(extracted_text) will raise;
normalize extracted_text to a string before computing token_count by coercing
None to "" (e.g., replace None with "" for the extracted_text variable used in
token_count calculation in tasks.py), then compute token_count =
len(extracted_text) // 4 so len() is safe.

567-572: ⚠️ Potential issue | 🟠 Major

Treat HTTP 404 from extraction persistence endpoints as terminal OSS no-op.

If cloud-only extraction endpoints are absent, persistence calls can 404 and currently flow into the outer exception path, then re-raise on Line 644. That breaks OSS-safe degradation for this callback path.

Proposed fix
@@
 def extraction_complete(
@@
-    try:
+    def _is_http_404(exc: Exception) -> bool:
+        status_code = getattr(exc, "status_code", None)
+        if status_code is None:
+            status_code = getattr(getattr(exc, "response", None), "status_code", None)
+        return status_code == 404
+
+    try:
@@
-            api.mark_extraction_error(
-                source=source,
-                file_id=file_id,
-                error=error_msg,
-                organization_id=org_id,
-            )
+            try:
+                api.mark_extraction_error(
+                    source=source,
+                    file_id=file_id,
+                    error=error_msg,
+                    organization_id=org_id,
+                )
+            except Exception as e:
+                if _is_http_404(e):
+                    logger.info(
+                        "Skipping extraction_error persistence (404 endpoint missing): source=%s file=%s",
+                        source,
+                        file_id,
+                    )
+                    return {"status": "ok", "note": "extraction callback endpoint not present (OSS)"}
+                raise
@@
-        api.mark_extraction_complete(
-            source=source,
-            file_id=file_id,
-            token_count=token_count,
-            extracted_text_path=extracted_text_path,
-            organization_id=org_id,
-        )
+        try:
+            api.mark_extraction_complete(
+                source=source,
+                file_id=file_id,
+                token_count=token_count,
+                extracted_text_path=extracted_text_path,
+                organization_id=org_id,
+            )
+        except Exception as e:
+            if _is_http_404(e):
+                logger.info(
+                    "Skipping extraction_complete persistence (404 endpoint missing): source=%s file=%s",
+                    source,
+                    file_id,
+                )
+                return {"status": "ok", "note": "extraction callback endpoint not present (OSS)"}
+            raise

Also applies to: 592-598, 620-644

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

In `@workers/ide_callback/tasks.py` around lines 567 - 572, The persistence calls
to the API (e.g., api.mark_extraction_error called with source, file_id,
organization_id/org_id and similar calls at the other locations) must treat an
HTTP 404 from the extraction persistence endpoints as a terminal OSS no-op: wrap
each api.* persistence call (api.mark_extraction_error, the matching
api.mark_extraction_success/persist call(s) around lines ~592-598 and ~620-644)
in a try/except that catches the HTTP error type your client raises (e.g.,
HTTPError/ApiError) and if the caught exception indicates response.status_code
== 404, swallow it and return/exit this callback path instead of propagating;
for any other exception re-raise so existing error handling still applies.
Ensure you reference the same parameters (source, file_id,
org_id/organization_id, error_msg, extracted_files, checksum) when locating and
wrapping the calls.
backend/prompt_studio/prompt_studio_core_v2/views.py (2)

923-926: ⚠️ Potential issue | 🔴 Critical

prompt_studio_tool is still effectively caller-controlled on this detail route.

Lines 923-926 only change which tool is used for the profile-count check. On Line 934, self.perform_create(serializer) still persists any prompt_studio_tool the request supplied, so a caller with access to one tool can create a profile manager under another tool ID. Force the save against self.get_object() here, or make the serializer field permission-scoped.

Suggested fix
-        prompt_studio_tool = (
-            serializer.validated_data.get(ProfileManagerKeys.PROMPT_STUDIO_TOOL)
-            or self.get_object()
-        )
+        prompt_studio_tool = self.get_object()
@@
-            self.perform_create(serializer)
+            serializer.save(prompt_studio_tool=prompt_studio_tool)

Also applies to: 934-934

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

In `@backend/prompt_studio/prompt_studio_core_v2/views.py` around lines 923 - 926,
The code currently allows a client-supplied prompt_studio_tool to be persisted
because perform_create(serializer) uses serializer data; before calling
self.perform_create(serializer) (in the view method surrounding get_object and
serializer.validated_data), force the tool to the detail object by setting
serializer.validated_data[ProfileManagerKeys.PROMPT_STUDIO_TOOL] =
self.get_object() or call serializer.save(prompt_studio_tool=self.get_object())
so the persisted ProfileManager always uses self.get_object() instead of any
caller-supplied value; alternatively, enforce the field is
read-only/permission-scoped on the serializer to prevent client override.

99-110: ⚠️ Potential issue | 🟠 Major

Use the requested execution path to decide whether to skip this gate.

Line 109 still keys the bypass off custom_tool.single_pass_extraction_mode, so fetch_response() / bulk_fetch_response() can skip the non-single-pass lookup block just by calling those endpoints on a tool configured for single-pass mode. Pass an explicit is_single_pass flag from each caller instead of inferring it from tool settings.

Suggested direction
-def _multi_var_lookup_block_response(custom_tool, prompt_ids=None):
+def _multi_var_lookup_block_response(
+    custom_tool, *, is_single_pass: bool, prompt_ids=None
+):
@@
-    if getattr(custom_tool, "single_pass_extraction_mode", False):
+    if is_single_pass:
         return None
-        if err := _multi_var_lookup_block_response(custom_tool, prompt_ids=[prompt_id]):
+        if err := _multi_var_lookup_block_response(
+            custom_tool, is_single_pass=False, prompt_ids=[prompt_id]
+        ):
             return err
-        if err := _multi_var_lookup_block_response(custom_tool, prompt_ids=prompt_ids):
+        if err := _multi_var_lookup_block_response(
+            custom_tool, is_single_pass=False, prompt_ids=prompt_ids
+        ):
             return err
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/prompt_studio/prompt_studio_core_v2/views.py` around lines 99 - 110,
The gate function _multi_var_lookup_block_response currently checks
custom_tool.single_pass_extraction_mode directly; change its signature to accept
an explicit is_single_pass boolean (e.g., def
_multi_var_lookup_block_response(custom_tool, prompt_ids=None,
is_single_pass=False)) and use that flag to decide skipping instead of
getattr(custom_tool, "single_pass_extraction_mode", False). Update each caller
(e.g., fetch_response and bulk_fetch_response) to pass the correct execution
path by computing and passing is_single_pass from the request context/parameters
rather than relying on tool settings. Ensure all call sites of
_multi_var_lookup_block_response are updated to include the new parameter to
avoid defaulting to the wrong behavior.
backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py (1)

301-304: ⚠️ Potential issue | 🟠 Major

Validate lookup assignments only for prompts that are actually exportable.

Validation is still executed on the full prompt list, but NOTES/inactive prompts are skipped during export. This can fail export for prompts that will never be emitted.

🔧 Suggested fix
-        lookup_configs, lookup_error = validate_lookups_for_export(prompts)
+        lookup_validation_prompts = [
+            prompt
+            for prompt in prompts
+            if prompt.prompt_type != JsonSchemaKey.NOTES and prompt.active
+        ]
+        lookup_configs, lookup_error = validate_lookups_for_export(
+            lookup_validation_prompts
+        )
         if lookup_error:
             raise InValidCustomToolError(lookup_error)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py`
around lines 301 - 304, The code calls validate_lookups_for_export(prompts) over
the full prompts list which includes NOTES/inactive prompts and can block export
for items that are never emitted; change the call to run only on the subset of
exportable prompts (filter prompts to exclude NOTES/inactive entries before
calling validate_lookups_for_export) so that validate_lookups_for_export only
inspects prompts that will be exported and only raise InValidCustomToolError
when those exportable prompts fail validation.
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py (1)

1180-1182: ⚠️ Potential issue | 🟠 Major

Single-pass lookup wiring is still missing in the legacy execution path.

This only updates build_single_pass_payload(). prompt_responder() still routes no-id flow through _execute_prompts_in_single_pass()_fetch_single_pass_response(), where tool_settings["lookup_configs"] is still not attached.

🔧 Suggested follow-up
 def _fetch_single_pass_response(...):
@@
         tool_settings[TSPKeys.SIMILARITY_TOP_K] = default_profile.similarity_top_k
+
+        lookup_configs = get_lookup_configs_for_tool(tool, prompts=prompts)
+        if lookup_configs:
+            tool_settings["lookup_configs"] = lookup_configs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py` around
lines 1180 - 1182, The no-id legacy execution path in prompt_responder still
fails to pass lookup configs into the single-pass flow, so ensure
tool_settings["lookup_configs"] is populated for that path by calling
get_lookup_configs_for_tool(tool, prompts=prompts) and assigning the result to
tool_settings["lookup_configs"] before invoking
_execute_prompts_in_single_pass() (and consequently
_fetch_single_pass_response()); update prompt_responder to mirror the
build_single_pass_payload() change by computing lookup_configs from prompts and
attaching them to the tool_settings used in the no-id flow.
backend/prompt_studio/prompt_studio_output_manager_v2/views.py (1)

74-96: ⚠️ Potential issue | 🟠 Major

Validate tool_id format before ORM filters.

A malformed tool_id can still bubble out as a server error instead of a clean 400. Add a UUID shape guard before both query paths use tool_id.

🔧 Suggested fix
+from uuid import UUID
@@
         tool_id = request.GET.get("tool_id")
@@
         if not tool_id:
             raise ValidationError(detail=PromptOutputManagerErrorMessage.TOOL_VALIDATION)
+        try:
+            UUID(str(tool_id))
+        except (TypeError, ValueError):
+            raise ValidationError(
+                detail=PromptOutputManagerErrorMessage.TOOL_VALIDATION
+            )
@@
         tool_id = request.GET.get("tool_id")
@@
         if not tool_id:
             raise ValidationError(detail=tool_validation_message)
+        try:
+            UUID(str(tool_id))
+        except (TypeError, ValueError):
+            raise ValidationError(detail=tool_validation_message)

Please verify with an API test that tool_id=not-a-uuid returns HTTP 400 on both endpoints.

Also applies to: 127-138

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

In `@backend/prompt_studio/prompt_studio_output_manager_v2/views.py` around lines
74 - 96, Add a UUID-shape guard that validates tool_id before any ORM usage:
attempt to parse request.GET.get("tool_id") with uuid.UUID(...) in a try/except
and if parsing fails raise
ValidationError(detail=PromptOutputManagerErrorMessage.TOOL_VALIDATION). Apply
this check in the current handler before building prompt_keys and before the
other query path referenced in the comment (the block using
ToolStudioPrompt.objects.filter and the similar block at lines 127-138), so no
malformed tool_id reaches the ORM; keep using the same ValidationError and
message to ensure the endpoint returns HTTP 400.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/prompt_studio/lookup_utils.py`:
- Around line 18-38: The ImportError guard misses the parent package name, so
when pluggable_apps is absent the exception is re-raised; update
_CLOUD_LOOKUP_MODULES to include "pluggable_apps" (in addition to the existing
entries) so the except block will treat missing the parent package as expected
and set LOOKUPS_AVAILABLE = False; ensure this change covers the import block
that imports execution/output_enrichment/staleness/validation and
LookupOutputResult (symbols: _CLOUD_LOOKUP_MODULES, _execution,
_output_enrichment, _staleness, _validation, _LookupOutputResult,
LOOKUPS_AVAILABLE, and the ImportError handling using e.name).

---

Duplicate comments:
In `@backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py`:
- Around line 1180-1182: The no-id legacy execution path in prompt_responder
still fails to pass lookup configs into the single-pass flow, so ensure
tool_settings["lookup_configs"] is populated for that path by calling
get_lookup_configs_for_tool(tool, prompts=prompts) and assigning the result to
tool_settings["lookup_configs"] before invoking
_execute_prompts_in_single_pass() (and consequently
_fetch_single_pass_response()); update prompt_responder to mirror the
build_single_pass_payload() change by computing lookup_configs from prompts and
attaching them to the tool_settings used in the no-id flow.

In `@backend/prompt_studio/prompt_studio_core_v2/views.py`:
- Around line 923-926: The code currently allows a client-supplied
prompt_studio_tool to be persisted because perform_create(serializer) uses
serializer data; before calling self.perform_create(serializer) (in the view
method surrounding get_object and serializer.validated_data), force the tool to
the detail object by setting
serializer.validated_data[ProfileManagerKeys.PROMPT_STUDIO_TOOL] =
self.get_object() or call serializer.save(prompt_studio_tool=self.get_object())
so the persisted ProfileManager always uses self.get_object() instead of any
caller-supplied value; alternatively, enforce the field is
read-only/permission-scoped on the serializer to prevent client override.
- Around line 99-110: The gate function _multi_var_lookup_block_response
currently checks custom_tool.single_pass_extraction_mode directly; change its
signature to accept an explicit is_single_pass boolean (e.g., def
_multi_var_lookup_block_response(custom_tool, prompt_ids=None,
is_single_pass=False)) and use that flag to decide skipping instead of
getattr(custom_tool, "single_pass_extraction_mode", False). Update each caller
(e.g., fetch_response and bulk_fetch_response) to pass the correct execution
path by computing and passing is_single_pass from the request context/parameters
rather than relying on tool settings. Ensure all call sites of
_multi_var_lookup_block_response are updated to include the new parameter to
avoid defaulting to the wrong behavior.

In `@backend/prompt_studio/prompt_studio_output_manager_v2/views.py`:
- Around line 74-96: Add a UUID-shape guard that validates tool_id before any
ORM usage: attempt to parse request.GET.get("tool_id") with uuid.UUID(...) in a
try/except and if parsing fails raise
ValidationError(detail=PromptOutputManagerErrorMessage.TOOL_VALIDATION). Apply
this check in the current handler before building prompt_keys and before the
other query path referenced in the comment (the block using
ToolStudioPrompt.objects.filter and the similar block at lines 127-138), so no
malformed tool_id reaches the ORM; keep using the same ValidationError and
message to ensure the endpoint returns HTTP 400.

In
`@backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py`:
- Around line 301-304: The code calls validate_lookups_for_export(prompts) over
the full prompts list which includes NOTES/inactive prompts and can block export
for items that are never emitted; change the call to run only on the subset of
exportable prompts (filter prompts to exclude NOTES/inactive entries before
calling validate_lookups_for_export) so that validate_lookups_for_export only
inspects prompts that will be exported and only raise InValidCustomToolError
when those exportable prompts fail validation.

In `@backend/usage_v2/internal_views.py`:
- Around line 164-193: The loop that constructs Usage objects from records (the
for r in records -> Usage(...) block) is too permissive and can mask producer
bugs; add explicit validation before building each Usage: verify required fields
(e.g., workflow_id, execution_id, usage_type, adapter_instance_id or run_id as
applicable) are present and of the expected type, coerce/validate numeric fields
(embedding_tokens, prompt_tokens, completion_tokens, total_tokens as ints;
cost_in_dollars as float), ensure model_name is non-empty when usage_type ==
"llm", and enforce the llm_usage_reason None/coercion rule; for records that
fail validation, log a clear error including the offending record and either
skip the record or collect/report them so the batch write isn't aborted by a
single malformed row, then only append validated Usage instances to
usage_objects.

In `@unstract/sdk1/src/unstract/sdk1/usage_handler.py`:
- Around line 108-112: The code dereferences self.token_counter (accessing
total_embedding_token_count) without checking for None, which can raise
AttributeError and skip usage persistence; fix by guarding token_counter in the
embedding callback(s): check if self.token_counter is not None before reading
self.token_counter.total_embedding_token_count (use 0 or an appropriate default
when None) and apply the same guard wherever total_embedding_token_count is
accessed (e.g., the lines referencing self.token_counter at the embedding
callback and at the later usage around line 154); ensure stream_log and any
usage persistence code use the guarded/ default value instead of assuming
token_counter exists.

In `@workers/executor/tasks.py`:
- Around line 113-130: The current flush path in tasks.py logs failures from
bulk_create_usage but swallows them, risking dropped billing rows; modify the
failure handling inside the block that calls bulk_create_usage so that on a
non-ok result or on exception you propagate an error to the caller (either by
re-raising the caught exception or raising a RecoverableError) instead of only
logging, so the orchestration can retry; keep the existing logger.error messages
(including run_id and organization_id) but follow them with a raise to fail the
task and preserve usage_records/context for retry handling.

In `@workers/ide_callback/tasks.py`:
- Around line 589-590: The code assigns extracted_text =
(result_dict.get("data") or {}).get("extracted_text", "") but if
data["extracted_text"] exists and is None len(extracted_text) will raise;
normalize extracted_text to a string before computing token_count by coercing
None to "" (e.g., replace None with "" for the extracted_text variable used in
token_count calculation in tasks.py), then compute token_count =
len(extracted_text) // 4 so len() is safe.
- Around line 567-572: The persistence calls to the API (e.g.,
api.mark_extraction_error called with source, file_id, organization_id/org_id
and similar calls at the other locations) must treat an HTTP 404 from the
extraction persistence endpoints as a terminal OSS no-op: wrap each api.*
persistence call (api.mark_extraction_error, the matching
api.mark_extraction_success/persist call(s) around lines ~592-598 and ~620-644)
in a try/except that catches the HTTP error type your client raises (e.g.,
HTTPError/ApiError) and if the caught exception indicates response.status_code
== 404, swallow it and return/exit this callback path instead of propagating;
for any other exception re-raise so existing error handling still applies.
Ensure you reference the same parameters (source, file_id,
org_id/organization_id, error_msg, extracted_files, checksum) when locating and
wrapping the calls.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce8433f8-3196-4387-824b-9de175c39622

📥 Commits

Reviewing files that changed from the base of the PR and between f9d3844 and 2fd9cf5.

📒 Files selected for processing (20)
  • .gitignore
  • backend/prompt_studio/lookup_utils.py
  • backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
  • backend/prompt_studio/prompt_studio_core_v2/views.py
  • backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py
  • backend/prompt_studio/prompt_studio_output_manager_v2/serializers.py
  • backend/prompt_studio/prompt_studio_output_manager_v2/views.py
  • backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py
  • backend/usage_v2/internal_views.py
  • backend/usage_v2/migrations/0006_alter_usage_status_and_more.py
  • backend/usage_v2/models.py
  • frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx
  • frontend/src/hooks/usePromptOutput.js
  • unstract/sdk1/src/unstract/sdk1/llm.py
  • unstract/sdk1/src/unstract/sdk1/usage_handler.py
  • workers/executor/executors/legacy_executor.py
  • workers/executor/executors/plugins/protocols.py
  • workers/executor/tasks.py
  • workers/ide_callback/tasks.py
  • workers/shared/clients/usage_client.py
✅ Files skipped from review due to trivial changes (2)
  • backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py
  • workers/executor/executors/legacy_executor.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .gitignore

Comment thread backend/prompt_studio/lookup_utils.py
Five threads on PR #1929 raised against the latest disposition push:

* prompt_studio_core_v2/views.py: drop the ``single_pass_extraction_mode``
  bypass from ``_multi_var_lookup_block_response`` — fetch_response /
  bulk_fetch_response are always non-SP, so the tool-setting check just
  let multi-var lookups slip past the gate when the tool happened to be
  configured for SP.

* prompt_studio_registry_helper.py: filter out NOTES + inactive prompts
  *before* calling ``validate_lookups_for_export`` so an incomplete
  lookup on a non-exportable prompt no longer fails the whole export.

* unstract/sdk1/usage_handler.py: guard ``self.token_counter is None``
  in the embedding-end branch — degrade with a warning instead of an
  AttributeError on early callbacks.

* workers/executor/legacy_executor.py: move the ``outcome.usage_records``
  / ``outcome.llm_metrics`` access inside the ``try`` so plugin contract
  drift hits the same graceful-degrade branch as a thrown ``run_with_metrics``.

* backend/prompt_studio/lookup_utils.py: include ``pluggable_apps`` itself
  in ``_CLOUD_LOOKUP_MODULES``. Pure OSS images don't have the parent
  package, so ``ImportError.name`` surfaces as ``"pluggable_apps"`` and
  the previous filter re-raised instead of setting LOOKUPS_AVAILABLE=False.
@chandrasekharan-zipstack
Copy link
Copy Markdown
Contributor Author

Round-2 disposition

OSS commit ba6c32ab0 addresses the five threads raised after the previous disposition.

Reviewer Path:Line Resolution
@coderabbitai prompt_studio_core_v2/views.py:123 (single-pass gate keys off tool setting) ba6c32ab0 — drop the single_pass_extraction_mode early-out from _multi_var_lookup_block_response. Both call sites (fetch_response, bulk_fetch_response) are non-SP execution paths; the SP entry point doesn't go through this helper, so the tool-setting check was a pure bypass
@coderabbitai prompt_studio_registry_helper.py:304 (validate_lookups_for_export runs before NOTES/inactive drop) ba6c32ab0 — filter to exportable prompts (prompt_type != NOTES and active) before passing into validate_lookups_for_export
@coderabbitai usage_handler.py:154 (token_counter not guarded) ba6c32ab0 — explicit if self.token_counter is None: warning + return before reading total_embedding_token_count / calling reset_counts
@greptile-apps legacy_executor.py:2099 (outcome.usage_records outside try) ba6c32ab0 — moved both attribute accesses inside the try so plugin contract drift hits the same graceful-degrade branch as a run_with_metrics exception
@coderabbitai lookup_utils.py:38 (_CLOUD_LOOKUP_MODULES misses parent package) ba6c32ab0 — added "pluggable_apps" itself to the set so pure OSS images (where ImportError.name == "pluggable_apps") set LOOKUPS_AVAILABLE=False instead of re-raising

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Tip

For best results, initiate chat on the files or code changes.

@chandrasekharan-zipstack — thank you for the clean round-2 accounting. All five items check out:

Item Verdict
views.py:123_multi_var_lookup_block_response no longer has the SP bypass ✅ Confirmed
prompt_studio_registry_helper.py:304 — exportable-prompt pre-filter before validate_lookups_for_export ✅ Confirmed
usage_handler.py:154 — explicit token_counter is None early-return guard ✅ Confirmed
legacy_executor.py:2099outcome.usage_records accesses moved inside try ✅ Confirmed
lookup_utils.py:38"pluggable_apps" added to _CLOUD_LOOKUP_MODULES sentinel set ✅ Confirmed

The "pluggable_apps" fix in particular closes a subtle gap — without it, a fresh OSS image missing the entire pluggable_apps namespace would re-raise rather than gracefully degrade to LOOKUPS_AVAILABLE = False, which would have caused hard startup failures in pure OSS deployments.

I'll resolve all my open threads now.

[resolve]

@athul-rs
Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Comment thread backend/usage_v2/internal_views.py
Comment thread backend/usage_v2/internal_views.py
Comment thread unstract/sdk1/src/unstract/sdk1/llm.py
Comment thread backend/prompt_studio/prompt_studio_core_v2/views.py Outdated
Comment thread backend/prompt_studio/prompt_studio_output_manager_v2/views.py
Comment thread frontend/src/components/custom-tools/document-parser/DocumentParser.jsx Outdated
Comment thread workers/executor/executors/legacy_executor.py Outdated
Comment thread workers/executor/executors/legacy_executor.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/views.py
Comment thread frontend/src/components/custom-tools/combined-output/CombinedOutput.jsx Outdated
Comment thread workers/ide_callback/tasks.py Outdated
Comment thread backend/usage_v2/migrations/0004_usage_metrics_fields.py
Comment thread frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx Outdated
Comment thread workers/executor/executors/legacy_executor.py
Comment thread workers/ide_callback/tasks.py Outdated
Comment thread workers/ide_callback/tasks.py Outdated
Comment thread workers/executor/executors/legacy_executor.py Outdated
Comment thread workers/executor/executors/legacy_executor.py Outdated
Comment thread unstract/sdk1/src/unstract/sdk1/utils/common.py
Comment thread backend/usage_v2/models.py
Comment thread frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx Outdated
Copy link
Copy Markdown
Contributor

@athul-rs athul-rs left a comment

Choose a reason for hiding this comment

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

A few nits from a follow-up pass — non-blocking.

Comment thread backend/prompt_studio/prompt_studio_core_v2/views.py Outdated
Comment thread workers/executor/executors/legacy_executor.py Outdated
Comment thread workers/executor/executors/legacy_executor.py Outdated
@athul-rs
Copy link
Copy Markdown
Contributor

nit (couldn't anchor inline — line is unchanged context): the docstring at unstract/sdk1/src/unstract/sdk1/usage_handler.py#L100 still says "Push the usage of Embedding to platform service." but on_event_end no longer pushes — it appends to self._pending_usage for deferred flushing. Worth updating the docstring to match the new contract (e.g. "Record embedding usage for deferred flush.").

Copy link
Copy Markdown
Contributor

@athul-rs athul-rs left a comment

Choose a reason for hiding this comment

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

@chandrasekharan-zipstack please chekc the NIt comments added

Drop archaeology / "previously / before-X-now-Y" framing; collapse
multi-line WHAT walkthroughs to single-line WHY. No logic changes.
…ient

Pairs with the cloud-side removal of the lookup token_count /
estimated_tokens fields — the worker no longer computes a value to send.
…ration

- UsageBatchCreateView raises DRF exceptions (drf-standardized-errors
  envelope) instead of hand-rolled JsonResponse — serializer validation
  via raise_exception=True, dedicated UsagePersistError(APIException)
  for the bulk_create failure path.
- Records validated through UsageBatchCreateSerializer / nested
  UsageRecordCreateSerializer so adapter_instance_id, model_name,
  usage_type are required and the rest get explicit defaults.
- Fold 0006_alter_usage_status_and_more (UsageStatus choices +
  reference_pair CheckConstraint) into 0004 — branch hasn't merged to
  main, so squashing avoids an extra ALTER on deploy.
…helpers

- llm.py: token_counter fallback when prompt_tokens=0; rsplit('/',1) for
  multi-segment provider IDs; spread _usage_kwargs first so explicit
  billing fields win.
- utils/common.py: stamp every record appended during the call window
  (was clobbering only the last entry).
- legacy_executor.py: extract run_lookup_enrichment / run_webhook_postprocessing
  / is_blank into workers/executor/executors/lookup_enrichment.py — caller
  passes shim/state in, plugin returns usage_records for the caller to
  extend its billing batch. Orchestrator stays pure dispatch.
- ide_callback/tasks.py: drop the char-÷4 token estimate heuristic;
  context-manage the API clients so HTTP sessions don't leak.
- prompt_studio/views.py: _multi_var_lookup_block_response uses
  ``detail`` instead of ``error`` to match drf-standardized-errors.
- CombinedOutput.jsx: hoist build helpers to module scope (selectedProfile
  passed as arg) — no per-render allocation, fewer useEffect closures.
- DocumentParser.jsx: hoist UUID_RE to module scope (no per-render compile).
- PromptOutput.jsx: silent ``catch {}`` on plugin dynamic-import failures
  so OSS doesn't surface noisy warnings for cloud-only modules.
- SideNavBar.jsx: hide the lookups submenu item when the lookup-studio
  plugin isn't loaded (keeps OSS nav clean).
Comment thread backend/usage_v2/serializers.py Outdated
chandrasekharan-zipstack and others added 4 commits May 5, 2026 15:47
…izer

Both columns are UUIDField on the Usage model — leaving them as
CharField in the serializer let invalid UUIDs slip through to bulk_create
and surface as a 500. UUIDField catches them at validation with a
standard DRF 400.
Drops the stale "Computes token count" line — the callback no longer
derives a token count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
If _validate_file_execution_id raised, the except handler hit
UnboundLocalError and masked the original ValueError. Pre-bind a
str(file_execution_id) fallback so the error response carries the
real cause. Also gitignore Codex's AGENTS.md scratchpad.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@harini-venkataraman harini-venkataraman left a comment

Choose a reason for hiding this comment

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

LGTM

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jaseemjaskp jaseemjaskp self-requested a review May 7, 2026 06:16
Copy link
Copy Markdown
Contributor

@jaseemjaskp jaseemjaskp left a comment

Choose a reason for hiding this comment

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

Re-reviewed with the multi-agent toolkit (Code Reviewer, Silent Failure Hunter, Type Design Analyzer, Comment Analyzer, PR Test Analyzer) against the latest HEAD. All previously-raised concerns from my prior pass have been addressed in subsequent commits — the salvage block, billing-flush ERROR promotion, JSON-parse webhook gate, broad-except narrowing, run_id/UUID coercion, and lookup helper extraction all look good. New agent findings either duplicate already-posted reviewer threads or fall below the bar I'm holding for this round (test coverage gaps and stricter serializer choice validation are worth follow-ups but don't block the merge). Approving.

…olumns + post-write hooks

Drop reference_id / reference_type from Usage in favour of typed
project_id / prompt_id columns indexed CONCURRENTLY for dashboard
rollups. Cloud-only attribution (e.g. lookup_version_id) now flows
through an opaque cloud_extras carrier on the batch endpoint, which
forwards it verbatim to plugin-registered post-write hooks invoked
inside the same atomic transaction as bulk_create — a hook failure
rolls back the Usage rows so attribution stays consistent or nothing
is written.

Removes the need for cloud to subclass UsageBatchCreateView or prepend
a URL override; the hook seam is generic for any future cloud feature.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 245 passed, 0 failed (245 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/file\_storage/test\_impl\_rm.py}}$$ $$\textcolor{#23d18b}{\tt{TestRmHappyPath.test\_bulk\_delete\_succeeds}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/file\_storage/test\_impl\_rm.py}}$$ $$\textcolor{#23d18b}{\tt{TestRmFallback.test\_missing\_md5\_triggers\_individual\_delete\_via\_rm\_file}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/file\_storage/test\_impl\_rm.py}}$$ $$\textcolor{#23d18b}{\tt{TestRmFallback.test\_fallback\_continues\_on\_per\_file\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/file\_storage/test\_impl\_rm.py}}$$ $$\textcolor{#23d18b}{\tt{TestRmFallback.test\_fallback\_swallows\_rmdir\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/file\_storage/test\_impl\_rm.py}}$$ $$\textcolor{#23d18b}{\tt{TestRmFallback.test\_non\_md5\_error\_propagates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/file\_storage/test\_impl\_rm.py}}$$ $$\textcolor{#23d18b}{\tt{TestRmFallback.test\_md5\_error\_without\_recursive\_propagates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/file\_storage/test\_impl\_rm.py}}$$ $$\textcolor{#23d18b}{\tt{TestFallbackDoesNotReenterBulkDelete.test\_only\_singular\_delete\_called}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_timeout\_passed\_to\_client\_post}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_none\_timeout\_passed\_to\_client\_post}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestPatchedEmbeddingSyncTimeoutForwarding.test\_httpx\_timeout\_object\_forwarded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_cohere\_handler\_patched}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_bedrock\_handler\_patched}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/patches/test\_litellm\_cohere\_timeout.py}}$$ $$\textcolor{#23d18b}{\tt{TestMonkeyPatchApplied.test\_patch\_module\_loaded\_via\_embedding\_import}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_legacy\_no\_auth\_type\_keeps\_keys}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_access\_keys\_mode\_keeps\_keys\_and\_strips\_auth\_type}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_iam\_role\_mode\_drops\_keys\_even\_when\_present}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_iam\_role\_mode\_with\_no\_keys}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_access\_keys\_mode\_blank\_keys\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_access\_keys\_mode\_whitespace\_keys\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_unknown\_auth\_type\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_llm\_other\_params\_preserved\_through\_strip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_embedding\_legacy\_no\_auth\_type\_keeps\_keys}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_embedding\_access\_keys\_mode\_keeps\_keys}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_embedding\_iam\_role\_mode\_drops\_stale\_keys}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_embedding\_iam\_role\_mode\_with\_no\_keys}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_embedding\_access\_keys\_mode\_blank\_keys\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_embedding\_unknown\_auth\_type\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_bedrock\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_embedding\_region\_required\_when\_absent}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_round\_trip\_serialization}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_json\_serializable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_enum\_values\_normalized}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_string\_values\_accepted}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_auto\_generates\_request\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_explicit\_request\_id\_preserved}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_optional\_organization\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_empty\_executor\_params\_default}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_complex\_executor\_params}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_validation\_rejects\_empty\_required\_fields}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_all\_operations\_accepted}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionContext.test\_from\_dict\_missing\_optional\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_success\_round\_trip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_round\_trip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_json\_serializable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_requires\_error\_message}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_success\_allows\_no\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_success\_rejects\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_factory}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_failure\_factory\_no\_metadata}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_error\_none\_in\_success\_dict}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_error\_in\_failure\_dict}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_default\_empty\_dicts}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_from\_dict\_missing\_optional\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_response\_contract\_extract}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_response\_contract\_index}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionResult.test\_response\_contract\_answer\_prompt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestBaseExecutor.test\_cannot\_instantiate\_abstract}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestBaseExecutor.test\_concrete\_subclass\_works}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestBaseExecutor.test\_execute\_returns\_result}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_and\_get}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_get\_returns\_fresh\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_as\_decorator}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_list\_executors}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_list\_executors\_empty}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_get\_unknown\_raises\_key\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_get\_unknown\_lists\_available}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_duplicate\_name\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_non\_subclass\_raises\_type\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_register\_non\_class\_raises\_type\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_clear}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorRegistry.test\_execute\_through\_registry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_dispatches\_to\_correct\_executor}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_unknown\_executor\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_executor\_exception\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_exception\_result\_has\_elapsed\_metadata}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_successful\_result\_passed\_through}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionOrchestrator.test\_executor\_returning\_failure\_is\_not\_wrapped}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_sends\_task\_and\_returns\_result}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_uses\_default\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_timeout\_from\_env}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_explicit\_timeout\_overrides\_env}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_timeout\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_generic\_exception\_returns\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_async\_returns\_task\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_no\_app\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_async\_no\_app\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_failure\_result\_from\_executor}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_context\_serialized\_correctly}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_sends\_link\_and\_link\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_success\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_error\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_no\_callbacks}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_returns\_async\_result}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_no\_app\_raises\_value\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_context\_serialized}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_custom\_task\_id}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutionDispatcher.test\_dispatch\_with\_callback\_no\_task\_id\_omits\_kwarg}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_platform\_api\_key\_returned}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_platform\_api\_key\_missing\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_other\_env\_var\_from\_environ}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_missing\_env\_var\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_empty\_env\_var\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_log\_routes\_to\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_log\_respects\_level}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_error\_and\_exit\_raises\_sdk\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_execution.py}}$$ $$\textcolor{#23d18b}{\tt{TestExecutorToolShim.test\_stream\_error\_and\_exit\_wraps\_original}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_model\_prefixes\_when\_missing}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_model\_does\_not\_double\_prefix}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_model\_blank\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_disabled\_by\_default}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_excludes\_control\_fields\_from\_model}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_enabled\_with\_budget}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_overrides\_user\_temperature}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_enabled\_without\_budget\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_budget\_tokens\_invalid\_type\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_thinking\_budget\_tokens\_too\_small\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_preserves\_existing\_thinking\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_validate\_does\_not\_mutate\_input}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_thinking\_controls\_not\_pydantic\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_api\_key\_is\_required}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_adapter\_identity}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_schema\_required\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_schema\_enable\_thinking\_default\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_adapter.py}}$$ $$\textcolor{#23d18b}{\tt{test\_schema\_budget\_tokens\_conditional}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_adapter\_registration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_get\_id\_format}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_get\_adapter\_type}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_get\_name}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_get\_provider}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_json\_schema\_loads}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_json\_schema\_required\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_json\_schema\_no\_batch\_size\_default}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_json\_schema\_api\_key\_password\_format}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_json\_schema\_model\_default}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_model\_adds\_prefix}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_model\_idempotent}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_model\_does\_not\_mutate\_input}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_does\_not\_mutate\_input}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_model\_empty\_string\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_model\_whitespace\_only\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_model\_none\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_model\_missing\_key\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_empty\_model\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_none\_model\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_missing\_api\_key\_raises}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_calls\_validate\_model}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_embed\_batch\_size\_none\_by\_default}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_embed\_batch\_size\_preserved}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_strips\_extra\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_validate\_includes\_base\_fields}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_gemini\_embedding.py}}$$ $$\textcolor{#23d18b}{\tt{TestGeminiEmbeddingAdapter.test\_metadata}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_reuses\_llm\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_returns\_llmcompat\_instance}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_sets\_model\_name}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatFromLlm.test\_from\_llm\_does\_not\_call\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_complete\_delegates\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_chat\_delegates\_to\_llm\_complete}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_chat\_forwards\_kwargs\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_complete\_forwards\_kwargs\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_acomplete\_delegates\_to\_llm}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_achat\_delegates\_to\_llm\_acomplete}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_stream\_chat\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_stream\_complete\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_astream\_chat\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_astream\_complete\_not\_implemented}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_metadata\_returns\_emulated\_type}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_get\_model\_name\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_get\_metrics\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestLLMCompatDelegation.test\_test\_connection\_delegates}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_message\_role\_values}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_chat\_message\_defaults}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_chat\_response\_message\_access}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_completion\_response\_text}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestEmulatedTypes.test\_llm\_metadata\_defaults}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_single\_user\_message}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_none\_content\_becomes\_empty\_string}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_preserves\_all\_messages}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_multi\_turn\_conversation}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_empty\_messages\_returns\_empty\_string}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_llm\_compat.py}}$$ $$\textcolor{#23d18b}{\tt{TestMessagesToPrompt.test\_string\_role\_fallback}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{245}}$$ $$\textcolor{#23d18b}{\tt{245}}$$

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 8, 2026

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.

4 participants