Skip to main content

Open-source trajectory logging for LLM agents. Two lines of code to structured, replayable records of every agent run.

Project description

sentric

Open-source trajectory logging for LLM agents. Two lines of code to get structured, replayable records of every agent run — ready for evaluation and fine-tuning.

No backend. No API key. No vendor lock-in. Just local JSON files.

Quickstart

pip install sentric

OpenAI

from sentric import TrajectoryCollector, trace

collector = TrajectoryCollector(
    task_id="fix-auth-bug",
    domain="code",
    model={"name": "gpt-4o", "version": "2024-08-06", "provider": "openai"},
)

@trace(collector)
def call_llm(messages):
    return openai.chat.completions.create(model="gpt-4o", messages=messages)

# Use call_llm as normal — every call is automatically logged
response = call_llm(messages=[
    {"role": "system", "content": "You are a software engineer."},
    {"role": "user", "content": "Fix the authentication bug in auth.py"},
])

# When the agent finishes, save the trajectory
collector.save_episode()
# -> data/trajectories/f47ac10b-58cc-4372-a567-0e02b2c3d479.json

That's it. The @trace decorator auto-detects the OpenAI response, extracts the assistant message, tool calls, and token counts, and logs everything in a structured format.

Anthropic

@trace(collector)
def call_llm(messages):
    return anthropic.messages.create(model="claude-sonnet-4-20250514", messages=messages, max_tokens=4096)

Same decorator. Auto-detected. Handles text blocks and tool use blocks.

Any other LLM

def my_normalizer(response):
    return [{"role": "assistant", "content": response.text}], response.token_count

@trace(collector, normalizer=my_normalizer)
def call_llm(messages):
    return my_custom_client.generate(messages)

Write a 5-line normalizer function that tells us how to extract fields from your response type. Or skip it entirely — unknown types fall back to stringifying the response.

What gets logged

Each trajectory is a JSON file containing the full conversation:

{
  "episode_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "task_id": "fix-auth-bug",
  "domain": "code",
  "model": {
    "name": "gpt-4o",
    "version": "2024-08-06",
    "provider": "openai"
  },
  "messages": [
    {"role": "system", "content": "You are a software engineer."},
    {"role": "user", "content": "Fix the authentication bug in auth.py"},
    {
      "role": "assistant",
      "content": "Let me look at the auth module.",
      "tool_calls": [
        {"id": "call_1", "name": "bash", "arguments": "{\"command\": \"cat auth.py\"}"}
      ]
    },
    {"role": "tool", "tool_call_id": "call_1", "content": "class AuthHandler:\n    ..."}
  ],
  "reward": null,
  "success": null,
  "created_at": "2026-04-13T10:30:00Z",
  "duration_ms": 45200,
  "total_tokens": 8430,
  "metadata": {}
}

Reward fields start as null — they get filled in later by a verifier that scores whether the agent succeeded.

Manual logging

If you need more control, use the collector directly:

from sentric import TrajectoryCollector

collector = TrajectoryCollector(
    task_id="extract-invoice",
    domain="extraction",
    model={"name": "gpt-4o-mini", "version": "2024-07-18", "provider": "openai"},
)

collector.add_message(role="system", content="Extract fields from this invoice.")
collector.add_message(role="user", content="Invoice #1234...")
collector.add_message(role="assistant", content='{"vendor": "Acme", "amount": 150.00}')
collector.add_tokens(500)
collector.save_episode()

Multiple episodes

Use reset() to reuse a collector across tasks:

for task in tasks:
    collector.reset(task_id=task.id)
    run_agent(task, collector)
    collector.save_episode()

License

MIT

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

sentric-0.1.0.tar.gz (7.1 kB view details)

Uploaded Source

Built Distribution

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

sentric-0.1.0-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file sentric-0.1.0.tar.gz.

File metadata

  • Download URL: sentric-0.1.0.tar.gz
  • Upload date:
  • Size: 7.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for sentric-0.1.0.tar.gz
Algorithm Hash digest
SHA256 54761f4d1273b5e463381f2b80fc852cb6fe5e80e9addc470d7abe5d58a94e83
MD5 ee6b36d768e3b7dfdeda4cb75c2af81e
BLAKE2b-256 62ba7247b5c19f8b8b99b76c4a7ae9b12e17d16824eb19c19b6ee814800516d5

See more details on using hashes here.

File details

Details for the file sentric-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: sentric-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for sentric-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e50cf61fa6cf8affd40a59b33565f93df2145ea896b8ec9704bce40cc5e8120c
MD5 df96ead21bfbe48385c7a550442666b0
BLAKE2b-256 609f0c4515b2e073778d13b297ab157f5f637d3a3b52075e670e172ab8c031ed

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