Skip to main content
Use the Pylva callback when LangGraph is your orchestration layer and you want cost attribution by graph node and end customer.

Python

pip install "pylva-sdk[langchain]"
import os
from pylva.langchain import PylvaCallbackHandler

handler = PylvaCallbackHandler(api_key=os.environ["PYLVA_API_KEY"])

result = graph.invoke(
    {"question": "Where did spend increase?"},
    config={
        "callbacks": [handler],
        "metadata": {"pylva_customer_id": "cust_acme"},
    },
)
Pylva reads LangChain usage metadata at callback time. It sends model, provider, tokens in, tokens out, latency, status, LangGraph run ids, customer_id, and step_name. It does not send prompts, completions, tool arguments, or tool outputs.

TypeScript

pnpm add @pylva/sdk @langchain/core @langchain/langgraph
import { PylvaCallbackHandler } from "@pylva/sdk/langgraph";

const handler = new PylvaCallbackHandler({
  apiKey: process.env.PYLVA_API_KEY!,
});

const result = await graph.invoke(
  { question: "Where did spend increase?" },
  {
    callbacks: [handler],
    metadata: { pylva_customer_id: "cust_acme" },
  },
);
@pylva/sdk/langchain re-exports the same TypeScript handler for LangChain.js callback discoverability. The deep entrypoints do not import the root SDK, so they do not auto-patch provider clients.

Node Attribution

LangGraph adds run metadata such as langgraph_node. Pylva uses that as the event step_name, so costs naturally group by graph node.
graph.invoke(
    inputs,
    config={
        "callbacks": [handler],
        "metadata": {
            "pylva_customer_id": "cust_acme",
            "pylva_step": "support_triage",
        },
    },
)
Step resolution order:
  • metadata["langgraph_node"]
  • metadata["pylva_step"]
  • metadata["langgraph_step"]
  • LangChain run name
Customer resolution order:
  • PylvaCallbackHandler(customer_id="...")
  • new PylvaCallbackHandler({ customerId: "..." }) in TypeScript
  • metadata["pylva_customer_id"]
  • metadata["customer_id"]
  • Active pylva.track_context
  • Active track() context in TypeScript
  • anonymous

Per-Customer Billing

Pass one stable opaque customer id per graph invocation.
for customer_id, question in pending_questions:
    graph.invoke(
        {"question": question},
        config={
            "callbacks": [handler],
            "metadata": {"pylva_customer_id": customer_id},
        },
    )
Pylva prices usage on the server, so pricing changes do not require changing the graph code that reports raw usage.

Tool Calls

Tool-call billing is opt-in because many tools are not billable. Enable it only when a tool execution itself should create a configured usage event.
handler = PylvaCallbackHandler(
    api_key=os.environ["PYLVA_API_KEY"],
    track_tool_calls=True,
)
Tool events report metric="calls" and metric_value=1. Configure the price for that metric in Pylva before using it for invoices.

Avoid Double Counting

Use either the LangGraph callback path or the provider auto-instrumentation path for the same runtime. In TypeScript, importing @pylva/sdk/langgraph or @pylva/sdk/langchain alone does not auto-patch providers; importing the root @pylva/sdk package does.

Failure Behavior

Callback errors are swallowed. If Pylva is unavailable, the graph continues. When LangChain does not provide token usage, Pylva records the run with zero tokens and metadata.usage_missing=true rather than reading prompt text to estimate usage.