Build a coding agent with LangChain
This example builds a coding agent that writes Python code and then actually
runs it to check whether it works -- iterating on failures until it succeeds. It
uses a Lightning Sandbox, the
lightning-sdk, and
LangGraph.
The agent is orchestrated with LangGraph, the LLM is driven with the standard
LangChain ChatOpenAI
integration, and -- the key part -- the agent's generated code executes inside an
isolated sandbox so it can't touch your machine, even under prompt injection.
Because this is a multi-file project, the last section shows how to clone and run the complete code straight from GitHub.
Why a sandbox?
An LLM that writes code is only useful if you can run that code -- but running model-generated code on your own machine is dangerous. The sandbox gives the agent a real, disposable Python environment to execute in: the LangGraph "brain" runs locally, and only the untrusted generated code runs remotely.
How it works
The agent is a graph (see src/nodes.py and src/edges.py):
generate ──▶ check_code_imports ──▶ check_code_execution ──▶ evaluate_execution ──▶ finish
▲ │ │
└────────────────┴──────────────────── retry ───────────────────┘generate-- a LangChain LCEL chain (PromptTemplate | chat_model.with_structured_output(Code)) asks the model for a structured{prefix, imports, code}solution, using crawled documentation as context.check_code_imports-- runs just the imports in the sandbox. A non-zero exit code means the imports are broken, so it regenerates with the error as feedback.check_code_execution-- runs the full code block in the sandbox and captures its stdout/stderr and exit code.evaluate_execution-- the model judges whether the run succeeded and decidesfinishorretry.finish-- assembles the final answer (approach + code + real execution output) and deletes the sandbox.
Because Lightning AI merges a command's stdout and stderr into one stream, the
run() helper redirects each to a separate file in the sandbox and reads them
back, and it returns the process exit code -- that, not the mere presence of
stderr, is what distinguishes a real failure from benign warnings (e.g. Hugging
Face writes model-download progress to stderr while still exiting 0).
One key, no extra LLM provider account
Lightning AI exposes an OpenAI-compatible LLM gateway at
https://lightning.ai/api/v1, authenticated with the same sk-lit-... key
used for sandboxes. src/llm.py simply points ChatOpenAI at it via base_url,
so bind_tools / with_structured_output / LCEL all work exactly as they would
against OpenAI -- with no separate OpenAI or Anthropic key.
Pick any model from your gateway's catalog in src/common.py:
MODEL = "anthropic/claude-opus-4-8"
DEBUG_MODEL = "lightning-ai/deepseek-v4-pro"Executing generated code in the sandbox
The heart of the agent is a run() helper that executes a snippet inside the
sandbox and returns (exit_code, stdout, stderr). It reuses the same sandbox
container across runs (preserving state) and recovers the separate streams that
the graph nodes rely on:
def run(code: str, sb: SandboxInstance) -> tuple[int, str, str]:
"""Execute ``code`` inside the sandbox, returning (exit_code, stdout, stderr)."""
sb.write_file(CODE_PATH, code)
try:
returncode = run_and_wait(
sb,
f"python -u {CODE_PATH}",
timeout=EXEC_TIMEOUT_S,
poll_interval=2.0,
stdout_path=OUT_PATH,
stderr_path=ERR_PATH,
)
except TimeoutError:
returncode = 1
stdout = sb.read_file(OUT_PATH) or ""
stderr = sb.read_file(ERR_PATH) or ""
return returncode, stdout, stderrrun_and_wait launches the command detached, redirects its output to files
in the sandbox, and polls an exit-code sentinel file until the command finishes
-- the robust way to run work that takes longer than the synchronous ~120s
deadline (see the
safe code execution example
for the same pattern).
Repo structure
| File | Purpose |
|---|---|
agent.py |
Entry point: creates the sandbox, builds + runs the graph, defines run(). |
src/common.py |
Shared config: model ids, sandbox runtime/instance, dependency install script, graph state. |
src/llm.py |
Builds a ChatOpenAI wired to Lightning AI's gateway. |
src/nodes.py |
Graph nodes (the actions that mutate state). |
src/edges.py |
Graph edges (the transitions between nodes). |
src/retrieval.py |
Crawls the docs used as context for generation. |
Run the complete example
This example is a multi-file package, so clone it straight from the SDK repo and run it:
git clone https://github.com/Lightning-AI/sdk.git
cd sdk/python/examples
# Install the local "brain" dependencies (torch + transformers are installed
# *in the sandbox* at create time, not here).
pip install -r langchain/requirements.txtThen run it from the directory that contains the langchain package. The
same Lightning AI key authenticates both the sandbox API and the LLM gateway --
see Authentication for how to
create a teamspace-scoped key:
export LIGHTNING_SANDBOX_API_KEY=sk-lit-...
python -m langchain.agent \
--question "How do I run a pre-trained model from the transformers library?"Useful flags:
--debug-- shorter doc context and a smaller/faster model (DEBUG_MODEL).--api-key sk-lit-...-- pass the key explicitly instead of via the environment.
Expected output
🏖️ Creating sandbox (cpu-4, runtime=python313)
🏖️ Installing torch + transformers in the sandbox (this can take a few minutes)
🏖️ Sandbox ready (<sandbox-id>)
📚: Retrieving documents from https://huggingface.co/docs/transformers/index
📚: Retrieved 50 documents
---GENERATE SOLUTION---
---CHECKING CODE IMPORTS---
📦: Running in sandbox
from transformers import pipeline
---CODE IMPORT CHECK: SUCCESS---
---DECIDE TO TEST CODE EXECUTION---
---DECISION: TEST CODE EXECUTION---
---CHECKING CODE EXECUTION---
📦: Running in sandbox
(full generated script using pipeline("sentiment-analysis"))
---CODE BLOCK CHECK: SUCCESS---
---EVALUATING EXECUTION---
---DECIDE TO FINISH---
---DECISION: FINISH---
---FINISHING---
To run a pre-trained model from the transformers library, the easiest approach is to
use the `pipeline()` function ...
from transformers import pipeline
classifier = pipeline(task="sentiment-analysis")
result = classifier("Hugging Face is the best!")
print("Single input:", result)
results = classifier(["This restaurant is awesome", "This restaurant is awful"])
print("Batch input:", results)
...
Result of code execution:
Single input: [{'label': 'POSITIVE', 'score': 0.9998661279678345}]
Batch input: [{'label': 'POSITIVE', 'score': 0.9998743534088135}, {'label': 'NEGATIVE', 'score': 0.9996669292449951}]Adapting it
To make the agent an expert on a different library, change two things:
- The sandbox dependency install (
SANDBOX_SETUP_SCRIPTinsrc/common.py). - The documentation URL crawled in
src/retrieval.py.
Was this page helpful?
Thanks for the feedback.