Skip to main content

Zhanla SDK for instrumenting AI components

Project description

zhanla-sdk-py

Python SDK for defining Zhanla components in code.

Use it to declare tools, skills, agents, orchestrations, and evals as module-level Python objects, then run or upload them with the zhanla CLI.

Installation

pip install zhanla-sdk-py zhanla

Requires Python >=3.10.

Install a provider SDK only when your component makes LLM calls:

pip install anthropic
pip install openai
pip install google-genai

Quick start

Create components.py:

import zhanla


def classify_priority(message: str, customer_tier: str = "standard", **_) -> dict:
    if "urgent" in message.lower() or customer_tier == "enterprise":
        return {"priority": "high"}
    return {"priority": "normal"}


priority_tool = zhanla.Tool(
    name="priority_tool",
    description="Classify support ticket priority.",
    key="priority-tool",
    fn=classify_priority,
    input_schema={"message": str, "customer_tier": str},
    output_schema={"priority": str},
)


def priority_eval(model_response, expected_output=None, **_) -> dict:
    response = zhanla.parse_json_response(model_response)
    expected = zhanla.parse_json_response(expected_output or "{}")
    return {
        "score": 1.0
        if response.get("priority") == expected.get("priority")
        else 0.0
    }


priority_eval_component = zhanla.CodeEval(
    name="priority_eval",
    description="Check predicted priority.",
    key="priority-eval",
    fn=priority_eval,
)

Create tickets.json:

[
  { "_schema": { "message": "string", "expected_output": "object" } },
  { "message": "urgent outage", "expected_output": { "priority": "high" } }
]

Run locally:

zhanla validate components.py
zhanla run components.py:priority-tool --dataset tickets.json --eval components.py:priority-eval --dry-run --yes

After zhanla login, drop --dry-run to sync definitions, dataset rows, and results to the web app.

Authoring rules

  • Import with import zhanla.
  • Define components as module-level objects so the CLI can discover them.
  • Give every component an explicit stable key.
  • Keys must contain only lowercase letters, digits, and hyphens.
  • CLI target suffixes use key, not Python variable name or display name.
  • Prompt-backed components need model plus exactly one of client or runner for local execution.
  • Skill is prompt-only configuration and cannot be run directly.

Provider-backed components

Use client= for normal provider usage:

import anthropic
import zhanla

support_agent = zhanla.Agent(
    name="support_agent",
    description="Respond to support requests.",
    key="support-agent",
    instructions='Answer clearly. Return JSON: {"answer": "..."}',
    model="claude-sonnet-4-6",
    client=anthropic.Anthropic(),
    output_schema={"answer": str},
    json_repair=True,
)

client= creates a zhanla.Runner internally. Use runner= when you need custom runner behavior or want to share one runner:

runner = zhanla.Runner(client=anthropic.Anthropic())

OpenAI:

import openai

agent = zhanla.Agent(
    name="openai_agent",
    description="Use OpenAI.",
    key="openai-agent",
    instructions="Return JSON.",
    model="gpt-4.1",
    client=openai.OpenAI(),
)

Google Gemini:

import google.genai

agent = zhanla.Agent(
    name="gemini_agent",
    description="Use Gemini.",
    key="gemini-agent",
    instructions="Return JSON.",
    model="gemini-2.5-flash",
    client=google.genai.Client(),
)

OpenRouter through the OpenAI-compatible client:

import os
import openai

runner = zhanla.Runner(
    client=openai.OpenAI(
        base_url="https://openrouter.ai/api/v1",
        api_key=os.environ["OPENROUTER_API_KEY"],
    )
)

Components

Tool

Use a Tool for deterministic Python logic.

lookup_customer = zhanla.Tool(
    name="lookup_customer",
    description="Fetch a customer record.",
    key="lookup-customer",
    fn=get_customer,
    input_schema={"customer_id": str},
    output_schema={"id": str, "email": str},
)

fn can be sync or async. Non-dict returns are wrapped as {"result": value}.

Python schemas can be shorthand dicts, JSON-Schema-shaped dicts, or Pydantic model classes. The CLI validates the first local output against output_schema.

Skill

Use a Skill for reusable prompt instructions.

summarize_ticket = zhanla.Skill(
    name="summarize_ticket",
    description="Summarize support tickets.",
    key="summarize-ticket",
    instructions="Summarize the ticket in one short paragraph.",
)

Skills can be attached to agents or orchestrations, but they are not top-level local runtime targets.

Agent

Use an Agent for LLM-backed work with optional tools, skills, and nested agents.

support_agent = zhanla.Agent(
    name="support_agent",
    description="Respond to support requests.",
    key="support-agent",
    instructions="Answer clearly and use available tools when needed.",
    model="claude-sonnet-4-6",
    client=anthropic.Anthropic(),
    tools=[lookup_customer],
    skills=[summarize_ticket],
    output_schema={"answer": str},
)

LLMProcessor

Use an LLMProcessor for one prompt-defined transformation.

intent_classifier = zhanla.LLMProcessor(
    name="intent_classifier",
    description="Classify intent.",
    key="intent-classifier",
    instructions='Return JSON: {"intent": "billing|technical|other"}',
    model="claude-sonnet-4-6",
    client=anthropic.Anthropic(),
    output_schema={"intent": str},
)

Orchestration

Use Step to compose components into a DAG.

support_pipeline = zhanla.Orchestration(
    name="support_pipeline",
    description="Classify priority, then reply.",
    key="support-pipeline",
    steps=[
        zhanla.Step(component=priority_tool, name="classify", next=["reply"]),
        zhanla.Step(component=support_agent, name="reply"),
    ],
)

Use Conditional for routing:

zhanla.Step(
    name="route",
    component=zhanla.Conditional(
        condition=lambda state: state["classify"]["priority"] == "high",
        if_true="urgent_reply",
        if_false="normal_reply",
    ),
)

Evals

CodeEval

CodeEval functions receive canonical text kwargs:

  • model_response
  • expected_output
  • model_input

model_response must be a required parameter. The other two can be optional.

def score(model_response, expected_output=None, model_input=None, **_):
    response = zhanla.parse_json_response(model_response)
    expected = zhanla.parse_json_response(expected_output or "{}")
    return {"score": 1.0 if response == expected else 0.0}

Non-dict returns are wrapped as {"score": value}.

model_response_format defaults to "JSON" and can be set to "TEXT" or "YAML" for synced eval metadata. Current local and CLI-managed eval-only execution still passes strings to the function, so parse structured values yourself.

LLMEval

Use either instructions or questions, not both.

tone_eval = zhanla.LLMEval(
    name="tone_eval",
    description="Evaluate tone.",
    key="tone-eval",
    instructions='Return JSON: {"score": 0.0, "reason": "..."}',
    model="claude-sonnet-4-6",
    client=anthropic.Anthropic(),
    output_schema={"score": float, "reason": str},
)

Checklist and EvalTree

answer_quality = zhanla.Checklist(
    name="answer_quality",
    description="Combine evals.",
    key="answer-quality",
    evals=[priority_eval_component, tone_eval],
    weights=[0.8, 0.2],
)
adaptive_eval = zhanla.EvalTree(
    name="adaptive_eval",
    description="Route evals by threshold.",
    key="adaptive-eval",
    root=zhanla.Branch(
        eval=priority_eval_component,
        threshold=0.8,
        if_pass=[zhanla.Edge(weight=1.0, node=zhanla.Leaf(eval=priority_eval_component))],
        if_fail=[zhanla.Edge(weight=1.0, node=zhanla.Leaf(eval=tone_eval))],
    ),
)

CLI usage

zhanla validate workflow.py
zhanla upload workflow.py:support-pipeline
zhanla run workflow.py:support-pipeline --dataset tickets.json --eval evals.py:answer-quality --dry-run --yes
zhanla run workflow.py:support-pipeline --dataset tickets.json --web-eval answer-quality --yes

Local JSON datasets must start with a _schema metadata row. CSV datasets use their header row as fields.

Programmatic execution

Run a component in your app with trajectory tracking:

import zhanla

output = await zhanla.run_component(support_agent, {"message": "Need help"})

Set ZHANLA_API_KEY="bm_kid_....bm_sec_..." to export production LLM trajectories. The exporter is fail-silent and batches in the background.

Use zhanla.execute_component(component, input) for bare dispatch without trajectory export. In serverless environments, call zhanla.flush() before shutdown.

Observability helpers

Use zhanla.wrap(client) when calling a provider client directly inside a tool or helper function:

client = zhanla.wrap(anthropic.Anthropic())

Runner-backed components already wrap their client internally.

Use zhanla.parse_json_response(text) to parse bare JSON or fenced JSON from model output.

Common gotchas

  1. Components must be module-level for CLI discovery.
  2. Every component needs an explicit valid key.
  3. CLI target suffixes use key, not variable name or display name.
  4. Skill cannot run directly.
  5. Prompt-backed local execution requires model plus client or runner, not both.
  6. Tool.input_schema must normalize to an object JSON Schema.
  7. Local eval kwargs are text strings; parse JSON/YAML inside the eval body.
  8. Python CodeEval.fn must require model_response.
  9. LLMEval must provide exactly one of instructions or questions.
  10. Local JSON datasets must start with _schema; {"schema": ..., "rows": ...} is not accepted by the CLI.

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

zhanla_sdk_py-0.1.2.12.tar.gz (33.3 kB view details)

Uploaded Source

Built Distribution

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

zhanla_sdk_py-0.1.2.12-py3-none-any.whl (36.5 kB view details)

Uploaded Python 3

File details

Details for the file zhanla_sdk_py-0.1.2.12.tar.gz.

File metadata

  • Download URL: zhanla_sdk_py-0.1.2.12.tar.gz
  • Upload date:
  • Size: 33.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zhanla_sdk_py-0.1.2.12.tar.gz
Algorithm Hash digest
SHA256 d9937653cc1489a7012159e22eee42c4a1c84ba7cbe56addceb2b4ffac19fb21
MD5 6a8e5c11b12653a990ff1e569f58816c
BLAKE2b-256 bf7e9d0453b73cfeb4321f97b07d92718ca650334b01661d8f58ea6c39c31170

See more details on using hashes here.

File details

Details for the file zhanla_sdk_py-0.1.2.12-py3-none-any.whl.

File metadata

  • Download URL: zhanla_sdk_py-0.1.2.12-py3-none-any.whl
  • Upload date:
  • Size: 36.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zhanla_sdk_py-0.1.2.12-py3-none-any.whl
Algorithm Hash digest
SHA256 3c23f8c043d43cac8c23b8caf46584769fcc99998fbb90c9edd5182656d1e88f
MD5 520338a7c42f8b33cce5fb472307d560
BLAKE2b-256 c978246f79c788a46f8c66410265d0bbb53e566dd8f251309aa81ceac56ebc2f

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