Liyagent Python SDK
Build, run, evaluate and govern AI agents on Liyagent from Python. Install an agent from a template, ask it a question, gate a release on its eval score and work its approval queue, all with the same permissions, guardrails and audit trail as the console.
- Blocking
Clientand asyncioAsyncClient, with the same calls - Typed request bodies (
py.typed), a typed exception per error kind - Retries with backoff on 429 and 5xx (respects
Retry-After), timeouts - One dependency:
httpx
Install
pip install liyagent
Python 3.10 or later.
Quickstart
1. Get a token. In the Liyagent console, open Access → Service accounts → + New
service account, choose the account it acts as and copy the secret (it is shown
once). The token is svc/<name>:<secret>. The service account can do only what that
account's role allows. Installing a template, as below, needs a role that writes
agents and guardrails, such as Administrator. See
Service accounts.
2. Set the address and the token. The address is the URL you open the console at:
https://liyagent.com for the hosted service, or your own host if you run
Liyagent yourself.
export LIYA_URL="https://liyagent.com"
export LIYA_TOKEN="svc/ci-deployer:..."
3. Run it.
from liyagent import Client
with Client() as liya: # reads LIYA_URL and LIYA_TOKEN
for t in liya.templates.list(solution="itsm"):
print(t["key"], t["version"], t.get("title", ""))
plan = liya.templates.preview("it-ticket-triage", agent_id="desk-triage")
if plan["valid"]:
liya.templates.install("it-ticket-triage", agent_id="desk-triage")
else:
print("would be refused at", plan["step"], plan["refusal"]["detail"])
reply = liya.agents.ask("desk-triage", "VPN drops every 10 minutes since this morning")
print(reply["reply"] or reply.get("skipped"))
You can also pass everything explicitly:
Client("https://liyagent.com", token="svc/ci-deployer:...").
Authentication
| Credential | Use it for | How |
|---|---|---|
Service account svc/<name>:<secret> |
CI, scripts, services (recommended) | token= or LIYA_TOKEN. Exchanged for a one-hour session, renewed automatically. |
| Session token | A token another step minted | token= (sent as it is, not renewed) |
| Username and password | Local experiments | username=, password=. Accounts that require an authenticator code can't sign in this way; use a service account. |
| Instance API key or client token | knowledge.answer only |
api_key= or LIYA_API_KEY |
Core concepts
Every namespace maps to one part of the platform. Calls return the server's JSON
(dict/list) as it is. The API endpoints
page documents every field.
Agents
for agent in liya.agents.list(filter="enabled = true"): # follows every page
print(agent["id"], agent["label"])
liya.agents.put("desk-helper", label="Desk helper", tags=["team=it"]) # create or change
answer = liya.agents.ask("desk-helper", "How do I reset my password?")
bundle = liya.agents.export("desk-helper") # agents as code
liya.agents.apply(bundle, dry_run=True) # plan without writing
Runs
# A workflow runs to its end, or to its first wait (an approval).
run = liya.agents.run_workflow("desk-triage", "triage", {"title": "Laptop won't boot"})
print(run["run_id"], run["status"], run.get("waiting_on") or run.get("error"))
run = liya.agents.workflow_run("desk-triage", run["run_id"]) # poll it later
recent = liya.runs.list(limit=20) # agent runs, newest first
# A run suspended for input resumes with the answer:
# liya.runs.resume("<run id>", answer="yes")
Approvals
Risky steps wait for a person. Approve by digest so an edit made since you read it refuses the decision:
for item in liya.approvals.list(status="pending"):
liya.approvals.approve(item["id"], reason="checked", digest=item.get("digest", ""))
Evals
started = liya.evals.start_run("desk-triage-evals", "desk-triage") # a background job
result = liya.evals.wait(started["run"]["id"], timeout=900)
print(result["status"], result["metrics"])
To gate a release, pin a good run as the baseline in the console, then pass
baseline="pinned", allowed_drop=0.05. result["passed"] is then False when the
agent scores worse than the baseline by more than the allowed drop.
Knowledge
hits = Client(api_key="...").knowledge.answer("How do I connect to the VPN?", top_k=3)
for article in liya.knowledge.articles(status="draft")["articles"]:
liya.knowledge.approve_article(article["id"])
Templates
A template installs an agent with its guardrails, workflow, triggers, eval set and
budget in one step. preview makes every write and rolls it back, so you see the plan
and any refusal first.
liya.templates.install("hr-policy-answers", agent_id="people-policy",
params={"team": "People Ops"})
liya.templates.uninstall("people-policy", reason="replaced", dry_run=True)
To publish your own templates, use draft_from_agent, validate, save_draft,
publish and withdraw. These need templates.write.
Anything else
liya.request("GET", "/api/...", params={...}, json={...}) calls any route with the
same credentials, retries and errors. liya.check_server() lists the operations this
SDK uses that an older server does not serve.
Errors
Every failure raises a subclass of liyagent.LiyagentError:
| Exception | When | kind |
|---|---|---|
AuthenticationError |
401: no credentials, or they were refused | auth |
PermissionDeniedError |
403: the role lacks a permission (the message names it) | forbidden |
NotFoundError |
404 | not_found |
BadRequestError |
400, 422: the body does not match the schema | invalid |
ConflictError |
409, 412: exists already, or changed since read | conflict |
RateLimitError |
429, after the retries | rate_limited |
InternalServerError |
5xx, after the retries | server |
APIConnectionError |
the server was not reached or timed out | unreachable |
Each one carries status, detail (the server's reason; for a bundle or template
refusal, {message, at} pointing at the part of the document), request_id (quote it
to support), method and path.
from liyagent import Client, NotFoundError, PermissionDeniedError
try:
liya.agents.get("no-such-agent")
except NotFoundError as e:
print(e.status, e.detail, e.request_id)
except PermissionDeniedError as e:
print("ask an admin for:", e.detail)
Retries and timeouts
A 429 or 503 is sent again for any method, because the server did not run it. So is a
request that never reached the server. A 500, 502 or 504, or a read timeout, is retried
only for GET, PUT and DELETE, which are safe to repeat. The client waits as long as
Retry-After says, or otherwise backs off exponentially with jitter (0.5 s, 1 s, 2 s,
up to 8 s).
liya = Client(max_retries=5, timeout=30.0) # defaults: 2 retries, 60 s
liya = Client(max_retries=0) # no retries
Set logging.getLogger("liyagent") to INFO to see each retry.
Async
import asyncio
from liyagent import AsyncClient
async def main() -> None:
async with AsyncClient() as liya:
async for agent in liya.agents.list():
print(agent["id"], agent["label"])
templates, runs = await asyncio.gather(liya.templates.list(), liya.runs.list())
asyncio.run(main())
Types
The package ships py.typed. Request bodies are TypedDicts in liyagent.models
(TemplateInstallBody, BundleApplyBody, EvalOfflineRunBody, ...). They are
generated from the server's OpenAPI document, so mypy and pyright check what you send.
Links
- Documentation: liyagent.com/docs and the SDK guide
- HTTP API: overview and endpoints
- Changelog
- TypeScript SDK:
@liyagent/sdk - Support: docs@liyagent.com. Include the error's request id.
- Security: liyagent.com/security
Licence
Apache-2.0
Metadata
Release files for liyagent 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| liyagent-0.2.1.tar.gz | 31.5 kB | Without provenance | Details for liyagent-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| liyagent-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for liyagent-0.2.1-py3-none-any.whl |
Total release size: 59.9 kB
Release files / liyagent-0.2.1.tar.gz
| Download URL | liyagent-0.2.1.tar.gz |
|---|---|
| Size | 31.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a450b2b557037a96750eeeeffba7cf1d153978c87d3cf24949c972e988e31179
|
|
BLAKE2b-256 checksum How to use checksums |
131949336dc18125c5301bb47d5328b3123434aab1c953f2a786e9d1ebdd1583
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / liyagent-0.2.1-py3-none-any.whl
| Download URL | liyagent-0.2.1-py3-none-any.whl |
|---|---|
| Size | 28.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
58eee2a098792f5be4db42b1dc43b4a90ed4448dcdf602a5b14c59562fc03b08
|
|
BLAKE2b-256 checksum How to use checksums |
bedb02b392fb195ccc294a6ab408084a998edb9d02a95ea820a7caa43b838183
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Security for liyagent 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.