Metadata-Version: 2.4
Name: liyagent
Version: 0.2.1
Summary: Official Python SDK for Liyagent, the governed AI agent platform: agents, runs, approvals, evals, knowledge and solution templates
Author-email: Liyagent <docs@liyagent.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://liyagent.com
Project-URL: Documentation, https://liyagent.com/docs/reference/sdks
Project-URL: API reference, https://liyagent.com/docs/reference/api-endpoints
Project-URL: Changelog, https://liyagent.com/docs/reference/sdk-changelog
Project-URL: Issues, https://liyagent.com/docs/reference/sdks#support
Project-URL: Security, https://liyagent.com/security
Keywords: liyagent,ai agents,agents,agent platform,llm,evals,approvals,human-in-the-loop,ai governance,itsm,sdk,api client
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Framework :: AsyncIO
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.25
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# Liyagent Python SDK

[![PyPI version](https://img.shields.io/pypi/v/liyagent.svg)](https://pypi.org/project/liyagent/)
[![Python versions](https://img.shields.io/pypi/pyversions/liyagent.svg)](https://pypi.org/project/liyagent/)
[![License](https://img.shields.io/pypi/l/liyagent.svg)](https://www.apache.org/licenses/LICENSE-2.0)

**Build, run, evaluate and govern AI agents on [Liyagent](https://liyagent.com) 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 `Client` and asyncio `AsyncClient`, 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`](https://www.python-httpx.org/)

## Install

```bash
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](https://liyagent.com/docs/govern/identity-roles#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.

```bash
export LIYA_URL="https://liyagent.com"
export LIYA_TOKEN="svc/ci-deployer:..."
```

**3. Run it.**

```python
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](https://liyagent.com/docs/reference/api-endpoints)
page documents every field.

### Agents

```python
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

```python
# 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:

```python
for item in liya.approvals.list(status="pending"):
    liya.approvals.approve(item["id"], reason="checked", digest=item.get("digest", ""))
```

### Evals

```python
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

```python
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.

```python
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`.

```python
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).

```python
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

```python
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 `TypedDict`s 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](https://liyagent.com/docs) and the
  [SDK guide](https://liyagent.com/docs/reference/sdks)
- HTTP API: [overview](https://liyagent.com/docs/reference/api) and
  [endpoints](https://liyagent.com/docs/reference/api-endpoints)
- [Changelog](https://liyagent.com/docs/reference/sdk-changelog)
- TypeScript SDK: [`@liyagent/sdk`](https://www.npmjs.com/package/@liyagent/sdk)
- Support: [docs@liyagent.com](mailto:docs@liyagent.com). Include the error's request id.
- Security: [liyagent.com/security](https://liyagent.com/security)

## Licence

Apache-2.0
