Skip to content

nullrunio/nullrun-examples

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 

Repository files navigation

nullrun-examples

Working examples for the nullrun Python SDK.

Each example is a self-contained, runnable file. The intent is to show the smallest possible change to add enforcement to a common agent framework.

Prerequisites

pip install nullrun
export NULLRUN_API_KEY=nr_live_...

Get an API key from the NullRun dashboard.

Shared .env (recommended)

Every example reads its keys from examples/.env if python-dotenv is installed (pip install python-dotenv) — no shell export needed. Copy the template and fill in your keys:

cp examples/.env.example examples/.env
$EDITOR examples/.env   # set NULLRUN_API_KEY + per-vendor keys
python examples/raw_openai_basic.py

Without python-dotenv the examples fall back to whatever the developer's shell already has exported — the auto-load is purely a convenience. examples/.env is in .gitignore; commit only .env.example.

Auto-instrumentation

nullrun.init(api_key=...) patches the underlying HTTP transport and any imported agent framework (openai, openai-agents, langgraph, autogen, crewai, llama-index, ...) automatically. You get cost tracking without changing your call sites; @protect is the gate layer (budget / kill / pause) that runs before the call.

For frameworks that ship an extra, install with the matching optional dependency (nullrun[langgraph], nullrun[openai], nullrun[anthropic], nullrun[mistral], nullrun[gemini], nullrun[cohere], nullrun[bedrock], nullrun[agents], nullrun[langchain], nullrun[llama-index], nullrun[crewai], nullrun[autogen]). The openai-agents SDK is auto-detected at runtime without a separate extra.

Examples

File Framework What it shows
raw_openai_basic.py raw OpenAI @protect + @guarded on a single LLM call
anthropic_basic.py raw Anthropic @protect + @guarded on a messages.create call
mistral_basic.py raw Mistral @protect + @guarded on a chat.complete call
gemini_basic.py raw Gemini @protect + @guarded on models.generate_content
cohere_basic.py raw Cohere @protect + @guarded on client.chat (V2)
bedrock_basic.py AWS Bedrock @protect + manual track_llm (boto3 uses urllib3, not httpx)
langchain_basic.py LangChain Auto-instrumented ChatModel.invoke (not via LangGraph)
langgraph_basic.py LangGraph Auto-instrumented StateGraph (recommended)
langgraph_manual_wrapper.py LangGraph nullrun.toolbox.langgraph.wrapper for re-compiled graphs
llama_index_basic.py llama-index Auto-instrumented LLMChatEndEvent / FunctionCallEvent
crewai_basic.py CrewAI Auto-instrumented Crew.kickoff + usage_metrics flush
autogen_basic.py AutoGen Auto-instrumented BaseChatAgent.on_messages
openai_agents_basic.py OpenAI Agents SDK @protect + @guarded on a multi-step agent run
cost_cap_demo.py any Hard budget cap that halts the agent
chain_soft_mode.py any Soft-mode pass via active chain context
on_error_hook.py any nullrun.on_error hook for Sentry / dashboards

Running

export NULLRUN_API_KEY=nr_live_...
python examples/raw_openai_basic.py

All examples are read-only — they do not modify org state, policies, or keys on your account. They do emit track events to the gateway (auto-instrumented HTTP traffic from init()), so a cost_attribution or examples tag in the dashboard will pick them up.

Every example ends with nullrun.shutdown() in a finally block. This sends a clean WebSocket close frame so the backend does not log "Connection reset without closing handshake" on long-running scripts. No-op if init() was never called.

Error handling in 0 lines

The examples intentionally avoid any try/except NullRunError block. Three one-liners from the SDK do the work:

  • nullrun.init_or_die(api_key=...) — wraps nullrun.init() so a missing NULLRUN_API_KEY produces a clean exit, not a raw traceback.
  • @nullrun.guarded (decorator) — wrap a function so any NullRunError is translated into format_user_message(exc) on stderr and sys.exit(1).
  • with nullrun.handle(): (context manager) — same behaviour for a region of code.

All three propagate WorkflowKilledInterrupt (a BaseException) unchanged — kill signals must reach the top of the agent loop. Non-NullRun exceptions also propagate so user bugs surface as honest tracebacks.

For observability, register a hook with nullrun.on_error(...). The hook fires before every NullRunError raise with the structured fields (error_code, retryable, user_action, docs_url, stage, workflow_id) — pair it with @guarded for Sentry / dashboards:

import nullrun

@nullrun.on_error
def _to_sentry(err, ctx):
    sentry_sdk.capture_exception(err, extra={
        "error_code": err.error_code,
        "retryable": err.retryable,
        "stage": ctx.stage,
        "workflow_id": ctx.workflow_id,
    })

If you need to branch on a specific error_code (operator dashboards, per-code retry policies), reach for nullrun.NullRunError.error_code and the structured fields — see the error code catalogue for the full list. But for the common "run an agent and print a friendly message on failure" case, init_or_die + @guarded / handle is enough.

Contributing

PRs welcome. Keep each example under 80 lines. No external state beyond the NullRun API key.

Releases

No releases published

Packages

 
 
 

Contributors