Skip to main content

lilac-sdk

Python SDK for Lilac — agent observability. Capture LLM exchanges with one line, no wrapping of your provider call, no added latency.

Install

pip install lilac-sdk

Quickstart

import lilac

lilac.init(api_key="sk_live_...", agent_id="agt_yourAgentId")
# Self-hosted: set LILAC_ENDPOINT (and LILAC_DEPLOYMENT_MODE=self_hosted) in
# your environment instead of passing endpoint= here. See "Endpoint
# resolution" below.
import lilac
from openai import OpenAI, APIError, RateLimitError

client = OpenAI()
lilac.init(api_key="sk_live_...", agent_id="agt_yourAgentId")

def handle_user_message(conversation_id: str, user_text: str) -> str:
    call_input = [{"role": "user", "content": user_text}]
    try:
        response = client.responses.create(model="gpt-5.5", input=call_input)
    except (APIError, RateLimitError) as e:
        # The call still happened and still belongs to this conversation —
        # record it as a failed exchange rather than letting it vanish.
        lilac.capture_failure(input=call_input, conversation_id=conversation_id, error=str(e))
        raise

    lilac.capture(response, input=call_input, conversation_id=conversation_id)
    return response.output_text

That's the whole integration for a single exchange. capture() reads model/token usage off the real OpenAI/Anthropic response object automatically — you never set those by hand.

Endpoint resolution

lilac.init() resolves where to send data by checking these in order:

  1. Explicit endpoint= passed to init()
  2. LILAC_ENDPOINT environment variable — the normal path, set once per deployment in your own application's environment
  3. Default https://api.trylilac.ai/v1 — used automatically only for the Online/hosted product (or when deployment mode is unset)

Self-hosted deployments fail closed, not open. Set LILAC_DEPLOYMENT_MODE=self_hosted alongside LILAC_ENDPOINT (or pass deployment="self_hosted" to init()). With this set, if LILAC_ENDPOINT is missing or empty for any reason, init() raises immediately at startup instead of silently falling back to the public api.trylilac.ai endpoint. This matters most for government/compliant-channel deployments, where telemetry reaching the public endpoint even once is a real compliance failure, not just a misconfiguration.

# Self-hosted
export LILAC_DEPLOYMENT_MODE=self_hosted
export LILAC_ENDPOINT=https://your-lilac-host/v1

# Online — omit both; the SDK defaults to the hosted endpoint

capture()

lilac.capture(
    response,
    input=call_input,
    conversation_id=conversation_id,
    user_id=current_user.id,              # hashed at ingest, never stored in the clear
    system_prompt=SYSTEM_PROMPT,          # feeds judge-tier grounding when set
    output_type="answer",                 # answer | handoff | action | refusal | clarification
    correlation_ref=order_id,             # machine-to-machine join key
    tags={"plan": "enterprise", "region": "us-east"},
)
Parameter Required Notes
input required The same input/messages value you passed to the provider call — this is the only source of the user's turn; the response object doesn't contain it.
conversation_id required (or via context manager, below) Groups this event with the rest of its conversation.
user_id optional Enables cross-session/return-rate analysis; hashed at ingest.
system_prompt optional Feeds judge-tier grounding when set.
output_type optional answer, handoff, action, refusal, or clarification.
correlation_ref optional Machine-to-machine join key for linking this session to another.
tags optional Any flat dict of string keys/values — segmentation (plan tier, region, experiment arm, etc).

capture_failure()

The except-block counterpart to capture() — records a failed exchange so provider-side outages/rate limits don't silently vanish from your session data. Optional, but recommended for any agent running at meaningful volume.

try:
    response = client.responses.create(model="gpt-5.5", input=call_input)
except (APIError, RateLimitError) as e:
    lilac.capture_failure(input=call_input, conversation_id=conversation_id, error=str(e))
    raise

Streaming

call_input = [{"role": "user", "content": user_text}]
stream = client.responses.create(model="gpt-5.5", input=call_input, stream=True)
for chunk in lilac.capture_stream(stream, input=call_input, conversation_id=conversation_id):
    yield chunk   # your consumption of the stream is unchanged

Multi-turn conversations — the context manager

If a single conversation spans several separate capture() calls (a multi-turn chat, or a call several function calls deep inside a tool dispatcher), with lilac.conversation(id): sets conversation_id once and every nested capture() call underneath inherits it automatically — a real contextvars value, propagating through async/await and nested calls with no explicit threading.

with lilac.conversation(conversation_id):
    previous_id = None
    for user_text in incoming_messages():
        response = client.responses.create(
            model="gpt-5.5", previous_response_id=previous_id, input=user_text,
        )
        lilac.capture(response, input=user_text)   # conversation_id inherited
        previous_id = response.id

lilac.set_conversation(id) is the bare-setter equivalent for places a context manager doesn't fit cleanly (middleware, framework hooks, background workers).

Precedence, never silent: explicit conversation_id= argument > context variable > raise. If neither is set, Lilac never guesses — it raises LilacUsageError so the gap is visible during development.

Drop-in wrapper

For bespoke/custom agent loops with call sites scattered across a large codebase — swap your provider import, every call through that client is captured automatically:

# Before
from openai import OpenAI
client = OpenAI()

# After
from lilac.openai import OpenAI
client = OpenAI(lilac_api_key="sk_live_...", agent_id="agt_yourAgentId")

# Every call below is captured automatically — no lilac.capture() needed
with lilac.conversation(conversation_id):
    response = client.responses.create(model="gpt-5.5", input=[...])

Trade-off worth knowing: this wrapper owns the client instance, so it's the right fit for a single-vendor integration but doesn't compose as cleanly if another tool is also wrapping the same client. Use capture() instead if you're layering Lilac alongside another observability tool on the same call sites.

Callback function

from lilac.integrations.langchain import LilacCallbackHandler

agent_executor.invoke(
    {"input": user_text},
    config={"callbacks": [LilacCallbackHandler(conversation_id=conversation_id)]},
)

Requires the langchain extra: pip install lilac-sdk[langchain].

The same pattern applies to any framework exposing a callback/hook interface around its LLM calls (LlamaIndex, CrewAI, Semantic Kernel, and similar) — the handler implementation differs per framework's callback interface, but this SDK currently ships only the LangChain handler; others are on the roadmap.

Raw OTLP (non-Python stacks)

Send OpenTelemetry spans directly to the same endpoint every integration method above uses under the hood: POST {endpoint}/ingest/otlp. See the full API reference for the span attribute contract.

License

MIT

Download files

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

Source Distribution

lilac_sdk-0.1.0.tar.gz (18.9 kB view details)

Uploaded Source

Built Distribution

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

lilac_sdk-0.1.0-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for lilac_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f8929b469561e55d917a6e2647a239bedc74193a35610787c66175bdc6e4aaab
MD5 f59150216c5f8562853623221ce28d76
BLAKE2b-256 2542225c82cd23e1f0584adfb98beca5b84315d1430cebb53894880748aadf81

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for lilac_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 23bb2de6a7fd0a8eff369e8329f6ee4e0c6d2a1cdced2ccb2423abe7457006c8
MD5 9db013ad1b125f2ba2294289e2c85602
BLAKE2b-256 10158e0b58325c138d3963a424f2aec01ba3b0df923e70a8f7b31296306055aa

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