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: ToolRuntimeparameter even though it’s unused - it makes the bridge inject the LangGraph runtime, whichStateBackendneeds to read thefileschannel. (WithFilesystemBackend/StoreBackendit’s optional.) max_result_charsonly truncates what’s returned to the model fromeval, 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 exposingwrite_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.