agentics-you 1.2.0__py3-none-any.whl
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.
- agentics_you/__init__.py +82 -0
- agentics_you/cli.py +305 -0
- agentics_you/client.py +201 -0
- agentics_you/gateway.py +358 -0
- agentics_you/instrument.py +202 -0
- agentics_you/litellm_callback.py +118 -0
- agentics_you/memory.py +335 -0
- agentics_you/network.py +107 -0
- agentics_you/openai_agents.py +130 -0
- agentics_you/receipts.py +181 -0
- agentics_you/tasks.py +179 -0
- agentics_you/version.py +1 -0
- agentics_you-1.2.0.dist-info/METADATA +235 -0
- agentics_you-1.2.0.dist-info/RECORD +17 -0
- agentics_you-1.2.0.dist-info/WHEEL +5 -0
- agentics_you-1.2.0.dist-info/entry_points.txt +2 -0
- agentics_you-1.2.0.dist-info/top_level.txt +1 -0
agentics_you/__init__.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""agentics-you — the Agentics Python SDK: shared memory for AI agents, receipts and the gateway.
|
|
2
|
+
|
|
3
|
+
Memory quickstart:
|
|
4
|
+
|
|
5
|
+
from agentics_you import AgenticsClient
|
|
6
|
+
|
|
7
|
+
client = AgenticsClient() # reads AGENTICS_API_KEY (ak_...), optional AGENTICS_AGENT
|
|
8
|
+
client.memories.add("Dana prefers aisle seats.", wait=True)
|
|
9
|
+
hits = client.memories.search("Which seat does Dana want?", k=5)["hits"]
|
|
10
|
+
|
|
11
|
+
Receipts (decorator style):
|
|
12
|
+
|
|
13
|
+
from agentics_you import Network
|
|
14
|
+
|
|
15
|
+
net = Network(api_key="ak_...")
|
|
16
|
+
|
|
17
|
+
@net.task(scope=["read:tickets", "write:reply"])
|
|
18
|
+
def handle_ticket(ticket_id: str) -> str:
|
|
19
|
+
return reply
|
|
20
|
+
|
|
21
|
+
Extras: ``agentics-you[openai]`` (agentics_you.openai_agents trace processor) and ``agentics-you[litellm]``
|
|
22
|
+
(agentics_you.litellm_callback).
|
|
23
|
+
"""
|
|
24
|
+
from .client import Agentics, Task, TaskContext
|
|
25
|
+
from .network import Network
|
|
26
|
+
from .version import __version__
|
|
27
|
+
|
|
28
|
+
# Memory client (namespaces, memories, ingest) with typed errors and retries.
|
|
29
|
+
from .memory import (
|
|
30
|
+
AgenticsClient,
|
|
31
|
+
AsyncAgenticsClient,
|
|
32
|
+
AgenticsError,
|
|
33
|
+
ValidationError,
|
|
34
|
+
AuthenticationError,
|
|
35
|
+
PermissionDeniedError,
|
|
36
|
+
NotFoundError,
|
|
37
|
+
ConflictError,
|
|
38
|
+
RateLimitError,
|
|
39
|
+
ServerError,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Task receipts posted to /v1/receipts (formerly packages/agentics-python).
|
|
43
|
+
from .receipts import client, Client
|
|
44
|
+
|
|
45
|
+
# Gateway client (drop-in OpenAI-compatible base_url swap). Stdlib-only, so this
|
|
46
|
+
# import is always safe even without the `openai` package installed.
|
|
47
|
+
from .gateway import (
|
|
48
|
+
AgenticsGateway,
|
|
49
|
+
GatewayError,
|
|
50
|
+
createHeaders,
|
|
51
|
+
list_models,
|
|
52
|
+
DEFAULT_GATEWAY_BASE_URL,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
# Memory
|
|
57
|
+
"AgenticsClient", "AsyncAgenticsClient", "AgenticsError", "ValidationError", "AuthenticationError",
|
|
58
|
+
"PermissionDeniedError", "NotFoundError", "ConflictError", "RateLimitError", "ServerError",
|
|
59
|
+
# Receipts
|
|
60
|
+
"client", "Client",
|
|
61
|
+
"Agentics", "Task", "TaskContext",
|
|
62
|
+
"Network",
|
|
63
|
+
"AgenticsTasks", "AgenticsTasksError",
|
|
64
|
+
# Gateway (drop-in OpenAI-compatible client + trace/metadata propagation)
|
|
65
|
+
"AgenticsGateway", "GatewayError", "createHeaders", "list_models",
|
|
66
|
+
"DEFAULT_GATEWAY_BASE_URL",
|
|
67
|
+
"__version__",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def __getattr__(name):
|
|
72
|
+
"""Lazily expose the framework callback handlers so importing `agentics`
|
|
73
|
+
never requires LangChain/LlamaIndex/CrewAI to be installed."""
|
|
74
|
+
if name in ("AgenticsTasks", "AgenticsTasksError"): # needs httpx, which is not a dependency
|
|
75
|
+
from . import tasks as _tasks
|
|
76
|
+
|
|
77
|
+
return getattr(_tasks, name)
|
|
78
|
+
if name in ("instrument", "LangchainCallbackHandler", "LlamaIndexCallbackHandler"):
|
|
79
|
+
from . import instrument as _instr
|
|
80
|
+
|
|
81
|
+
return getattr(_instr, name)
|
|
82
|
+
raise AttributeError(f"module 'agentics_you' has no attribute {name!r}")
|
agentics_you/cli.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Agentics CLI.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
agentics login --api-key ak_...
|
|
5
|
+
agentics deploy [--manifest agentics.json]
|
|
6
|
+
agentics receipt send --agent support-bot --model anthropic/claude-opus-4-6 --outcome success --input "..." --output "..."
|
|
7
|
+
agentics agents list
|
|
8
|
+
agentics flags
|
|
9
|
+
agentics whoami
|
|
10
|
+
|
|
11
|
+
# Gateway devex verbs (sdk spec Appendix A)
|
|
12
|
+
agentics gateway test [--url https://agentics.you/api/v1] [--user a@b.com]
|
|
13
|
+
agentics config publish [config.json] [--name default]
|
|
14
|
+
agentics config validate [config.json]
|
|
15
|
+
agentics prompt push <prompt.json>
|
|
16
|
+
agentics log tail [--limit 50]
|
|
17
|
+
agentics proof verify <hash>
|
|
18
|
+
agentics proof bundle <hash>
|
|
19
|
+
agentics models [--provider anthropic]
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
import argparse, json, os, sys
|
|
23
|
+
from urllib import request as _request
|
|
24
|
+
from urllib.error import HTTPError
|
|
25
|
+
|
|
26
|
+
from .client import Agentics, Task
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _api(method: str, path: str, body=None, key: str | None = None, query: dict | None = None):
|
|
30
|
+
base = os.environ.get("AGENTICS_BASE_URL", "https://agentics.you")
|
|
31
|
+
if query:
|
|
32
|
+
from urllib.parse import urlencode
|
|
33
|
+
clean = {k: v for k, v in query.items() if v is not None}
|
|
34
|
+
if clean:
|
|
35
|
+
path = f"{path}?{urlencode(clean)}"
|
|
36
|
+
req = _request.Request(
|
|
37
|
+
f"{base}{path}",
|
|
38
|
+
data=json.dumps(body).encode("utf-8") if body is not None else None,
|
|
39
|
+
headers={
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"Authorization": f"Bearer {key or os.environ.get('AGENTICS_API_KEY','')}",
|
|
42
|
+
},
|
|
43
|
+
method=method,
|
|
44
|
+
)
|
|
45
|
+
try:
|
|
46
|
+
with _request.urlopen(req, timeout=15) as r:
|
|
47
|
+
return json.loads(r.read().decode("utf-8") or "{}")
|
|
48
|
+
except HTTPError as e:
|
|
49
|
+
try:
|
|
50
|
+
payload = json.loads(e.read().decode("utf-8") or "{}")
|
|
51
|
+
except Exception:
|
|
52
|
+
payload = {}
|
|
53
|
+
# 404 from a flag-gated surface == "feature not enabled for this tenant".
|
|
54
|
+
if e.code == 404:
|
|
55
|
+
raise SystemExit(
|
|
56
|
+
"HTTP 404: feature not enabled for this tenant "
|
|
57
|
+
f"(path may be flag-gated). {payload.get('error') or ''}".strip()
|
|
58
|
+
)
|
|
59
|
+
raise SystemExit(f"HTTP {e.code}: {payload.get('error') or payload}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _read_json_file(path: str) -> dict:
|
|
63
|
+
if not path or not os.path.exists(path):
|
|
64
|
+
raise SystemExit(f"file not found: {path}")
|
|
65
|
+
with open(path) as f:
|
|
66
|
+
return json.loads(f.read() or "{}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _load_manifest(path: str | None) -> dict:
|
|
70
|
+
"""Load agent manifest from JSON (or YAML if PyYAML installed)."""
|
|
71
|
+
candidates = [path] if path else ["agentics.json", "agentics.yaml", "agentics.yml"]
|
|
72
|
+
for cand in candidates:
|
|
73
|
+
if not cand or not os.path.exists(cand):
|
|
74
|
+
continue
|
|
75
|
+
with open(cand) as f:
|
|
76
|
+
raw = f.read()
|
|
77
|
+
if cand.endswith((".yaml", ".yml")):
|
|
78
|
+
try:
|
|
79
|
+
import yaml # type: ignore
|
|
80
|
+
except ImportError:
|
|
81
|
+
raise SystemExit(
|
|
82
|
+
f"Manifest {cand} is YAML but PyYAML is not installed. "
|
|
83
|
+
"Install with: pip install agentics[yaml]"
|
|
84
|
+
)
|
|
85
|
+
return yaml.safe_load(raw) or {}
|
|
86
|
+
return json.loads(raw or "{}")
|
|
87
|
+
raise SystemExit(
|
|
88
|
+
"No agent manifest found. Create agentics.json (or pass --manifest) with at least: "
|
|
89
|
+
"foundation_model, scope_array, display_name."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _do_deploy(args) -> None:
|
|
94
|
+
manifest = _load_manifest(args.manifest)
|
|
95
|
+
# CLI flags override manifest values
|
|
96
|
+
for flag, key in (
|
|
97
|
+
("handle", "handle"),
|
|
98
|
+
("model", "foundation_model"),
|
|
99
|
+
("name", "display_name"),
|
|
100
|
+
("description", "description"),
|
|
101
|
+
("avatar_url", "avatar_url"),
|
|
102
|
+
("owner_email", "owner_email"),
|
|
103
|
+
):
|
|
104
|
+
v = getattr(args, flag, None)
|
|
105
|
+
if v:
|
|
106
|
+
manifest[key] = v
|
|
107
|
+
if args.scope:
|
|
108
|
+
manifest["scope_array"] = [s.strip() for s in args.scope.split(",") if s.strip()]
|
|
109
|
+
|
|
110
|
+
if not manifest.get("foundation_model"):
|
|
111
|
+
raise SystemExit("Manifest missing required field: foundation_model")
|
|
112
|
+
if not manifest.get("scope_array"):
|
|
113
|
+
raise SystemExit("Manifest missing required field: scope_array (e.g. ['read:tickets'])")
|
|
114
|
+
|
|
115
|
+
print(f"Deploying agent to {os.environ.get('AGENTICS_BASE_URL', 'https://agentics.you')} ...")
|
|
116
|
+
resp = _api("POST", "/api/v1/agents/register", body=manifest)
|
|
117
|
+
print(json.dumps(resp, indent=2))
|
|
118
|
+
if resp.get("api_key"):
|
|
119
|
+
print(
|
|
120
|
+
"\n✓ Deployed. Save the api_key above as AGENTICS_API_KEY; the agent will start"
|
|
121
|
+
" appearing on /network/registry once its first receipt is anchored.",
|
|
122
|
+
file=sys.stderr,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main(argv=None):
|
|
127
|
+
p = argparse.ArgumentParser(prog="agentics", description="Agentics CLI")
|
|
128
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
129
|
+
|
|
130
|
+
sp = sub.add_parser("login"); sp.add_argument("--api-key", required=True)
|
|
131
|
+
sub.add_parser("whoami")
|
|
132
|
+
sub.add_parser("flags")
|
|
133
|
+
sub.add_parser("agents")
|
|
134
|
+
|
|
135
|
+
dp = sub.add_parser("deploy", help="Register or update an agent from a manifest")
|
|
136
|
+
dp.add_argument("--manifest", help="Path to agentics.json / agentics.yaml (default: ./agentics.json)")
|
|
137
|
+
dp.add_argument("--handle", help="Override handle from manifest")
|
|
138
|
+
dp.add_argument("--model", help="Override foundation_model")
|
|
139
|
+
dp.add_argument("--name", help="Override display_name")
|
|
140
|
+
dp.add_argument("--description", help="Override description")
|
|
141
|
+
dp.add_argument("--avatar-url", dest="avatar_url", help="Override avatar URL")
|
|
142
|
+
dp.add_argument("--owner-email", dest="owner_email", help="Owner contact email")
|
|
143
|
+
dp.add_argument("--scope", help="Comma-separated scope override, e.g. read:tickets,write:reply")
|
|
144
|
+
|
|
145
|
+
rs = sub.add_parser("receipt"); rss = rs.add_subparsers(dest="rcmd", required=True)
|
|
146
|
+
rsn = rss.add_parser("send")
|
|
147
|
+
rsn.add_argument("--agent", required=True)
|
|
148
|
+
rsn.add_argument("--model", required=True)
|
|
149
|
+
rsn.add_argument("--version", default="")
|
|
150
|
+
rsn.add_argument("--outcome", default="success", choices=["success", "partial", "failed"])
|
|
151
|
+
rsn.add_argument("--input", default="")
|
|
152
|
+
rsn.add_argument("--output", default="")
|
|
153
|
+
rsn.add_argument("--scope", default="", help="comma-separated declared scope")
|
|
154
|
+
rsn.add_argument("--scope-used", default="", help="comma-separated used scope")
|
|
155
|
+
|
|
156
|
+
# ── gateway test ──────────────────────────────────────────────────────────
|
|
157
|
+
gt = sub.add_parser("gateway", help="Gateway client helpers")
|
|
158
|
+
gts = gt.add_subparsers(dest="gcmd", required=True)
|
|
159
|
+
gtt = gts.add_parser("test", help="Send a 1-token ping through the gateway")
|
|
160
|
+
gtt.add_argument("--url", help="Gateway base_url (…/v1). Defaults to env AGENTICS_GATEWAY_URL")
|
|
161
|
+
gtt.add_argument("--user", default=None, help="x-agentics-user attribution")
|
|
162
|
+
gtt.add_argument("--model", default="@anthropic/claude-opus-4-8")
|
|
163
|
+
|
|
164
|
+
# ── config publish / validate ─────────────────────────────────────────────
|
|
165
|
+
cp = sub.add_parser("config", help="Routing config (Routing Fabric)")
|
|
166
|
+
cps = cp.add_subparsers(dest="ccmd", required=True)
|
|
167
|
+
cpp = cps.add_parser("publish", help="Create + publish a GatewayConfig")
|
|
168
|
+
cpp.add_argument("file", nargs="?", default="gateway-config.json")
|
|
169
|
+
cpp.add_argument("--name", default=None, help="Config name (default: from file or 'default')")
|
|
170
|
+
cpv = cps.add_parser("validate", help="Dry-run validate a GatewayConfig (no publish)")
|
|
171
|
+
cpv.add_argument("file", nargs="?", default="gateway-config.json")
|
|
172
|
+
|
|
173
|
+
# ── prompt push ───────────────────────────────────────────────────────────
|
|
174
|
+
pp = sub.add_parser("prompt", help="Prompt Registry")
|
|
175
|
+
pps = pp.add_subparsers(dest="pcmd", required=True)
|
|
176
|
+
ppp = pps.add_parser("push", help="Publish a prompt template")
|
|
177
|
+
ppp.add_argument("file")
|
|
178
|
+
|
|
179
|
+
# ── log tail ──────────────────────────────────────────────────────────────
|
|
180
|
+
lt = sub.add_parser("log", help="Gateway receipt logs")
|
|
181
|
+
lts = lt.add_subparsers(dest="lcmd", required=True)
|
|
182
|
+
ltt = lts.add_parser("tail", help="Tail recent gateway receipts (with trace_id)")
|
|
183
|
+
ltt.add_argument("--limit", type=int, default=50)
|
|
184
|
+
ltt.add_argument("--since", default=None)
|
|
185
|
+
|
|
186
|
+
# ── proof verify / bundle ─────────────────────────────────────────────────
|
|
187
|
+
pr = sub.add_parser("proof", help="Verify a receipt end-to-end (Proof Mesh)")
|
|
188
|
+
prs = pr.add_subparsers(dest="prcmd", required=True)
|
|
189
|
+
prv = prs.add_parser("verify", help="4-step verify: leaf, chain, merkle, anchor")
|
|
190
|
+
prv.add_argument("hash")
|
|
191
|
+
prb = prs.add_parser("bundle", help="Offline-verifiable evidence bundle")
|
|
192
|
+
prb.add_argument("hash")
|
|
193
|
+
|
|
194
|
+
# ── models (Model Garden catalog, D8) ─────────────────────────────────────
|
|
195
|
+
md = sub.add_parser("models", help="List the Model Garden catalog")
|
|
196
|
+
md.add_argument("--provider", default=None)
|
|
197
|
+
md.add_argument("--modality", default=None)
|
|
198
|
+
md.add_argument("-q", "--query", dest="q", default=None)
|
|
199
|
+
md.add_argument("--include-deprecated", action="store_true")
|
|
200
|
+
|
|
201
|
+
args = p.parse_args(argv)
|
|
202
|
+
|
|
203
|
+
if args.cmd == "login":
|
|
204
|
+
cfg = os.path.expanduser("~/.agentics")
|
|
205
|
+
os.makedirs(cfg, exist_ok=True)
|
|
206
|
+
with open(os.path.join(cfg, "credentials"), "w") as f:
|
|
207
|
+
f.write(args.api_key)
|
|
208
|
+
print("Saved credentials to ~/.agentics/credentials")
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# Load creds from file if env is empty
|
|
212
|
+
if not os.environ.get("AGENTICS_API_KEY"):
|
|
213
|
+
cfg = os.path.expanduser("~/.agentics/credentials")
|
|
214
|
+
if os.path.exists(cfg):
|
|
215
|
+
with open(cfg) as f:
|
|
216
|
+
os.environ["AGENTICS_API_KEY"] = f.read().strip()
|
|
217
|
+
|
|
218
|
+
if args.cmd == "whoami":
|
|
219
|
+
print(json.dumps(_api("GET", "/api/me"), indent=2))
|
|
220
|
+
elif args.cmd == "flags":
|
|
221
|
+
print(json.dumps(_api("GET", "/api/admin/flags"), indent=2))
|
|
222
|
+
elif args.cmd == "agents":
|
|
223
|
+
print(json.dumps(_api("GET", "/api/agents-e"), indent=2))
|
|
224
|
+
elif args.cmd == "deploy":
|
|
225
|
+
_do_deploy(args)
|
|
226
|
+
elif args.cmd == "receipt" and args.rcmd == "send":
|
|
227
|
+
client = Agentics(async_buffer=False)
|
|
228
|
+
t = Task(
|
|
229
|
+
agent_id=args.agent,
|
|
230
|
+
foundation_model=args.model,
|
|
231
|
+
model_version=args.version,
|
|
232
|
+
scope=[s for s in args.scope.split(",") if s],
|
|
233
|
+
)
|
|
234
|
+
t.set_outputs(args.output)
|
|
235
|
+
t.set_outcome(args.outcome)
|
|
236
|
+
for s in [s for s in args.scope_used.split(",") if s]:
|
|
237
|
+
t.use_scope(s)
|
|
238
|
+
client.send(t)
|
|
239
|
+
client.flush()
|
|
240
|
+
print(json.dumps({"sent": True, "task_id": t.task_id}, indent=2))
|
|
241
|
+
|
|
242
|
+
# ── gateway test ──────────────────────────────────────────────────────────
|
|
243
|
+
elif args.cmd == "gateway" and args.gcmd == "test":
|
|
244
|
+
from .gateway import AgenticsGateway, GatewayError
|
|
245
|
+
gw = AgenticsGateway(base_url=args.url).with_options(user=args.user)
|
|
246
|
+
try:
|
|
247
|
+
resp = gw.chat.completions.create(
|
|
248
|
+
model=args.model,
|
|
249
|
+
messages=[{"role": "user", "content": "ping"}],
|
|
250
|
+
max_tokens=1,
|
|
251
|
+
)
|
|
252
|
+
print(json.dumps({"ok": True, "gateway": gw.base_url, "response": resp}, indent=2))
|
|
253
|
+
except GatewayError as e:
|
|
254
|
+
raise SystemExit(f"gateway test failed: HTTP {e.status}: {e.body[:300]}")
|
|
255
|
+
|
|
256
|
+
# ── config publish / validate (Routing Fabric /api/routing/*) ─────────────
|
|
257
|
+
elif args.cmd == "config" and args.ccmd == "publish":
|
|
258
|
+
body = _read_json_file(args.file)
|
|
259
|
+
name = args.name or body.get("name") or "default"
|
|
260
|
+
created = _api("POST", "/api/routing/configs", body={"name": name, "body": body.get("body", body)})
|
|
261
|
+
cfg_id = created.get("id")
|
|
262
|
+
if not cfg_id:
|
|
263
|
+
raise SystemExit(f"create failed: {json.dumps(created)}")
|
|
264
|
+
published = _api("POST", f"/api/routing/configs/{cfg_id}/publish")
|
|
265
|
+
print(json.dumps({"published": True, "id": cfg_id, "name": name, "result": published}, indent=2))
|
|
266
|
+
elif args.cmd == "config" and args.ccmd == "validate":
|
|
267
|
+
body = _read_json_file(args.file)
|
|
268
|
+
# Routing Fabric's simulate endpoint dry-runs a config without persisting.
|
|
269
|
+
out = _api("POST", "/api/routing/simulate", body={"config": body.get("body", body)})
|
|
270
|
+
print(json.dumps(out, indent=2))
|
|
271
|
+
|
|
272
|
+
# ── prompt push (Prompt Registry /api/prompts/*) ──────────────────────────
|
|
273
|
+
elif args.cmd == "prompt" and args.pcmd == "push":
|
|
274
|
+
body = _read_json_file(args.file)
|
|
275
|
+
print(json.dumps(_api("POST", "/api/prompts", body=body), indent=2))
|
|
276
|
+
|
|
277
|
+
# ── log tail (gateway receipt tail w/ trace_id — sdk devex /api/sdk/logs) ──
|
|
278
|
+
elif args.cmd == "log" and args.lcmd == "tail":
|
|
279
|
+
out = _api("GET", "/api/sdk/logs", query={"limit": args.limit, "since": args.since})
|
|
280
|
+
for row in out.get("logs", []):
|
|
281
|
+
print(json.dumps(row))
|
|
282
|
+
|
|
283
|
+
# ── proof verify / bundle (Proof Mesh /api/proof/*) ───────────────────────
|
|
284
|
+
elif args.cmd == "proof" and args.prcmd == "verify":
|
|
285
|
+
out = _api("GET", "/api/proof/verify", query={"hash": args.hash})
|
|
286
|
+
print(json.dumps(out, indent=2))
|
|
287
|
+
if not out.get("verified"):
|
|
288
|
+
raise SystemExit(2)
|
|
289
|
+
elif args.cmd == "proof" and args.prcmd == "bundle":
|
|
290
|
+
print(json.dumps(_api("GET", "/api/proof/bundle", query={"hash": args.hash}), indent=2))
|
|
291
|
+
|
|
292
|
+
# ── models (Model Garden catalog, D8) ─────────────────────────────────────
|
|
293
|
+
elif args.cmd == "models":
|
|
294
|
+
out = _api("GET", "/api/model-garden/catalog", query={
|
|
295
|
+
"provider": args.provider, "modality": args.modality, "q": args.q,
|
|
296
|
+
"includeDeprecated": "true" if args.include_deprecated else None,
|
|
297
|
+
})
|
|
298
|
+
rows = out if isinstance(out, list) else (
|
|
299
|
+
out.get("models") or out.get("catalog") or out.get("data") or out.get("rows") or []
|
|
300
|
+
)
|
|
301
|
+
print(json.dumps(rows, indent=2))
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if __name__ == "__main__":
|
|
305
|
+
main()
|
agentics_you/client.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Agentics SDK client + Task context manager."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
import hashlib
|
|
9
|
+
import threading
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
13
|
+
from urllib import request as _request
|
|
14
|
+
from urllib.error import URLError, HTTPError
|
|
15
|
+
|
|
16
|
+
from .version import __version__
|
|
17
|
+
|
|
18
|
+
DEFAULT_BASE_URL = "https://agentics.you"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _now_iso() -> str:
|
|
22
|
+
return datetime.now(timezone.utc).isoformat()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _sha256(s: str) -> str:
|
|
26
|
+
return hashlib.sha256(s.encode("utf-8") if isinstance(s, str) else s).hexdigest()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Task:
|
|
31
|
+
agent_id: str
|
|
32
|
+
foundation_model: str
|
|
33
|
+
model_version: str
|
|
34
|
+
system_prompt: str = ""
|
|
35
|
+
tool_manifest: Any = None
|
|
36
|
+
scope: List[str] = field(default_factory=list)
|
|
37
|
+
task_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
38
|
+
started_at: str = field(default_factory=_now_iso)
|
|
39
|
+
finished_at: Optional[str] = None
|
|
40
|
+
outcome: str = "success"
|
|
41
|
+
cost_usd: float = 0.0
|
|
42
|
+
tokens_in: int = 0
|
|
43
|
+
tokens_out: int = 0
|
|
44
|
+
scope_used: List[str] = field(default_factory=list)
|
|
45
|
+
inputs: str = ""
|
|
46
|
+
outputs: str = ""
|
|
47
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
def set_inputs(self, text: str) -> None:
|
|
50
|
+
self.inputs = text
|
|
51
|
+
|
|
52
|
+
def set_outputs(self, text: str) -> None:
|
|
53
|
+
self.outputs = text
|
|
54
|
+
|
|
55
|
+
def set_outcome(self, outcome: str) -> None:
|
|
56
|
+
if outcome not in ("success", "partial", "failed"):
|
|
57
|
+
raise ValueError("outcome must be one of: success | partial | failed")
|
|
58
|
+
self.outcome = outcome
|
|
59
|
+
|
|
60
|
+
def add_usage(self, *, tokens_in: int = 0, tokens_out: int = 0, cost_usd: float = 0.0) -> None:
|
|
61
|
+
self.tokens_in += tokens_in
|
|
62
|
+
self.tokens_out += tokens_out
|
|
63
|
+
self.cost_usd += cost_usd
|
|
64
|
+
|
|
65
|
+
def use_scope(self, scope: str) -> None:
|
|
66
|
+
if scope not in self.scope_used:
|
|
67
|
+
self.scope_used.append(scope)
|
|
68
|
+
|
|
69
|
+
def to_payload(self) -> Dict[str, Any]:
|
|
70
|
+
return {
|
|
71
|
+
"event_id": self.task_id,
|
|
72
|
+
"agent": {
|
|
73
|
+
"external_id": self.agent_id,
|
|
74
|
+
"foundation_model": self.foundation_model,
|
|
75
|
+
"model_version": self.model_version,
|
|
76
|
+
"system_prompt": self.system_prompt,
|
|
77
|
+
"tool_manifest": self.tool_manifest,
|
|
78
|
+
"scope": self.scope,
|
|
79
|
+
},
|
|
80
|
+
"task": {
|
|
81
|
+
"task_id": self.task_id,
|
|
82
|
+
"started_at": self.started_at,
|
|
83
|
+
"finished_at": self.finished_at or _now_iso(),
|
|
84
|
+
"outcome": self.outcome,
|
|
85
|
+
"cost_usd": self.cost_usd,
|
|
86
|
+
"tokens_in": self.tokens_in,
|
|
87
|
+
"tokens_out": self.tokens_out,
|
|
88
|
+
"scope_used": self.scope_used,
|
|
89
|
+
"inputs": self.inputs,
|
|
90
|
+
"outputs": self.outputs,
|
|
91
|
+
"metadata": self.metadata,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TaskContext:
|
|
97
|
+
"""Context manager that auto-emits a receipt on exit."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, client: "Agentics", task: Task) -> None:
|
|
100
|
+
self.client = client
|
|
101
|
+
self.task = task
|
|
102
|
+
|
|
103
|
+
def __enter__(self) -> Task:
|
|
104
|
+
return self.task
|
|
105
|
+
|
|
106
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
107
|
+
if exc:
|
|
108
|
+
self.task.set_outcome("failed")
|
|
109
|
+
self.task.metadata["error"] = repr(exc)
|
|
110
|
+
self.task.finished_at = _now_iso()
|
|
111
|
+
try:
|
|
112
|
+
self.client.send(self.task)
|
|
113
|
+
except Exception: # never break the caller because telemetry failed
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Agentics:
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
api_key: Optional[str] = None,
|
|
121
|
+
base_url: Optional[str] = None,
|
|
122
|
+
timeout: float = 5.0,
|
|
123
|
+
async_buffer: bool = True,
|
|
124
|
+
) -> None:
|
|
125
|
+
self.api_key = api_key or os.environ.get("AGENTICS_API_KEY", "")
|
|
126
|
+
if not self.api_key:
|
|
127
|
+
raise RuntimeError("Agentics SDK: AGENTICS_API_KEY required")
|
|
128
|
+
self.base_url = base_url or os.environ.get("AGENTICS_BASE_URL", DEFAULT_BASE_URL)
|
|
129
|
+
self.timeout = timeout
|
|
130
|
+
self._buffer: List[Dict[str, Any]] = []
|
|
131
|
+
self._buffer_lock = threading.Lock()
|
|
132
|
+
self._async = async_buffer
|
|
133
|
+
if async_buffer:
|
|
134
|
+
self._flusher = threading.Thread(target=self._flush_loop, daemon=True)
|
|
135
|
+
self._flusher.start()
|
|
136
|
+
|
|
137
|
+
def track(
|
|
138
|
+
self,
|
|
139
|
+
*,
|
|
140
|
+
agent_id: str,
|
|
141
|
+
foundation_model: str,
|
|
142
|
+
model_version: str = "",
|
|
143
|
+
system_prompt: str = "",
|
|
144
|
+
tool_manifest: Any = None,
|
|
145
|
+
scope: Optional[List[str]] = None,
|
|
146
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
147
|
+
) -> TaskContext:
|
|
148
|
+
return TaskContext(self, Task(
|
|
149
|
+
agent_id=agent_id, foundation_model=foundation_model,
|
|
150
|
+
model_version=model_version, system_prompt=system_prompt,
|
|
151
|
+
tool_manifest=tool_manifest, scope=list(scope or []),
|
|
152
|
+
metadata=dict(metadata or {}),
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
def send(self, task: Task) -> None:
|
|
156
|
+
payload = task.to_payload()
|
|
157
|
+
if self._async:
|
|
158
|
+
with self._buffer_lock:
|
|
159
|
+
self._buffer.append(payload)
|
|
160
|
+
else:
|
|
161
|
+
self._post(payload)
|
|
162
|
+
|
|
163
|
+
def send_batch(self, tasks: Iterable[Task]) -> Dict[str, Any]:
|
|
164
|
+
return self._post({"batch": [t.to_payload() for t in tasks]})
|
|
165
|
+
|
|
166
|
+
def flush(self) -> None:
|
|
167
|
+
with self._buffer_lock:
|
|
168
|
+
items = self._buffer[:]
|
|
169
|
+
self._buffer.clear()
|
|
170
|
+
if not items:
|
|
171
|
+
return
|
|
172
|
+
try:
|
|
173
|
+
self._post({"batch": items})
|
|
174
|
+
except Exception:
|
|
175
|
+
with self._buffer_lock:
|
|
176
|
+
self._buffer.extend(items[:1000])
|
|
177
|
+
|
|
178
|
+
def _flush_loop(self) -> None:
|
|
179
|
+
while True:
|
|
180
|
+
time.sleep(2.0)
|
|
181
|
+
self.flush()
|
|
182
|
+
|
|
183
|
+
def _post(self, body: Dict[str, Any]) -> Dict[str, Any]:
|
|
184
|
+
data = json.dumps(body).encode("utf-8")
|
|
185
|
+
req = _request.Request(
|
|
186
|
+
f"{self.base_url}/api/sdk/ingest",
|
|
187
|
+
data=data,
|
|
188
|
+
headers={
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
191
|
+
"User-Agent": f"agentics-python/{__version__}",
|
|
192
|
+
},
|
|
193
|
+
method="POST",
|
|
194
|
+
)
|
|
195
|
+
try:
|
|
196
|
+
with _request.urlopen(req, timeout=self.timeout) as resp:
|
|
197
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
198
|
+
except HTTPError as e:
|
|
199
|
+
return {"error": "http", "status": e.code, "body": e.read().decode("utf-8", "ignore")}
|
|
200
|
+
except URLError as e:
|
|
201
|
+
return {"error": "network", "reason": str(e)}
|