liyaengine 0.2.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: liyaengine
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Official Python client for the Liya Engine public API
5
5
  Project-URL: Homepage, https://liyaengine.ai
6
6
  Project-URL: Documentation, https://liyaengine.ai/docs/sdks/python
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
31
31
 
32
32
  Official Python client for the [Liya Engine](https://liyaengine.ai) public API.
33
33
 
34
- > **Status: early access.** This SDK currently covers Collections and Agents. More resources (Domains, Run, Workflows, Guardrail Policies, Evals) ship incrementally — see [Roadmap](#roadmap).
34
+ > **Status: early access.** This SDK currently covers Collections, Agents, Workflows, and Evaluations. More resources (Domains, Run, Guardrail Policies) ship incrementally — see [Roadmap](#roadmap).
35
35
 
36
36
  ## Install
37
37
 
@@ -81,6 +81,66 @@ result = client.agents.run(agent.agent_key, input={"message": "My order hasn't a
81
81
  history = client.agents.list_runs(agent.agent_key)
82
82
  ```
83
83
 
84
+ ## Workflows
85
+
86
+ ```python
87
+ workflow = client.workflows.create(
88
+ name="Lead Intake",
89
+ steps=[{"step_type": "trigger", "config": {"trigger_subtype": "webhook"}}],
90
+ )
91
+
92
+ # Workflows are created in draft status — deploy to publish and make them
93
+ # callable. Deploying a webhook-triggered workflow for the first time mints
94
+ # its webhook secret; capture it immediately, it is never returned again.
95
+ deployed = client.workflows.deploy(workflow.workflow_key)
96
+ webhook_url, webhook_secret = deployed["webhook_url"], deployed["webhook_secret"]
97
+
98
+ # Roll the secret with a grace window so in-flight senders don't break.
99
+ client.workflows.rotate_webhook_secret(workflow.workflow_key, grace_period_seconds=300)
100
+
101
+ # Flip a deployed workflow on/off without touching its definition.
102
+ client.workflows.toggle(workflow.workflow_key)
103
+
104
+ result = client.workflows.run(workflow.workflow_key, input={"email": "ada@example.com"})
105
+
106
+ history = client.workflows.list_runs(workflow.workflow_key)
107
+ ```
108
+
109
+ > `deploy()` and `rotate_webhook_secret()` return the plaintext webhook secret exactly once. Store it immediately — subsequent reads (`get`, `list`) only ever expose `trigger_config["has_secret"]`.
110
+
111
+ ## Evaluations
112
+
113
+ A Suite binds a Dataset to one specific intent; `suites.run()` calls that intent for real and scores what it produces. `runs.submit()`/`evaluations.score()` score a response you already generated yourself — no intent execution involved.
114
+
115
+ ```python
116
+ dataset = client.evaluations.datasets.create(
117
+ name="Support Replies",
118
+ cases=[{"input": {"message": "Where is my order?"}}],
119
+ )
120
+
121
+ suite = client.evaluations.suites.create(
122
+ name="Order Status Suite",
123
+ domain_key="support",
124
+ intent_key="order-status",
125
+ dataset_id=dataset.id,
126
+ )
127
+
128
+ # Runs are always async — poll for status/results.
129
+ run = client.evaluations.suites.run(suite.id)
130
+ result = client.evaluations.runs.get(run.id)
131
+
132
+ # A failed run can be resumed — already-scored cases are skipped.
133
+ client.evaluations.runs.resume(run.id)
134
+
135
+ # Score a response you already generated, no dataset/suite required.
136
+ scored = client.evaluations.score(
137
+ input={"message": "Where is my order?"},
138
+ output="Your order shipped yesterday and should arrive by Friday.",
139
+ )
140
+ ```
141
+
142
+ > `custom_scorer_webhook_secret` on a Suite is write-only — it's never returned; only a `custom_scorer_webhook_secret_set` boolean comes back on reads.
143
+
84
144
  ## Error handling
85
145
 
86
146
  Every failed request raises `LiyaEngineAPIError`, carrying the API's `code`, `message`, and HTTP `status`:
@@ -114,11 +174,11 @@ LiyaEngine(
114
174
 
115
175
  - [x] Collections
116
176
  - [x] Agents (full CRUD, deploy, run, run/session history)
177
+ - [x] Workflows (full CRUD, toggle, deploy, webhook secret rotate, run, run history)
178
+ - [x] Evaluations (Datasets/Cases/Suites/Runs/Reviews CRUD, suite execution, cancel/resume, statistical + pairwise compare, standalone scoring)
117
179
  - [ ] Domains (custom domain + intent CRUD)
118
180
  - [ ] Run / Run (streaming)
119
- - [ ] Workflows
120
181
  - [ ] Guardrail Policies
121
- - [ ] Evaluations
122
182
  - [ ] Async client
123
183
 
124
184
  Full docs: https://liyaengine.ai/docs/sdks/python
@@ -0,0 +1,168 @@
1
+ # liyaengine
2
+
3
+ Official Python client for the [Liya Engine](https://liyaengine.ai) public API.
4
+
5
+ > **Status: early access.** This SDK currently covers Collections, Agents, Workflows, and Evaluations. More resources (Domains, Run, Guardrail Policies) ship incrementally — see [Roadmap](#roadmap).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install liyaengine
11
+ ```
12
+
13
+ ## Quickstart
14
+
15
+ ```python
16
+ from liyaengine import LiyaEngine
17
+
18
+ client = LiyaEngine(api_key="liya_...")
19
+
20
+ collection = client.collections.create(
21
+ slug="contracts",
22
+ label="Contracts",
23
+ domain_keys=["legal-ops"],
24
+ )
25
+
26
+ collections = client.collections.list()
27
+ ```
28
+
29
+ Or as a context manager (closes the underlying HTTP connection pool automatically):
30
+
31
+ ```python
32
+ with LiyaEngine(api_key="liya_...") as client:
33
+ collections = client.collections.list()
34
+ ```
35
+
36
+ Get an API key from your [Liya Engine dashboard](https://app.liyaengine.ai) under Settings → API Keys.
37
+
38
+ ## Agents
39
+
40
+ ```python
41
+ agent = client.agents.create(
42
+ agent_key="support-triage",
43
+ name="Support Triage",
44
+ goal="Triage incoming support tickets and route them to the right team.",
45
+ )
46
+
47
+ # Agents are created in draft status — deploy to activate for execution.
48
+ client.agents.deploy(agent.agent_key)
49
+
50
+ result = client.agents.run(agent.agent_key, input={"message": "My order hasn't arrived yet."})
51
+
52
+ history = client.agents.list_runs(agent.agent_key)
53
+ ```
54
+
55
+ ## Workflows
56
+
57
+ ```python
58
+ workflow = client.workflows.create(
59
+ name="Lead Intake",
60
+ steps=[{"step_type": "trigger", "config": {"trigger_subtype": "webhook"}}],
61
+ )
62
+
63
+ # Workflows are created in draft status — deploy to publish and make them
64
+ # callable. Deploying a webhook-triggered workflow for the first time mints
65
+ # its webhook secret; capture it immediately, it is never returned again.
66
+ deployed = client.workflows.deploy(workflow.workflow_key)
67
+ webhook_url, webhook_secret = deployed["webhook_url"], deployed["webhook_secret"]
68
+
69
+ # Roll the secret with a grace window so in-flight senders don't break.
70
+ client.workflows.rotate_webhook_secret(workflow.workflow_key, grace_period_seconds=300)
71
+
72
+ # Flip a deployed workflow on/off without touching its definition.
73
+ client.workflows.toggle(workflow.workflow_key)
74
+
75
+ result = client.workflows.run(workflow.workflow_key, input={"email": "ada@example.com"})
76
+
77
+ history = client.workflows.list_runs(workflow.workflow_key)
78
+ ```
79
+
80
+ > `deploy()` and `rotate_webhook_secret()` return the plaintext webhook secret exactly once. Store it immediately — subsequent reads (`get`, `list`) only ever expose `trigger_config["has_secret"]`.
81
+
82
+ ## Evaluations
83
+
84
+ A Suite binds a Dataset to one specific intent; `suites.run()` calls that intent for real and scores what it produces. `runs.submit()`/`evaluations.score()` score a response you already generated yourself — no intent execution involved.
85
+
86
+ ```python
87
+ dataset = client.evaluations.datasets.create(
88
+ name="Support Replies",
89
+ cases=[{"input": {"message": "Where is my order?"}}],
90
+ )
91
+
92
+ suite = client.evaluations.suites.create(
93
+ name="Order Status Suite",
94
+ domain_key="support",
95
+ intent_key="order-status",
96
+ dataset_id=dataset.id,
97
+ )
98
+
99
+ # Runs are always async — poll for status/results.
100
+ run = client.evaluations.suites.run(suite.id)
101
+ result = client.evaluations.runs.get(run.id)
102
+
103
+ # A failed run can be resumed — already-scored cases are skipped.
104
+ client.evaluations.runs.resume(run.id)
105
+
106
+ # Score a response you already generated, no dataset/suite required.
107
+ scored = client.evaluations.score(
108
+ input={"message": "Where is my order?"},
109
+ output="Your order shipped yesterday and should arrive by Friday.",
110
+ )
111
+ ```
112
+
113
+ > `custom_scorer_webhook_secret` on a Suite is write-only — it's never returned; only a `custom_scorer_webhook_secret_set` boolean comes back on reads.
114
+
115
+ ## Error handling
116
+
117
+ Every failed request raises `LiyaEngineAPIError`, carrying the API's `code`, `message`, and HTTP `status`:
118
+
119
+ ```python
120
+ from liyaengine import LiyaEngineAPIError
121
+
122
+ try:
123
+ client.collections.create(slug="contracts", label="Contracts", domain_keys=["legal-ops"])
124
+ except LiyaEngineAPIError as err:
125
+ if err.code == "SLUG_CONFLICT":
126
+ # handle the conflict
127
+ pass
128
+ raise
129
+ ```
130
+
131
+ Network failures and timeouts raise `LiyaEngineNetworkError` instead. Requests are retried automatically on `429`/`5xx` responses and transient network errors (2 retries by default).
132
+
133
+ ## Configuration
134
+
135
+ ```python
136
+ LiyaEngine(
137
+ api_key="liya_...",
138
+ base_url="https://api.liyaengine.ai", # override for local/staging
139
+ timeout_s=30.0,
140
+ max_retries=2,
141
+ )
142
+ ```
143
+
144
+ ## Roadmap
145
+
146
+ - [x] Collections
147
+ - [x] Agents (full CRUD, deploy, run, run/session history)
148
+ - [x] Workflows (full CRUD, toggle, deploy, webhook secret rotate, run, run history)
149
+ - [x] Evaluations (Datasets/Cases/Suites/Runs/Reviews CRUD, suite execution, cancel/resume, statistical + pairwise compare, standalone scoring)
150
+ - [ ] Domains (custom domain + intent CRUD)
151
+ - [ ] Run / Run (streaming)
152
+ - [ ] Guardrail Policies
153
+ - [ ] Async client
154
+
155
+ Full docs: https://liyaengine.ai/docs/sdks/python
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ python3 -m venv .venv && source .venv/bin/activate
161
+ pip install -e ".[dev]"
162
+ mypy src
163
+ pytest
164
+ ```
165
+
166
+ ## License
167
+
168
+ MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "liyaengine"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Official Python client for the Liya Engine public API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,6 +2,8 @@ from .client import LiyaEngine
2
2
  from .errors import LiyaEngineAPIError, LiyaEngineNetworkError
3
3
  from .resources.agents import Agent
4
4
  from .resources.collections import Collection
5
+ from .resources.workflows import Workflow
6
+ from .resources.evaluations import EvalDataset, EvalCase, EvalSuite, EvalRun, EvalReview
5
7
 
6
8
  __all__ = [
7
9
  "LiyaEngine",
@@ -9,6 +11,12 @@ __all__ = [
9
11
  "LiyaEngineNetworkError",
10
12
  "Collection",
11
13
  "Agent",
14
+ "Workflow",
15
+ "EvalDataset",
16
+ "EvalCase",
17
+ "EvalSuite",
18
+ "EvalRun",
19
+ "EvalReview",
12
20
  ]
13
21
 
14
- __version__ = "0.2.0"
22
+ __version__ = "0.4.0"
@@ -7,6 +7,8 @@ import httpx
7
7
  from ._http import HttpClient
8
8
  from .resources.agents import AgentsResource
9
9
  from .resources.collections import CollectionsResource
10
+ from .resources.workflows import WorkflowsResource
11
+ from .resources.evaluations import EvaluationsResource
10
12
 
11
13
  _DEFAULT_BASE_URL = "https://api.liyaengine.ai"
12
14
 
@@ -41,6 +43,8 @@ class LiyaEngine:
41
43
  )
42
44
  self.collections = CollectionsResource(self._http)
43
45
  self.agents = AgentsResource(self._http)
46
+ self.workflows = WorkflowsResource(self._http)
47
+ self.evaluations = EvaluationsResource(self._http)
44
48
 
45
49
  def close(self) -> None:
46
50
  self._http.close()