Skip to content

Tool layer: surface tool errors to the model instead of aborting the run; URL-based table loads #41

Description

@rohan-hotdata

Two items in the tool-wiring layer between LangChain and the SDK, both found by running an agent against production rather than by reading code.

1. A raising tool aborts the whole agent run

The tools are thin pass-throughs that let exceptions propagate — a deliberate decision recorded in CHANGELOG.md for 0.2.1. In an agent loop that decision has a consequence that was not visible when it was made: an exception out of a tool kills the entire LangGraph run.

Observed: the model wrote invalid SQL, the tool raised, and the run died with a traceback instead of the model seeing the error and retrying.

Neither obvious LangChain mechanism covers it:

  • create_agent(tools=...) does not accept a ToolNode, so handle_tool_errors=True is unreachable — TypeError: 'ToolNode' object is not iterable.
  • BaseTool.handle_tool_error only catches ToolException, and these tools raise RuntimeError.

demo/bm25_search_demo.py works around it with with_error_feedback, which wraps each tool to return the error as a message, and also walks the __cause__ chain to recover the engine's real text (see #36 item 1 — the framework surfaces only "Bad Request"). It is ~20 lines and every consumer building an agent will need the same thing.

Proposal: fold it into the package as opt-in behaviour on make_hotdata_tools (e.g. handle_errors=True), returning a {"error": "<engine message>"} payload consistent with the existing JSON envelope. Partly moot once #36 item 1 lands — but only partly: the graph-abort problem is independent of message quality.

Worth deciding explicitly rather than by default, since it reverses a documented choice.

2. Loads only accept a local file path

hotdata_load_managed_table(database, table, file, schema_name) takes a filesystem path, so an agent cannot use it for anything it has not already downloaded. demo/bm25_search_demo.py has to fetch the fixture parquet itself — and needs a non-default User-Agent to do it, because the asset host 403s urllib's default.

A URL-based load would make the ingest path actually reachable by an agent, which matters for the "agent stands up its own searchable dataset" story: create database → load → index → search. Steps 1 and 4 are agent-callable today; step 2 needs a local file and step 3 has no tool at all (create_index, #36 item 2).

Notes:

  • client.load_managed_table accepts upload_id or file, and upload_parquet handles the session/finalize upload flow, so a URL variant is fetch-then-upload rather than new API surface.
  • Only parquet is accepted; the framework validates with is_parquet_path and raises otherwise. CSV is selected by Content-Type at upload time per the e2e harness, so a broader-format load is a separate question.
  • Fetching a caller-supplied URL server-side would be a different security posture; client-side fetch keeps it in the agent's own trust boundary.

The tool description already states the current limitation truthfully ("Only local parquet paths are accepted — not URLs, and not other file formats"), pinned by tests/test_descriptions.py::test_load_description_states_the_accepted_input, so nothing is misleading in the meantime.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai-native-layerPart of the AI-native query layer effort for LangChainenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions