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 displayname. - Prompt-backed components need
modelplus exactly one ofclientorrunnerfor local execution. Skillis 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_responseexpected_outputmodel_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
- Components must be module-level for CLI discovery.
- Every component needs an explicit valid
key. - CLI target suffixes use
key, not variable name or displayname. Skillcannot run directly.- Prompt-backed local execution requires
modelplusclientorrunner, not both. Tool.input_schemamust normalize to an object JSON Schema.- Local eval kwargs are text strings; parse JSON/YAML inside the eval body.
- Python
CodeEval.fnmust requiremodel_response. LLMEvalmust provide exactly one ofinstructionsorquestions.- Local JSON datasets must start with
_schema;{"schema": ..., "rows": ...}is not accepted by the CLI.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9937653cc1489a7012159e22eee42c4a1c84ba7cbe56addceb2b4ffac19fb21
|
|
| MD5 |
6a8e5c11b12653a990ff1e569f58816c
|
|
| BLAKE2b-256 |
bf7e9d0453b73cfeb4321f97b07d92718ca650334b01661d8f58ea6c39c31170
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c23f8c043d43cac8c23b8caf46584769fcc99998fbb90c9edd5182656d1e88f
|
|
| MD5 |
520338a7c42f8b33cce5fb472307d560
|
|
| BLAKE2b-256 |
c978246f79c788a46f8c66410265d0bbb53e566dd8f251309aa81ceac56ebc2f
|