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 ───────────────────┘
  1. 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.
  2. 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.
  3. check_code_execution -- runs the full code block in the sandbox and captures its stdout/stderr and exit code.
  4. evaluate_execution -- the model judges whether the run succeeded and decides finish or retry.
  5. 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, stderr

run_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.txt

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

  1. The sandbox dependency install (SANDBOX_SETUP_SCRIPT in src/common.py).
  2. The documentation URL crawled in src/retrieval.py.

Was this page helpful?

What went wrong?

↑↓ Navigate↵ Open⌘↵ New tabesc Close