n4nAI

W&B Weave for tracking LLM experiments and prompts

A practical guide to using W&B Weave for LLM tracking: instrument prompts, version experiments, capture token usage, and avoid common pitfalls.

n4n Team3 min read746 words

Audio narration

Coming soon — every post will get a voice note here.

W&B Weave for LLM tracking solves a specific problem: most teams instrument LLM calls with print statements and a CSV, then lose prompt lineage the moment they tweak a template. This guide lays out an ordered path to adopt Weave properly—from op-level tracing to prompt versioning and experiment comparison—without turning your codebase into a telemetry dump.

1. Install and initialize a project

Weave is a Python package (with TS support, but Python is ahead). Pin a version; the API has shifted across minor releases.

pip install weave==0.51.0

Initialize once per process. The project name scopes your objects in the W&B UI.

import weave

weave.init("llm-experiments")

If you run this outside a script (e.g., a notebook), the first call creates the project. In CI or serving, set WEAVE_PROJECT env var and call weave.init() with no args.

2. Wrap LLM calls with @weave.op

The core primitive is the op. Any function decorated with @weave.op gets traced: inputs, outputs, latency, and call graph.

import openai
import weave

weave.init("llm-experiments")

@weave.op()
def generate(prompt: str, model: str = "gpt-4o-mini") -> str:
    client = openai.OpenAI()
    resp = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.choices[0].message.content

Call it normally. Weave captures the call asynchronously; you’ll see it in the Weave tab of your W&B project.

Pitfall: do not pass huge objects (full document corpora, base64 PDFs) as op inputs. Weave serializes inputs to its object store. Use weave.ref or log only hashes/summaries.

@weave.op()
def generate_from_doc(doc_id: str, prompt: str) -> str:
    # fetch doc outside the logged input
    text = fetch_doc(doc_id)
    return generate(prompt + "\n" + text[:2000])

3. Version prompts with weave.Model

A prompt template is code, but it deserves versioning independent of your Git SHA. Subclass weave.Model and declare the template as an attribute. Publishing creates an immutable version.

import weave

class Summarizer(weave.Model):
    prompt_template: str
    model_name: str = "gpt-4o-mini"

    @weave.op()
    def predict(self, text: str) -> str:
        prompt = self.prompt_template.format(text=text)
        return generate(prompt, self.model_name)

# version 1
v1 = Summarizer(prompt_template="Summarize in one sentence: {text}")
weave.publish(v1, name="summarizer")

# later, tweak and republish
v2 = Summarizer(prompt_template="You are an editor. Condense: {text}")
weave.publish(v2, name="summarizer")

In the UI you get a diff between versions and can pin a specific one for inference. Tradeoff: models are snapshots. If you mutate the attribute at runtime without republishing, Weave logs the mutated value but won’t auto-version.

4. Run experiments across model variants

Weave doesn’t require its own eval framework to be useful. Wrap a loop and let ops accumulate.

models = ["gpt-4o-mini", "gpt-4o", "claude-3-5-sonnet"]
texts = load_eval_set()

for m in models:
    for t in texts:
        out = generate(f"Classify: {t}", model=m)
        # optionally score

For structured comparison, publish a dataset and use weave.Evaluation (available in recent versions). But the manual loop is enough to spot regressions in tone or latency.

Common mistake: logging every token of a 100k-call production stream to Weave. Sample 5–10% in prod; run full sweeps in offline eval.

5. Capture token usage and latency

Weave’s OpenAI integration auto-patches the client and records usage metadata. Use the integration instead of raw SDK when possible.

from weave.integrations.openai import OpenAI
import weave

weave.init("llm-experiments")
client = OpenAI()  # patched

@weave.op()
def generate(prompt: str, model: str):
    resp = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.choices[0].message.content, resp.usage

The resp.usage appears in the op’s output panel. You can then group by model in the Weave board to compare cost per successful call. Latency is captured by the op wrapper itself—no extra code.

If you use a non-OpenAI provider, return usage explicitly as a dict and Weave will store it; just don’t expect automatic cost conversion.

6. Pitfalls and tradeoffs

Serialization overhead. Every op input/output is serialized via pydantic and sent to the Weave server. For high-QPS services, this adds 1–5ms per call. Run the Weave client in a background thread or disable tracing in hot paths with weave.trace_disabled().

Prompt drift. Publishing a weave.Model is a manual step. If your team edits templates in a JSON file but forgets to republish, experiments will reference stale versions. Enforce publishing in CI when prompt files change.

No real-time alerting. Weave is for post-hoc analysis. Don’t poll it for latency SLO breaches; use Prometheus for that. Weave answers “which prompt version caused the drop in quality,” not “is the site up.”

Object store cost. Long-text inputs stored indefinitely can inflate W&B storage bills. Set a retention policy or use weave.ref to externalize large blobs.

7. Multi-provider routing through a gateway

When you route across vendors, tracking per-call metadata gets fragmented. If you front models with a gateway like n4n.ai—one OpenAI-compatible endpoint covering 240+ models with automatic fallback on provider degradation—you keep a single call surface. Wrap that call in an op and Weave stays consistent across backends.

from weave.integrations.openai import OpenAI
import weave

weave.init("llm-experiments")
# point at gateway, still OpenAI-compatible
client = OpenAI(base_url="https://api.n4n.ai/v1", api_key="YOUR_KEY")

@weave.op()
def routed_generate(prompt: str, model: str):
    resp = client.chat.completions.create(
        model=model,  # e.g. "anthropic/claude-3-5-sonnet"
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.choices[0].message.content

The gateway handles fallback; Weave records which model string you requested and the latency observed. You lose provider-internal token breakdowns unless the gateway returns them in usage (n4n.ai meters per-token usage and forwards it). That’s enough to attribute cost by experiment.

8. What to do next

Start with one critical prompt path. Wrap it, publish the model, and run a 50-example eval set weekly. Resist the urge to trace every helper function—only ops that change outputs matter.

Once you have two prompt versions and a score, build a Weave board filtering by model_name and prompt_template version. That view is where W&B Weave for LLM tracking pays off: you can show exactly which template edit moved the accuracy number, and roll back by pinning the prior published model.

For deeper workflows, add weave.dataset versioning and automated eval in CI. But the ordered path above is sufficient to stop losing prompt lineage by Friday.

Tagswandb-weavellm-observabilityexperiment-trackingprompts

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All llm observability platforms posts →