Skip to main content

Trace ingestion client for Torina — captures LLM agent traces and forwards them over HTTP.

Project description

torina

Captures LLM agent traces (currently: LangChain) and forwards them to a Torina HTTP ingestion endpoint via a single POST per trace, authenticated with an x-api-key header. Not built on OpenTelemetry — it patches LangChain's own callback system directly, so the only dependency is wrapt.

Install

uv add torina
# or
pip install torina

Usage

import os
import torina

torina.auto_instrument()
torina.init_logger(
    api_key=os.environ["TORINA_API_KEY"],
    project="My Project (Python)",
    endpoint="https://ingest.torina.example/v1/traces",  # or set TORINA_ENDPOINT
)

auto_instrument() patches LangChain automatically if it's installed, so every agent.invoke()/.stream() call anywhere in your app gets captured with no per-call-site changes. A trace is uploaded whole — one HTTP request per completed trace (every llm/tool span it contains, plus any chain span that carries real data — empty structural wrapper chains, like LangGraph's internal per-step nodes, are pruned), not one request per span.

Every trace carries payload["source"] ("langchain" today — the only integration so far) so traces from different sources stay distinguishable as more get added later.

A few things get deduplicated rather than taken at face value. Tool schemas and the system prompt are both usually identical across every LLM call in a trace — they're a property of the agent's definition, not the individual call, but get resent on every call because that's how chat APIs work. Both get hoisted to payload["tool_definitions"] / payload["system_prompt"] when they match across every llm span — left per-span only if a trace genuinely varied them mid-conversation.

Spans don't carry parent_span_id — there's no span tree. Relationships that matter are expressed directly instead: a tool span's tool_call_id matches the id in the requesting llm span's output_message.tool_calls, which is more precise than a parent/child edge would be anyway (LangChain's own run tree only reflects LangGraph's internal step wrappers, not which llm call actually caused which tool call).

Conversation grouping, the agent's name, and arbitrary metadata all come from LangChain's own config/create_agent(name=...), no Torina-specific code needed:

agent = create_agent(..., name="weather-agent")  # -> payload["agent_name"]

agent.invoke(
    {"messages": [...]},
    config={
        "configurable": {"thread_id": thread_id},  # -> payload["conversation_id"]
        "metadata": {"user_id": "u_123", "plan": "enterprise"},  # -> payload["metadata"]
    },
)

For manual control over a specific call site instead of the global patch, TorinaCallbackHandler is available directly (requires langchain-core):

from torina.langchain import TorinaCallbackHandler

agent.invoke({"messages": [...]}, config={"callbacks": [TorinaCallbackHandler()]})

dry_run

torina.init_logger(dry_run=True)  # no api_key or endpoint needed

Traces are logged instead of sent — useful for local testing before you have a real endpoint. Same code path either way; dry_run only swaps the network call for a log line.

Development

This project uses uv.

uv sync
uv run pytest
uv run ruff check .

Releasing

Publishing to PyPI happens via .github/workflows/publish.yml, triggered by a GitHub release. It uses trusted publishing (OIDC), so no PyPI API token is stored in the repo.

  1. Bump version in pyproject.toml.
  2. Commit and push to main.
  3. Tag and create a release:
    git tag vX.Y.Z
    git push origin vX.Y.Z
    gh release create vX.Y.Z --title vX.Y.Z --generate-notes
    
  4. Publishing the release triggers the workflow, which runs uv build and uv publish. Check the Actions tab for the run, then confirm at pypi.org/project/torina.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

torina-0.1.1.tar.gz (8.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

torina-0.1.1-py3-none-any.whl (10.8 kB view details)

Uploaded Python 3

File details

Details for the file torina-0.1.1.tar.gz.

File metadata

  • Download URL: torina-0.1.1.tar.gz
  • Upload date:
  • Size: 8.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for torina-0.1.1.tar.gz
Algorithm Hash digest
SHA256 939ad20ee187a5b1f87ac67361f4121573636980d507c233718d874fedec7a5d
MD5 4c1e9c36a08725353bffccf3d14f3270
BLAKE2b-256 a9c41e09d3f940b43290aafadf39bf5ad7223d1e2e544fcd6a135749ba8eb47e

See more details on using hashes here.

File details

Details for the file torina-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: torina-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 10.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for torina-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 edb0afb8b4a77d6968e811c835bf4857d560bd4ac2376bbc1823a9c3d3df1bb0
MD5 0a455346b604fe44219b585039cc8eae
BLAKE2b-256 c4be216647628c198d94fc89ce25ccd47bca5b0942b7f12bc3ec2e75d5167d2c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page