Specialize read_file for use within eval

I’m using programmatic tool calling inside the eval tool. I put a JSON file to my agent’s filesystem and I exposed the read_file tool to the code interpreter. My problem is that read_file adds line numbers at the beginning of each line, and then the script needs to process it with a regex to remove them (and it helps if I tell it ahead of time that it will need to do that.) I’d like for the script to be able to read the file raw. How do you think I should approach this?

hi @kgeis

read_file is a model-facing tool and the line numbers are added unconditionally by FilesystemMiddleware (format_content_with_line_numbers), with no option to disable them. A regex also breaks on real files: read_file defaults to limit=100 lines, splits lines longer than 5000 chars into 5.1/5.2 continuation rows (a minified JSON file gets chunked), and may append truncation notices.

The clean fix is to write your own tiny tool that reads through the backend’s download_files() (raw bytes, implemented by every backend) and pass the tool instance to the ptc allowlist - ptc accepts BaseTool instances, not just names (Interpreters docs). Bonus: the PTC bridge hands the tool’s native return value to JS, so if you return a dict the script gets an object directly and doesn’t need JSON.parse.

import json
from deepagents import create_deep_agent
from deepagents.backends import StateBackend
from langchain.tools import ToolRuntime, tool
from langchain_quickjs import CodeInterpreterMiddleware

backend = StateBackend()

@tool
def read_json(file_path: str, runtime: ToolRuntime) -> dict | list:
    """Parse a JSON file from the agent filesystem. Returns the decoded value (already parsed)."""
    resp = backend.download_files([file_path])[0]
    if resp.error:
        raise ValueError(f"{file_path}: {resp.error}")
    return json.loads(resp.content)

agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    backend=backend,
    middleware=[CodeInterpreterMiddleware(ptc=[read_json, "write_file"])],
)

In eval:

const data = await tools.readJson({ file_path: "/data/input.json" }); // already an object
  • Keep the runtime: ToolRuntime parameter even though it’s unused - it makes the bridge inject the LangGraph runtime, which StateBackend needs to read the files channel. (With FilesystemBackend/StoreBackend it’s optional.)
  • max_result_chars only truncates what’s returned to the model from eval, not what flows into JS, so large files are fine to process in-script.
  • As with all PTC calls, interrupt_on/HITL isn’t enforced per tool call - fine for a read-only tool, but be deliberate about exposing write_file.

Small correction to my earlier reply: the runtime: ToolRuntime parameter isn’t actually required for StateBackend - I verified from source that the PTC bridge runs the tool on the graph’s loop, so StateBackend.download_files() finds the LangGraph config via contextvars either way. You can drop the parameter; def read_json(file_path: str) -> dict | list is enough.

hi! thanks for flagging this – this is definitely next up on our roadmap for work with the repl.