Skip to content

UN-3403 [FEAT] Agentic table extractor plugin with multi-agent LLM-powered table extraction#1914

Open
harini-venkataraman wants to merge 105 commits intomainfrom
feat/agentic-table-extractor
Open

UN-3403 [FEAT] Agentic table extractor plugin with multi-agent LLM-powered table extraction#1914
harini-venkataraman wants to merge 105 commits intomainfrom
feat/agentic-table-extractor

Conversation

@harini-venkataraman
Copy link
Copy Markdown
Contributor

What

  • Adds a new Agentic Table Extractor plugin that uses multi-agent LLM orchestration to detect, extract, and structure tabular data from documents
  • Introduces AgenticTableSettings CRUD backend (pluggable app) with per-prompt configuration for the extractor (LLM adapter, page range, parallel pages, highlight toggle)
  • Adds frontend components: AgenticTableSettings modal for configuring the extractor and AgenticTableChecklist for real-time prompt readiness validation
  • Integrates the extractor into the existing Prompt Studio IDE execution pipeline via Celery async dispatch

Why

  • Existing table extraction approaches (regex, template-based) struggle with complex, unstructured, or multi-format tables across diverse document types
  • An agentic approach with specialized agents (presence detection, table detection, content extraction, code generation, code execution) enables more accurate and schema-conforming extraction
  • This is a cloud-only feature that extends Prompt Studio's enforce type options with agentic_table

How

Backend

  • New agentic_table_settings_v2 pluggable app with model, serializer, views, URL routing, and validation service
  • AgenticTableSettingsViewSet — full CRUD with update_or_create semantics; returns saved instance (with id) so frontend can PATCH
  • PromptValidationView — LLM-powered prompt analysis endpoint that checks whether a prompt contains target table, JSON structure, and instructions; uses get_or_create to avoid 404 chicken-and-egg issues
  • Payload modifier extended to build agentic_table execution payloads with adapter UUIDs from profile
  • Celery dispatch via backend app with dedicated agentic_table queue

Can this PR break any existing features? If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • Low risk to existing features. All changes are additive:
    • New pluggable app with its own URL namespace (/prompt-studio/prompt/agentic-table/)
    • New worker plugin registered under the agentic_table enforce type — no existing enforce types are modified
    • Frontend components only render when enforceType === "agentic_table" is selected
    • The create view status code change (201 for new, 200 for update) is internal to this feature
    • Payload modifier changes are gated behind the agentic_table type check
  • Possible concern: The helm chart changes add a new queue — existing deployments will need to pick up the new worker configuration

Relevant Docs

  • UN-3266 Jira ticket

Related Issues or PRs

- UN-3403

Dependencies Versions / Env Variables

  • No new environment variables required
  • Requires a configured LiteLLM adapter instance for the validation and extraction to function

Notes on Testing

Backend Tests

Run the agentic table settings test suite:

cd backend && .venv/bin/python -m pytest pluggable_apps/apps/agentic_table_settings_v2/ -v

Manual Testing

  1. Settings persistence: Open agentic table settings gear -> save -> refresh -> reopen -> verify values persist and "Update" button shows
  2. Checklist persistence: Type prompt with all 3 components -> wait for green checkboxes -> refresh -> verify checkboxes restore immediately
  3. Validation flow: Select agentic_table on a fresh prompt card -> type prompt -> verify no 404 -> configure LLM adapter -> verify checkboxes update
  4. End-to-end extraction: Configure a prompt with agentic_table enforce type, set up LLM adapter, run extraction on a document with tables

Screenshots

Attached in respective cloud PR.

...

Checklist

I have read and understood the Contribution Guidelines.

harini-venkataraman and others added 30 commits February 19, 2026 20:39
Conflicts resolved:
- docker-compose.yaml: Use main's dedicated dashboard_metric_events queue for worker-metrics
- PromptCard.jsx: Keep tool_id matching condition from our async socket feature
- PromptRun.jsx: Merge useEffect import from main with our branch
- ToolIde.jsx: Keep fire-and-forget socket approach (spinner waits for socket event)
- SocketMessages.js: Keep both session-store and socket-custom-tool imports + updateCusToolMessages dep
- SocketContext.js: Keep simpler path-based socket connection approach
- usePromptRun.js: Keep Celery fire-and-forget with socket delivery over polling
- setupProxy.js: Accept main's deletion (migrated to Vite)
harini-venkataraman and others added 13 commits March 23, 2026 21:43
- Route _handle_structure_pipeline to _handle_single_pass_extraction when
  is_single_pass=True (was always calling _handle_answer_prompt)
- Delegate _handle_single_pass_extraction to cloud plugin via ExecutorRegistry,
  falling back to _handle_answer_prompt if plugin not installed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new complete_vision() method alongside existing complete() that
accepts pre-built multimodal messages (text + image_url) in OpenAI-style
format. LiteLLM auto-translates for Anthropic/Bedrock/Vertex providers.
This enables the agentic table extractor plugin to send page images
alongside text prompts for VLM-based table detection and extraction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- PromptCardItems loads AgenticTableChecklist plugin and owns the
  isAgenticTableReady state, rendering the checklist above the prompt
  text area and delegating the settings gear visibility to the plugin.
- Header and PromptOutput disable their Run buttons when
  isAgenticTableReady is false (default true for non-agentic types).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	backend/api_v2/deployment_helper.py
#	backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
#	backend/prompt_studio/prompt_studio_core_v2/views.py
#	docker/docker-compose.yaml
#	docker/sample.compose.override.yaml
#	frontend/src/components/custom-tools/prompt-card/DisplayPromptResult.jsx
#	frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx
#	frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx
#	frontend/src/components/custom-tools/tools-main/ToolsMain.jsx
#	frontend/src/hooks/usePromptRun.js
#	frontend/src/hooks/usePromptStudioSocket.js
#	unstract/sdk1/src/unstract/sdk1/execution/dispatcher.py
#	unstract/sdk1/src/unstract/sdk1/execution/result.py
#	unstract/sdk1/src/unstract/sdk1/llm.py
#	unstract/sdk1/tests/test_execution.py
#	uv.lock
#	workers/executor/README.md
#	workers/executor/executors/index.py
#	workers/executor/executors/legacy_executor.py
#	workers/executor/executors/retrievers/base_retriever.py
#	workers/executor/executors/retrievers/fusion.py
#	workers/executor/executors/retrievers/keyword_table.py
#	workers/executor/executors/retrievers/router.py
#	workers/executor/executors/retrievers/subquestion.py
#	workers/executor/tasks.py
#	workers/executor/worker.py
#	workers/file_processing/structure_tool_task.py
#	workers/run-worker-docker.sh
#	workers/shared/enums/task_enums.py
#	workers/shared/enums/worker_enums_base.py
#	workers/shared/infrastructure/config/registry.py
#	workers/tests/test_answer_prompt.py
#	workers/tests/test_retrieval.py
ToolStudioPrompt uses prompt_id as its primary key, not id.
Count("id") causes FieldError on the list endpoint (500).

Co-authored-by: Chandrasekharan M <chandrasekharan@zipstack.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cloud build adds "agentic_table" to the prompt enforce_type
dropdown, but the OSS ToolStudioPrompt model rejected it as an
invalid choice. Add AGENTIC_TABLE to EnforceType and ship a
matching migration so the value can be persisted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The single-prompt run flow had no branch for prompts with
enforce_type=agentic_table, so clicking Run silently fell through to
the legacy prompt-service path and never invoked the agentic_table
executor. Adds an AGENTIC_TABLE constant to TSPKeys, includes it in
the OperationNotSupported guard, and dispatches to
PayloadModifier.execute_agentic_table when the plugin is available
so the result still flows through _handle_response.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ExecutionDispatcher derives the queue name from the executor name
(celery_executor_{name}), so dispatches to the agentic_table executor
land on celery_executor_agentic_table. The local docker-compose default
only listed celery_executor_legacy and celery_executor_agentic, so no
worker consumed the new queue and dispatch hung for the full 1-hour
result timeout. Adds the missing queue to the docker-compose default.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The IDE Run button was building a legacy answer_prompt payload for
agentic_table prompts, so the agentic table executor was never
invoked. Branch fetch_response on enforce_type so agentic_table
prompts are built via the cloud payload_modifier plugin and
dispatched directly to celery_executor_agentic_table. Add the
enforce_type to the OSS dropdown choices and the JSON-dump set in
OutputManagerHelper so the persisted output is parseable by the FE
table renderer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The agentic_table executor returns {"output": {"tables": [...],
"page_count": ..., "headers": [...], ...}}, but
OutputManagerHelper.handle_prompt_output_update reads
outputs[prompt.prompt_key] when persisting prompt output. Without a
reshape the table list never lands under the prompt key and the FE
sees an empty result.

When cb_kwargs carries is_agentic_table=True and prompt_key (set by
the cloud build_agentic_table_payload), reshape outputs to
{prompt_key: tables} before calling update_prompt_output. The
executor itself also shapes its envelope, so this is a defensive
double-keying that keeps the legacy answer_prompt path untouched.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced "agentic table" as a new response output type for structured data extraction
    • Added readiness validation UI component for agentic table configurations before execution
    • Added multimodal vision completion capability to LLM module
  • Improvements

    • Extended socket timeout from 5 to 16 minutes to accommodate longer-running operations

Walkthrough

The pull request introduces a new "agentic_table" response type throughout the prompt studio system. Changes include adding the new constant across backend modules, updating execution pipelines to dispatch agentic table prompts through specialized handlers, modifying output management and registry export logic, adding frontend UI readiness states, extending worker executors with agentic table handling, and updating infrastructure configuration to support new execution queues.

Changes

Cohort / File(s) Summary
Core Constants
backend/prompt_studio/prompt_studio_core_v2/constants.py, backend/prompt_studio/prompt_studio_registry_v2/constants.py, backend/prompt_studio/prompt_studio_v2/models.py, backend/prompt_studio/prompt_studio_core_v2/static/select_choices.json
Added AGENTIC_TABLE = "agentic_table" constant to multiple modules and extended the EnforceType enum; added corresponding UI output type choice mapping.
Backend Execution Pipeline
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py, backend/prompt_studio/prompt_studio_core_v2/views.py
Updated _execute_single_prompt to treat agentic table as an enforced type requiring payload_modifier plugin, instantiating it to call execute_agentic_table(...); modified fetch_response to add dedicated dispatch path for agentic table prompts via dispatcher.dispatch_with_callback(...) returning HTTP 202, and excluded these prompts from single-pass extraction eligibility.
Output & Registry Management
backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py, backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py
Updated update_or_create_prompt_output to treat agentic table as a structured type requiring JSON serialization; added conditional export path in frame_export_json to call export_agentic_table_settings(...) when plugin is available.
Frontend UI Components
frontend/src/components/custom-tools/prompt-card/Header.jsx, frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx, frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx
Added isAgenticTableReady state and prop plumbing across components; conditionally rendered AgenticTableChecklist plugin; extended button disabled logic to block execution when readiness is false.
Worker Execution Processing
workers/executor/executors/legacy_executor.py, workers/file_processing/structure_tool_task.py, workers/ide_callback/tasks.py
Added agentic table type-conversion bypass in legacy executor; partitioned structure tool outputs into agentic/regular cohorts with separate dispatch phases, validating agentic settings; reshaped IDE callback output structure for agentic table results keyed by prompt name.
Utilities & SDK Extensions
unstract/sdk1/src/unstract/sdk1/llm.py, workers/executor/executors/retrievers/fusion.py, workers/executor/executors/retrievers/keyword_table.py, frontend/src/hooks/usePromptRun.js
Added complete_vision() method for multimodal chat completions; added inline comments on LLM context in retriever constructors; increased socket timeout from 5 to 16 minutes with documentation.
Infrastructure & Schema
backend/prompt_studio/prompt_studio_v2/migrations/0014_alter_toolstudioprompt_enforce_type.py, docker/docker-compose.yaml
Added Django migration updating enforce_type field with new choices; extended executor service CELERY_QUEUES_EXECUTOR to include celery_executor_agentic_table queue.
Tests
workers/tests/test_answer_prompt.py
Updated test expectations for invalid retrieval strategy and null-value sanitization to assert None instead of string "NA".

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Frontend as Frontend UI
    participant Views as Backend Views
    participant Helper as Backend Helper
    participant Dispatcher as Task Dispatcher
    participant Executor as Worker Executor
    participant Callback as IDE Callback
    participant Output as Output Manager
    
    User->>Frontend: Trigger agentic table prompt run
    Frontend->>Views: POST fetch_response (enforce_type=AGENTIC_TABLE)
    Views->>Views: Load payload_modifier plugin
    Views->>Views: Build agentic_table_payload via modifier
    Views->>Views: Generate executor_task_id and dispatch_time
    Views->>Dispatcher: dispatch_with_callback(task, callback_config)
    Dispatcher-->>Views: Return task_id
    Views-->>Frontend: HTTP 202 ACCEPTED {task_id}
    Dispatcher->>Executor: Execute agentic table extraction
    Executor->>Executor: Partition outputs (agentic vs regular)
    Executor->>Executor: Dispatch agentic prompts individually
    Executor-->>Dispatcher: Return structured results
    Dispatcher->>Callback: ide_prompt_complete(results)
    Callback->>Callback: Reshape output {prompt_key: tables}
    Callback->>Output: update_prompt_output(reshaped_output)
    Output->>Output: JSON serialize for agentic_table type
    Output-->>Callback: Confirm storage
    Callback-->>Frontend: Notify completion
    Frontend-->>User: Display results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introduction of a new Agentic Table Extractor plugin with multi-agent LLM-powered table extraction, which is the core feature across all modified files.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering What, Why, How, risk assessment, documentation, dependencies, testing, and a completed checklist—all required template sections are present and filled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agentic-table-extractor

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.

harini-venkataraman and others added 3 commits April 15, 2026 14:28
Signed-off-by: harini-venkataraman <115449948+harini-venkataraman@users.noreply.github.com>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
11.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 15, 2026

Greptile Summary

This PR adds an Agentic Table Extractor feature — a new agentic_table enforce type that routes table-extraction prompts through a dedicated multi-agent LLM pipeline instead of the legacy answer-prompt path. Changes are additive across backend (new pluggable app, migration, dispatch logic), workers (partition + dispatch in structure_tool_task, defensive guard in legacy_executor), SDK (complete_vision method on LLM), and frontend (checklist readiness gate, settings modal).

Confidence Score: 4/5

Safe to merge after addressing the two P2 contract/correctness concerns (global litellm mutation and is_agentic_table flag visibility).

All three flagged items are P2: a global litellm state mutation in complete_vision, reliance on a closed-source plugin to set the is_agentic_table flag (silent empty-output risk), and the TableExtractionSettingsBtn guard removal. None are likely to break existing prompts since all new code paths are gated behind enforce_type == agentic_table. The email NA-handling change is intentional and tested. Score is 4 rather than 5 because the is_agentic_table flag dependency, if the plugin forgets to set it, results in silently persisting empty output with no error.

unstract/sdk1/src/unstract/sdk1/llm.py (global litellm mutation) and workers/ide_callback/tasks.py (flag dependency on plugin).

Important Files Changed

Filename Overview
backend/prompt_studio/prompt_studio_core_v2/views.py Adds an agentic table dispatch path in fetch_response; logic is consistent with the existing regular path but is_agentic_table flag in cb_kwargs relies on the closed-source plugin to set it
unstract/sdk1/src/unstract/sdk1/llm.py Adds complete_vision method; sets litellm.drop_params = True as a global side-effect that persists for the process and affects all subsequent complete() calls
workers/file_processing/structure_tool_task.py Partitions prompts into agentic vs regular and dispatches them separately; mixed-case merge logic (setdefault("output", {}).update(agentic_results)) is correct
workers/ide_callback/tasks.py Output reshape for agentic executor response depends on is_agentic_table flag being set by the closed-source plugin; if omitted, persisted output will be empty
workers/executor/executors/legacy_executor.py Adds defensive guard for agentic_table prompts; also refactors email "NA" handling via _convert_scalar_answer (intentional behavioral change confirmed by updated tests)
frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx Adds AgenticTableChecklist plugin import and isAgenticTableReady gate; removes enforceType === TABLE outer guard on TableExtractionSettingsBtn, which now renders for all enforce types
backend/prompt_studio/prompt_studio_v2/migrations/0014_alter_toolstudioprompt_enforce_type.py Adds agentic_table choice to the enforce_type field; migration is additive and safe
frontend/src/hooks/usePromptRun.js Increases socket timeout from 5 min to 16 min to match the server-side 900 s LLM adapter timeout; well-commented and correct
workers/tests/test_answer_prompt.py Updates tests to reflect intentional behavioral change: email "NA" answers now become None instead of being preserved as-is

Sequence Diagram

sequenceDiagram
    participant FE as Frontend (PromptCardItems)
    participant BE as Backend (views.py fetch_response)
    participant PM as PayloadModifier Plugin
    participant EX as Celery: agentic_table executor
    participant CB as Celery: ide_callback

    FE->>BE: POST fetch_response (enforce_type=agentic_table)
    BE->>PM: build_agentic_table_payload(...)
    PM-->>BE: context, cb_kwargs (incl. is_agentic_table, prompt_key)
    BE->>EX: dispatch_with_callback(context, task_id)
    BE-->>FE: 202 Accepted {task_id, run_id}
    EX-->>CB: on_success → ide_prompt_complete(result_dict, cb_kwargs)
    CB->>CB: reshape output {tables:[...]} → {prompt_key: tables}
    CB->>BE: update_prompt_output(outputs, prompt_ids, ...)
    FE-->>FE: WebSocket notification → render output
Loading

Comments Outside Diff (2)

  1. unstract/sdk1/src/unstract/sdk1/llm.py, line 517 (link)

    P2 Global drop_params mutation affects all litellm calls

    litellm.drop_params = True is a module-level global that persists for the entire process lifetime. After the first complete_vision() call, every subsequent litellm.completion() from complete() will also silently drop unknown parameters, even though complete() was never designed with that expectation. This can hide parameter-mismatch errors that would otherwise be surfaced. Pass drop_params=True per-call instead:

    And remove the litellm.drop_params = True line at line 517.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: unstract/sdk1/src/unstract/sdk1/llm.py
    Line: 517
    
    Comment:
    **Global `drop_params` mutation affects all litellm calls**
    
    `litellm.drop_params = True` is a module-level global that persists for the entire process lifetime. After the first `complete_vision()` call, every subsequent `litellm.completion()` from `complete()` will also silently drop unknown parameters, even though `complete()` was never designed with that expectation. This can hide parameter-mismatch errors that would otherwise be surfaced. Pass `drop_params=True` per-call instead:
    
    
    
    And remove the `litellm.drop_params = True` line at line 517.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx, line 397-398 (link)

    P2 TableExtractionSettingsBtn now renders for every enforce type

    The enforceType === TABLE guard was removed, so TableExtractionSettingsBtn is rendered for all enforce types (text, json, boolean, date, …), not just table and agentic_table. Because the component receives enforceType as a prop it can self-gate, but if the plugin implementation does not check enforceType internally, users will see the table-settings gear on every prompt card regardless of type. Was the intent to show it only for table and agentic_table, and has the plugin been updated to do that check? Does the TableExtractionSettingsBtn plugin component internally check enforceType (e.g., only render for table / agentic_table), or should the outer guard still limit rendering to those two types?

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx
    Line: 397-398
    
    Comment:
    **`TableExtractionSettingsBtn` now renders for every enforce type**
    
    The `enforceType === TABLE` guard was removed, so `TableExtractionSettingsBtn` is rendered for all enforce types (text, json, boolean, date, …), not just `table` and `agentic_table`. Because the component receives `enforceType` as a prop it can self-gate, but if the plugin implementation does not check `enforceType` internally, users will see the table-settings gear on every prompt card regardless of type. Was the intent to show it only for `table` and `agentic_table`, and has the plugin been updated to do that check? Does the `TableExtractionSettingsBtn` plugin component internally check `enforceType` (e.g., only render for `table` / `agentic_table`), or should the outer guard still limit rendering to those two types?
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: unstract/sdk1/src/unstract/sdk1/llm.py
Line: 517

Comment:
**Global `drop_params` mutation affects all litellm calls**

`litellm.drop_params = True` is a module-level global that persists for the entire process lifetime. After the first `complete_vision()` call, every subsequent `litellm.completion()` from `complete()` will also silently drop unknown parameters, even though `complete()` was never designed with that expectation. This can hide parameter-mismatch errors that would otherwise be surfaced. Pass `drop_params=True` per-call instead:

```suggestion
            response: dict[str, object] = litellm.completion(
                messages=messages,
                drop_params=True,
                **completion_kwargs,
            )
```

And remove the `litellm.drop_params = True` line at line 517.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx
Line: 397-398

Comment:
**`TableExtractionSettingsBtn` now renders for every enforce type**

The `enforceType === TABLE` guard was removed, so `TableExtractionSettingsBtn` is rendered for all enforce types (text, json, boolean, date, …), not just `table` and `agentic_table`. Because the component receives `enforceType` as a prop it can self-gate, but if the plugin implementation does not check `enforceType` internally, users will see the table-settings gear on every prompt card regardless of type. Was the intent to show it only for `table` and `agentic_table`, and has the plugin been updated to do that check?

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: workers/ide_callback/tasks.py
Line: 399-403

Comment:
**`is_agentic_table` flag not visibly set in `cb_kwargs`**

The reshape at line 399–403 only fires when `cb.get("is_agentic_table")` is truthy. That flag must be injected into `cb_kwargs` by `modifier.build_agentic_table_payload(...)` in the cloud plugin. If the plugin omits it (or changes its key name), the agentic executor's `{"tables": [...]}` payload will be forwarded verbatim to `update_prompt_output`, which will find no value under `prompt_key` and persist an empty result silently. Consider adding an explicit `cb_kwargs["is_agentic_table"] = True` in `views.py` after the `modifier.build_agentic_table_payload` call — it keeps the contract visible in the open-source code and doesn't depend on the plugin remembering to set it.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Fixing syntax issues" | Re-trigger Greptile

Comment on lines +399 to +403
if cb.get("is_agentic_table"):
prompt_key = cb.get("prompt_key", "")
if prompt_key:
tables = outputs.get("tables", []) if isinstance(outputs, dict) else []
outputs = {prompt_key: tables}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 is_agentic_table flag not visibly set in cb_kwargs

The reshape at line 399–403 only fires when cb.get("is_agentic_table") is truthy. That flag must be injected into cb_kwargs by modifier.build_agentic_table_payload(...) in the cloud plugin. If the plugin omits it (or changes its key name), the agentic executor's {"tables": [...]} payload will be forwarded verbatim to update_prompt_output, which will find no value under prompt_key and persist an empty result silently. Consider adding an explicit cb_kwargs["is_agentic_table"] = True in views.py after the modifier.build_agentic_table_payload call — it keeps the contract visible in the open-source code and doesn't depend on the plugin remembering to set it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: workers/ide_callback/tasks.py
Line: 399-403

Comment:
**`is_agentic_table` flag not visibly set in `cb_kwargs`**

The reshape at line 399–403 only fires when `cb.get("is_agentic_table")` is truthy. That flag must be injected into `cb_kwargs` by `modifier.build_agentic_table_payload(...)` in the cloud plugin. If the plugin omits it (or changes its key name), the agentic executor's `{"tables": [...]}` payload will be forwarded verbatim to `update_prompt_output`, which will find no value under `prompt_key` and persist an empty result silently. Consider adding an explicit `cb_kwargs["is_agentic_table"] = True` in `views.py` after the `modifier.build_agentic_table_payload` call — it keeps the contract visible in the open-source code and doesn't depend on the plugin remembering to set it.

How can I resolve this? If you propose a fix, please make it concise.

@harini-venkataraman harini-venkataraman marked this pull request as ready for review April 15, 2026 09:22
@github-actions
Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@github-actions
Copy link
Copy Markdown
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 196 passed, 0 failed (196 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/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\_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\_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{196}}$$ $$\textcolor{#23d18b}{\tt{196}}$$

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: 4

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx (1)

300-306: ⚠️ Potential issue | 🟡 Minor

Keep the table-settings button behind an enforce-type gate.

This now renders the settings entry for every prompt as soon as the plugin is installed, including text/number/email prompts. That is confusing at best, and it makes it easier to save table-specific config on incompatible prompt types.

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

In `@frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx` around
lines 300 - 306, The TableExtractionSettingsBtn is being rendered for all
prompts; guard its render with the enforce-type check so the settings only
appear for table-enforced prompts. Update the conditional around
TableExtractionSettingsBtn in PromptCardItems.jsx (the JSX block that currently
uses TableExtractionSettingsBtn, promptDetails?.prompt_id, enforceType,
setAllTableSettings) to require a table-specific enforceType (e.g., enforceType
=== 'table' or enforceType?.includes('table')) in addition to
TableExtractionSettingsBtn before rendering the component, so incompatible
prompt types won’t show the table settings.
🧹 Nitpick comments (5)
unstract/sdk1/src/unstract/sdk1/llm.py (1)

390-390: Avoid per-call global mutation of litellm.drop_params.

Line 390 reassigns a module-global already initialized at Line 33; this contradicts the module-level intent to avoid repeated global mutation per request.

♻️ Proposed fix
-            litellm.drop_params = True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@unstract/sdk1/src/unstract/sdk1/llm.py` at line 390, Remove the per-call
reassignment of the module-global litellm.drop_params (the assignment at the
shown call site) and instead either set the desired value once at module
initialization where litellm is imported/initialized (the earlier initialization
around line 33) or avoid mutating the global by using a local variable (e.g.,
drop_params) and pass that into the litellm API calls; in short, delete the
litellm.drop_params = True line and either consolidate the flag into
module-level setup or thread a local parameter through the functions that invoke
litellm.
frontend/src/hooks/usePromptRun.js (1)

19-23: Prefer a config-driven timeout instead of a fixed 16-minute constant.

Line 23 can silently drift from server adapter settings across environments. Consider sourcing this value from backend-exposed config (with buffer applied client-side) to avoid premature UI timeout regressions after infra changes.

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

In `@frontend/src/hooks/usePromptRun.js` around lines 19 - 23, The hardcoded
SOCKET_TIMEOUT_MS constant in usePromptRun.js can drift from server adapter
settings; change it to derive the timeout from a backend-exposed config value
(e.g., an API response or injected runtime config) and apply the client-side
buffer (e.g., subtract or add the intended 1 minute) when computing
SOCKET_TIMEOUT_MS; implement a safe fallback to the current 16-minute value if
the backend config is unavailable, and update any functions using
SOCKET_TIMEOUT_MS so they reference the computed/config-driven value instead of
the hardcoded constant.
docker/docker-compose.yaml (1)

532-532: Good queue addition—mirror this default in all deployment targets.

Line 532 is correct for local/dev, but please ensure Helm/chart and runtime env defaults include celery_executor_agentic_table as well, or agentic-table jobs can remain unconsumed in some environments.

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

In `@docker/docker-compose.yaml` at line 532, The docker-compose default for
CELERY_QUEUES_EXECUTOR was extended to include celery_executor_agentic_table but
other deployment targets are missing it; update all runtime/defaults to match by
adding celery_executor_agentic_table to the CELERY_QUEUES_EXECUTOR default in
Helm values (values.yaml), chart Deployment/StatefulSet env entries (templates/*
where CELERY_QUEUES_EXECUTOR is set), and any CI/runtime environment variable
configs (e.g., container env vars, systemd or cloud run settings) so every
environment uses
"celery_executor_legacy,celery_executor_agentic,celery_executor_agentic_table"
as the default queue list.
backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py (1)

173-179: Use centralized enforce-type constants here to avoid string drift.

The new agentic_table branch is correct, but this block is still string-literal based. Switching to shared constants will prevent future typo/divergence bugs.

♻️ Suggested refactor
+from prompt_studio.prompt_studio_core_v2.constants import (
+    ToolStudioPromptKeys as TSPKeys,
+)
...
-            if prompt.enforce_type in {
-                "json",
-                "table",
-                "record",
-                "line-item",
-                "agentic_table",
-            }:
+            if prompt.enforce_type in {
+                TSPKeys.JSON,
+                TSPKeys.TABLE,
+                TSPKeys.RECORD,
+                TSPKeys.LINE_ITEM,
+                TSPKeys.AGENTIC_TABLE,
+            }:
                 output = json.dumps(output)
🤖 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/output_manager_helper.py`
around lines 173 - 179, Replace the literal string set check on
prompt.enforce_type with the centralized enforce-type constants: import and use
the shared constants for JSON/TABLE/RECORD/LINE_ITEM/AGENTIC_TABLE (e.g.,
ENFORCE_TYPE_JSON, ENFORCE_TYPE_TABLE, ENFORCE_TYPE_RECORD,
ENFORCE_TYPE_LINE_ITEM, ENFORCE_TYPE_AGENTIC_TABLE) from the module that defines
enforce-type values (the centralized constants module in prompt_studio), and
change the condition in output_manager_helper.py (the prompt.enforce_type check)
to use those constants instead of the string literals to avoid string drift.
workers/file_processing/structure_tool_task.py (1)

402-442: Consider defensive access for llm and name keys to provide clearer error messages.

Lines 414 and 442 use direct key access (at_output["llm"], at_output[_SK.NAME]) which will raise KeyError with a generic traceback if missing. Since the validation block (lines 302-313) only checks agentic_table_settings, these fields aren't validated beforehand.

If the export process guarantees these keys, this is acceptable. Otherwise, wrapping in explicit checks would produce actionable error messages matching the style at lines 305-313.

🔧 Optional: Add explicit validation for required output keys
     for at_output in agentic_table_outputs:
         at_settings = at_output.get("agentic_table_settings") or {}
+        if not at_output.get(_SK.NAME) or not at_output.get("llm"):
+            return ExecutionResult.failure(
+                error=(
+                    f"Agentic table output is missing required 'name' or 'llm' key. "
+                    f"Re-export the tool from Prompt Studio."
+                )
+            ).to_dict()
         if not at_settings.get("target_table") or not at_settings.get("json_structure"):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workers/file_processing/structure_tool_task.py` around lines 402 - 442, The
loop over agentic_table_outputs accesses at_output["llm"] and
at_output[_SK.NAME] directly which can raise KeyError; add defensive validation
before using them (e.g., confirm required keys in each at_output or use
at_output.get(...) and raise/return a clear error) so failures mirror the
earlier validation style for agentic_table_settings; specifically check each
entry in agentic_table_outputs for "llm" and _SK.NAME (or provide sensible
defaults) before building agentic_params and before assigning
agentic_results[...], and if missing return a structured error result (similar
to other validation paths) rather than letting a KeyError bubble from
dispatcher.dispatch/ExecutionContext.
🤖 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/prompt_studio_core_v2/prompt_studio_helper.py`:
- Around line 1574-1590: The single-pass prompt filter must also exclude
agentic-table prompts to prevent them from being bundled into legacy single-pass
execution; update the single-pass filter logic (the code that currently excludes
only TSPKeys.TABLE and TSPKeys.RECORD) to additionally exclude
TSPKeys.AGENTIC_TABLE by checking prompt_instance.enforce_type ==
TSPKeys.AGENTIC_TABLE (same symbol used in the single-prompt branch) so
agentic-table prompts follow the payload_modifier_plugin path and do not end up
in legacy_executor silent skips.

In `@frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx`:
- Around line 94-95: The `isAgenticTableReady` state is being used globally
causing non-agentic prompts to be blocked; scope this readiness to only
agentic_table prompts by initializing and updating `isAgenticTableReady` based
on `promptDetails?.prompt_type === 'agentic_table'` (use the
`promptDetails`/`promptId` context) and reset it to true or undefined when
`promptDetails.prompt_type` changes away from 'agentic_table'; update the places
that read this flag (components/functions `Header`, `PromptOutput`, and any
setters in `PromptCardItems.jsx` such as the `setIsAgenticTableReady` usage) so
they only disable run buttons when the current prompt is of type 'agentic_table'
and the readiness flag is false.

In `@workers/executor/executors/legacy_executor.py`:
- Around line 1873-1885: The current guard in the legacy executor silently
returns when output_type == "agentic_table", leaving
structured_output[prompt_name] unset; change this to raise an explicit exception
instead so the run fails visibly: in the same block that checks output_type ==
"agentic_table" (using variables output_type and prompt_name and logger),
replace the silent return with raising a clear exception (e.g., RuntimeError or
ValueError) that includes prompt_name and a message stating the prompt was
misrouted and should have been dispatched to the agentic_table executor; keep
the logger.warning call if you want a log entry before raising so the error is
recorded.

In `@workers/ide_callback/tasks.py`:
- Around line 395-403: The current branch for cb.get("is_agentic_table")
incorrectly replaces the full executor payload with only outputs["tables"],
discarding fields like page_count and headers; instead, preserve the entire
payload by nesting it under the prompt key before calling
update_prompt_output(): when cb.get("is_agentic_table") and prompt_key is set,
set outputs = {prompt_key: outputs} (if outputs is already a dict, wrap that
dict; if it isn't, wrap the original value as-is) so update_prompt_output()
receives the complete agentic-table payload (reference symbols: cb, prompt_key,
outputs, update_prompt_output, is_agentic_table).

---

Outside diff comments:
In `@frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx`:
- Around line 300-306: The TableExtractionSettingsBtn is being rendered for all
prompts; guard its render with the enforce-type check so the settings only
appear for table-enforced prompts. Update the conditional around
TableExtractionSettingsBtn in PromptCardItems.jsx (the JSX block that currently
uses TableExtractionSettingsBtn, promptDetails?.prompt_id, enforceType,
setAllTableSettings) to require a table-specific enforceType (e.g., enforceType
=== 'table' or enforceType?.includes('table')) in addition to
TableExtractionSettingsBtn before rendering the component, so incompatible
prompt types won’t show the table settings.

---

Nitpick comments:
In
`@backend/prompt_studio/prompt_studio_output_manager_v2/output_manager_helper.py`:
- Around line 173-179: Replace the literal string set check on
prompt.enforce_type with the centralized enforce-type constants: import and use
the shared constants for JSON/TABLE/RECORD/LINE_ITEM/AGENTIC_TABLE (e.g.,
ENFORCE_TYPE_JSON, ENFORCE_TYPE_TABLE, ENFORCE_TYPE_RECORD,
ENFORCE_TYPE_LINE_ITEM, ENFORCE_TYPE_AGENTIC_TABLE) from the module that defines
enforce-type values (the centralized constants module in prompt_studio), and
change the condition in output_manager_helper.py (the prompt.enforce_type check)
to use those constants instead of the string literals to avoid string drift.

In `@docker/docker-compose.yaml`:
- Line 532: The docker-compose default for CELERY_QUEUES_EXECUTOR was extended
to include celery_executor_agentic_table but other deployment targets are
missing it; update all runtime/defaults to match by adding
celery_executor_agentic_table to the CELERY_QUEUES_EXECUTOR default in Helm
values (values.yaml), chart Deployment/StatefulSet env entries (templates/*
where CELERY_QUEUES_EXECUTOR is set), and any CI/runtime environment variable
configs (e.g., container env vars, systemd or cloud run settings) so every
environment uses
"celery_executor_legacy,celery_executor_agentic,celery_executor_agentic_table"
as the default queue list.

In `@frontend/src/hooks/usePromptRun.js`:
- Around line 19-23: The hardcoded SOCKET_TIMEOUT_MS constant in usePromptRun.js
can drift from server adapter settings; change it to derive the timeout from a
backend-exposed config value (e.g., an API response or injected runtime config)
and apply the client-side buffer (e.g., subtract or add the intended 1 minute)
when computing SOCKET_TIMEOUT_MS; implement a safe fallback to the current
16-minute value if the backend config is unavailable, and update any functions
using SOCKET_TIMEOUT_MS so they reference the computed/config-driven value
instead of the hardcoded constant.

In `@unstract/sdk1/src/unstract/sdk1/llm.py`:
- Line 390: Remove the per-call reassignment of the module-global
litellm.drop_params (the assignment at the shown call site) and instead either
set the desired value once at module initialization where litellm is
imported/initialized (the earlier initialization around line 33) or avoid
mutating the global by using a local variable (e.g., drop_params) and pass that
into the litellm API calls; in short, delete the litellm.drop_params = True line
and either consolidate the flag into module-level setup or thread a local
parameter through the functions that invoke litellm.

In `@workers/file_processing/structure_tool_task.py`:
- Around line 402-442: The loop over agentic_table_outputs accesses
at_output["llm"] and at_output[_SK.NAME] directly which can raise KeyError; add
defensive validation before using them (e.g., confirm required keys in each
at_output or use at_output.get(...) and raise/return a clear error) so failures
mirror the earlier validation style for agentic_table_settings; specifically
check each entry in agentic_table_outputs for "llm" and _SK.NAME (or provide
sensible defaults) before building agentic_params and before assigning
agentic_results[...], and if missing return a structured error result (similar
to other validation paths) rather than letting a KeyError bubble from
dispatcher.dispatch/ExecutionContext.
🪄 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: 745f3b34-3732-4f3c-9564-7de5c201cfcd

📥 Commits

Reviewing files that changed from the base of the PR and between 6383b10 and d69a8f0.

📒 Files selected for processing (21)
  • backend/prompt_studio/prompt_studio_core_v2/constants.py
  • backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
  • backend/prompt_studio/prompt_studio_core_v2/static/select_choices.json
  • 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_registry_v2/constants.py
  • backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py
  • backend/prompt_studio/prompt_studio_v2/migrations/0014_alter_toolstudioprompt_enforce_type.py
  • backend/prompt_studio/prompt_studio_v2/models.py
  • docker/docker-compose.yaml
  • frontend/src/components/custom-tools/prompt-card/Header.jsx
  • frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx
  • frontend/src/components/custom-tools/prompt-card/PromptOutput.jsx
  • frontend/src/hooks/usePromptRun.js
  • unstract/sdk1/src/unstract/sdk1/llm.py
  • workers/executor/executors/legacy_executor.py
  • workers/executor/executors/retrievers/fusion.py
  • workers/executor/executors/retrievers/keyword_table.py
  • workers/file_processing/structure_tool_task.py
  • workers/ide_callback/tasks.py
  • workers/tests/test_answer_prompt.py

Comment on lines +1574 to +1590
if (
prompt_instance.enforce_type == TSPKeys.AGENTIC_TABLE
and payload_modifier_plugin
):
modifier_service = payload_modifier_plugin["service_class"]()
response = modifier_service.execute_agentic_table(
tool_id=tool_id,
prompt_id=str(prompt_instance.prompt_id),
prompt_key=prompt_name,
prompt=prompt_instance.prompt,
doc_path=doc_path,
doc_name=doc_name,
org_id=org_id,
user_id=user_id,
run_id=run_id,
)
else:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This only fixes the single-prompt path; single-pass can still misroute agentic_table.

AGENTIC_TABLE is special-cased here, but the same file’s single-pass prompt filter still excludes only TABLE and RECORD at Lines 1642-1646. That means agentic-table prompts can still be bundled into legacy single-pass execution and hit the silent skip in workers/executor/executors/legacy_executor.py.

Follow-up change needed in the single-pass filter
         prompts = [
             prompt
             for prompt in prompts
             if prompt.prompt_type != TSPKeys.NOTES
             and prompt.active
             and prompt.enforce_type != TSPKeys.TABLE
             and prompt.enforce_type != TSPKeys.RECORD
+            and prompt.enforce_type != TSPKeys.AGENTIC_TABLE
         ]
🤖 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 1574 - 1590, The single-pass prompt filter must also exclude agentic-table
prompts to prevent them from being bundled into legacy single-pass execution;
update the single-pass filter logic (the code that currently excludes only
TSPKeys.TABLE and TSPKeys.RECORD) to additionally exclude TSPKeys.AGENTIC_TABLE
by checking prompt_instance.enforce_type == TSPKeys.AGENTIC_TABLE (same symbol
used in the single-prompt branch) so agentic-table prompts follow the
payload_modifier_plugin path and do not end up in legacy_executor silent skips.

Comment on lines +94 to 95
const [isAgenticTableReady, setIsAgenticTableReady] = useState(true);
const promptId = promptDetails?.prompt_id;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scope the readiness checklist to agentic_table prompts and reset the flag when leaving that type.

Right now the checklist can drive isAgenticTableReady for every prompt, and that flag is then fed into both Header and PromptOutput run-button disables. A false readiness emitted for a non-agentic prompt will block runs that should still be allowed.

Proposed fix
   const [tableSettings, setTableSettings] = useState({});
   const [isAgenticTableReady, setIsAgenticTableReady] = useState(true);
+
+  useEffect(() => {
+    if (enforceType !== "agentic_table") {
+      setIsAgenticTableReady(true);
+    }
+  }, [enforceType]);
...
-            {AgenticTableChecklist && (
+            {enforceType === "agentic_table" && AgenticTableChecklist && (
               <AgenticTableChecklist
                 promptId={promptDetails?.prompt_id}
                 promptText={promptText}
                 enforceType={enforceType}
                 onReadinessChange={setIsAgenticTableReady}
               />
             )}

Also applies to: 225-226, 236-243, 346-347

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

In `@frontend/src/components/custom-tools/prompt-card/PromptCardItems.jsx` around
lines 94 - 95, The `isAgenticTableReady` state is being used globally causing
non-agentic prompts to be blocked; scope this readiness to only agentic_table
prompts by initializing and updating `isAgenticTableReady` based on
`promptDetails?.prompt_type === 'agentic_table'` (use the
`promptDetails`/`promptId` context) and reset it to true or undefined when
`promptDetails.prompt_type` changes away from 'agentic_table'; update the places
that read this flag (components/functions `Header`, `PromptOutput`, and any
setters in `PromptCardItems.jsx` such as the `setIsAgenticTableReady` usage) so
they only disable run buttons when the current prompt is of type 'agentic_table'
and the readiness flag is false.

Comment on lines +1873 to +1885
# Defensive guard: agentic_table prompts must be dispatched to
# the dedicated agentic_table executor by the worker (Layer 2 in
# workers/file_processing/structure_tool_task.py). If one ever
# reaches this method, the legacy fallthrough below would store
# the raw LLM completion as a string. Skip silently with a
# warning so the caller's existing entry (if any) survives.
if output_type == "agentic_table":
logger.warning(
"Skipping agentic_table prompt %s in legacy executor — "
"should have been dispatched to agentic_table executor",
prompt_name,
)
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail misrouted agentic_table prompts instead of silently dropping them.

This return leaves structured_output[prompt_name] unset, but the legacy answer flow still completes successfully. If an agentic-table prompt ever reaches this path, the run will look successful while persisting no value for that prompt.

Proposed fix
         if output_type == "agentic_table":
-            logger.warning(
-                "Skipping agentic_table prompt %s in legacy executor — "
-                "should have been dispatched to agentic_table executor",
-                prompt_name,
-            )
-            return
+            raise LegacyExecutorError(
+                message=(
+                    f"Prompt '{prompt_name}' with type 'agentic_table' "
+                    "was routed to the legacy executor"
+                )
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workers/executor/executors/legacy_executor.py` around lines 1873 - 1885, The
current guard in the legacy executor silently returns when output_type ==
"agentic_table", leaving structured_output[prompt_name] unset; change this to
raise an explicit exception instead so the run fails visibly: in the same block
that checks output_type == "agentic_table" (using variables output_type and
prompt_name and logger), replace the silent return with raising a clear
exception (e.g., RuntimeError or ValueError) that includes prompt_name and a
message stating the prompt was misrouted and should have been dispatched to the
agentic_table executor; keep the logger.warning call if you want a log entry
before raising so the error is recorded.

Comment on lines +395 to +403
# Agentic table executor returns {"tables": [...], "page_count": ...,
# "headers": [...], ...}, but OutputManagerHelper expects
# outputs[prompt.prompt_key] to be the value for that prompt. Reshape
# so the table list lands under the prompt key.
if cb.get("is_agentic_table"):
prompt_key = cb.get("prompt_key", "")
if prompt_key:
tables = outputs.get("tables", []) if isinstance(outputs, dict) else []
outputs = {prompt_key: tables}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't drop the rest of the agentic-table payload here.

The reshape keeps only outputs["tables"], so fields like page_count, headers, or any other executor output are discarded before update_prompt_output(). That makes the persisted result strictly less informative than what the executor returned.

Proposed fix
         if cb.get("is_agentic_table"):
             prompt_key = cb.get("prompt_key", "")
             if prompt_key:
-                tables = outputs.get("tables", []) if isinstance(outputs, dict) else []
-                outputs = {prompt_key: tables}
+                payload = outputs if isinstance(outputs, dict) else {"tables": []}
+                outputs = {prompt_key: payload}
🤖 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 395 - 403, The current branch for
cb.get("is_agentic_table") incorrectly replaces the full executor payload with
only outputs["tables"], discarding fields like page_count and headers; instead,
preserve the entire payload by nesting it under the prompt key before calling
update_prompt_output(): when cb.get("is_agentic_table") and prompt_key is set,
set outputs = {prompt_key: outputs} (if outputs is already a dict, wrap that
dict; if it isn't, wrap the original value as-is) so update_prompt_output()
receives the complete agentic-table payload (reference symbols: cb, prompt_key,
outputs, update_prompt_output, is_agentic_table).

@chandrasekharan-zipstack
Copy link
Copy Markdown
Contributor

Additional Review Findings

🔵 LOW: _sanitize_null_values behavior change affects all enforce types

File: workers/executor/executors/legacy_executor.py

Top-level "NA" strings are now converted to None. Tests were updated to match, but this is a global behavioral change for all enforce types, not just the new agentic_table type. Downstream consumers (deployed tools, ETL pipelines, connectors) that currently receive the string "NA" will now receive null instead.

Worth confirming this was intentional and won't break existing deployed tool outputs.


🔵 LOW: Socket timeout bump is global (5 → 16 min)

File: frontend/src/hooks/usePromptRun.js

SOCKET_TIMEOUT_MS changed from 5 minutes to 16 minutes. The comment explains it trails the server-side 900s LLM timeout — makes sense for agentic table extraction which is inherently slow. However, this timeout applies to all prompt runs including simple text/json/number prompts.

A regular prompt that hangs will now take 16 minutes before the UI gives up, instead of 5. Consider either:

  • Making the timeout conditional on enforce_type
  • Or adding a visible progress/elapsed-time indicator so users aren't staring at a spinner

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.

Additional Review Findings

Beyond what CodeRabbit and Greptile already flagged, here are additional issues found during a deeper review:


Critical

1. Silent incomplete export when payload_modifier plugin is missing
backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py — the new elif prompt.enforce_type == AGENTIC_TABLE block (around line 375)

When exporting an agentic_table prompt without the payload_modifier plugin available, the if payload_modifier_plugin: guard silently skips the call to export_agentic_table_settings. The export succeeds without agentic_table_settings, and the user only discovers this at document-processing time when structure_tool_task.py validation fails with "Re-export the tool from Prompt Studio."

This is a "fail later" anti-pattern — the failure should happen at export time:

elif prompt.enforce_type == PromptStudioRegistryKeys.AGENTIC_TABLE:
    payload_modifier_plugin = get_plugin("payload_modifier")
    if not payload_modifier_plugin:
        raise OperationNotSupported(
            "Agentic table export requires the payload_modifier plugin."
        )
    modifier_service = payload_modifier_plugin["service_class"]()
    output = modifier_service.export_agentic_table_settings(...)

Important

2. Missing prompt_key silently skips callback reshaping
workers/ide_callback/tasks.py — around line 397

When is_agentic_table=True but prompt_key is empty/missing, the if prompt_key: guard skips reshaping silently. The raw executor output ({"tables": [...], "page_count": ..., "headers": [...]}) gets persisted as-is with zero logging. Should log an error and fail explicitly rather than persisting malformed data.

3. No error handling around agentic table dispatch in views.py
backend/prompt_studio/prompt_studio_core_v2/views.py — lines ~512-562

The entire agentic table dispatch block (plugin instantiation, build_agentic_table_payload, dispatch_with_callback) runs without any try/except. Compare this to the existing indexing dispatch which wraps dispatch_with_callback in try/except with cleanup logic. If the cloud plugin's build_agentic_table_payload raises or the Celery broker is down, users get an opaque 500 with no actionable information.

4. Single agentic_table failure aborts ALL remaining prompts
workers/file_processing/structure_tool_task.py — around line 430

In the agentic_table dispatch loop, if any single prompt fails (if not at_result.success: return at_result.to_dict()), the function returns immediately — all subsequent agentic prompts AND the entire regular legacy pipeline are abandoned. For a tool with 10 prompts where only 1 is agentic_table, a failure in that one prompt produces zero output for all 10. At minimum, log the broader impact (how many prompts were abandoned).

5. 16-minute SOCKET_TIMEOUT_MS applies to ALL prompt types
frontend/src/hooks/usePromptRun.js — line 19

The timeout increase from 5→16 minutes is global. For regular text/number/email prompts that should complete in seconds, a stalled request now takes 16 minutes to surface a timeout error. Consider making the timeout type-aware (e.g. keep 5min for regular prompts, 16min for agentic_table).


Suggestions

6. Inaccurate comments referencing non-existent terminology

  • workers/executor/executors/legacy_executor.py: References "Layer 2 in workers/file_processing/structure_tool_task.py" — "Layer 2" doesn't appear anywhere in the codebase
  • workers/file_processing/structure_tool_task.py: References "populated by Layer 1 export" — same issue
  • workers/file_processing/structure_tool_task.py (~line 670): Comment says "Use local variables so tool_metadata[_SK.OUTPUTS] is preserved for METADATA.json serialization downstream in _write_tool_result" — this is factually incorrect. _write_tool_result() does not read tool_metadata[_SK.OUTPUTS]. The real reason is to feed only regular prompts into answer_params while keeping the full list for the agentic dispatch loop.

7. complete_vision() docstring omits key behavioral differences from complete()
unstract/sdk1/src/unstract/sdk1/llm.py — around line 488

The docstring says "Same error handling, usage tracking, and metrics as complete()" but doesn't mention:

  • Does NOT support extract_json or post_process_fn post-processing
  • Does NOT prepend the adapter's system prompt (unlike complete() which builds [{"role": "system", ...}, {"role": "user", ...}] internally)

Callers reading "same as complete()" might expect feature parity.

8. Significant test coverage gaps
No tests added for:

  • complete_vision() — 90-line new public method, zero coverage
  • Structure tool task partitioning/dispatch logic — core routing with zero tests
  • IDE callback agentic table reshaping — 2-3 test cases needed in existing TestIdePromptComplete
  • Legacy executor agentic_table guard — single test case needed

The IDE callback reshaping test is highest ROI: catches critical data-loss scenarios and the test infrastructure already exists in workers/tests/test_ide_callback.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants