anva 0.1.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.
- anva-0.1.0/.gitignore +6 -0
- anva-0.1.0/PKG-INFO +48 -0
- anva-0.1.0/README.md +28 -0
- anva-0.1.0/pyproject.toml +31 -0
- anva-0.1.0/src/anva/__init__.py +17 -0
- anva-0.1.0/src/anva/client.py +164 -0
anva-0.1.0/.gitignore
ADDED
anva-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: anva
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Anva — live AI avatars for your product.
|
|
5
|
+
Project-URL: Homepage, https://anva.ai
|
|
6
|
+
Project-URL: Documentation, https://anva.ai/docs
|
|
7
|
+
Project-URL: Source, https://github.com/C-L-2013/anva-sdk
|
|
8
|
+
Author-email: Anva <anva.ai.2026@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: ai,anva,avatar,voice,webrtc
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Provides-Extra: ws
|
|
18
|
+
Requires-Dist: websockets>=12; extra == 'ws'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# anva — Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python SDK for [Anva](https://anva.ai) — live AI avatars.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install anva # REST client, zero dependencies
|
|
27
|
+
pip install "anva[ws]" # + live event stream (WebSocket)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from anva import Anva
|
|
32
|
+
|
|
33
|
+
client = Anva("anva_key_...")
|
|
34
|
+
|
|
35
|
+
session = client.create_session(character_id="char_...")
|
|
36
|
+
print(session["embed_url"])
|
|
37
|
+
|
|
38
|
+
client.send_message(session["session_id"], "Welcome!")
|
|
39
|
+
client.interrupt(session["session_id"])
|
|
40
|
+
|
|
41
|
+
for event in client.events(session["session_id"]):
|
|
42
|
+
print(event)
|
|
43
|
+
|
|
44
|
+
chars = client.list_characters()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Errors raise `anva.AnvaError` with `.status`, `.code` and `.message`.
|
|
48
|
+
MIT © Penguin Robotics
|
anva-0.1.0/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# anva — Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [Anva](https://anva.ai) — live AI avatars.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install anva # REST client, zero dependencies
|
|
7
|
+
pip install "anva[ws]" # + live event stream (WebSocket)
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from anva import Anva
|
|
12
|
+
|
|
13
|
+
client = Anva("anva_key_...")
|
|
14
|
+
|
|
15
|
+
session = client.create_session(character_id="char_...")
|
|
16
|
+
print(session["embed_url"])
|
|
17
|
+
|
|
18
|
+
client.send_message(session["session_id"], "Welcome!")
|
|
19
|
+
client.interrupt(session["session_id"])
|
|
20
|
+
|
|
21
|
+
for event in client.events(session["session_id"]):
|
|
22
|
+
print(event)
|
|
23
|
+
|
|
24
|
+
chars = client.list_characters()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Errors raise `anva.AnvaError` with `.status`, `.code` and `.message`.
|
|
28
|
+
MIT © Penguin Robotics
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "anva"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for Anva — live AI avatars for your product."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Anva", email = "anva.ai.2026@gmail.com" }]
|
|
13
|
+
keywords = ["avatar", "ai", "webrtc", "voice", "anva"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
ws = ["websockets>=12"]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://anva.ai"
|
|
27
|
+
Documentation = "https://anva.ai/docs"
|
|
28
|
+
Source = "https://github.com/C-L-2013/anva-sdk"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
packages = ["src/anva"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Official Python SDK for Anva (https://anva.ai) — live AI avatars.
|
|
2
|
+
|
|
3
|
+
Quickstart:
|
|
4
|
+
|
|
5
|
+
from anva import Anva
|
|
6
|
+
|
|
7
|
+
client = Anva("anva_key_...")
|
|
8
|
+
session = client.create_session(character_id="char_...")
|
|
9
|
+
print(session["embed_url"])
|
|
10
|
+
|
|
11
|
+
for event in client.events(session["session_id"]): # pip install anva[ws]
|
|
12
|
+
print(event)
|
|
13
|
+
"""
|
|
14
|
+
from .client import Anva, AnvaError
|
|
15
|
+
|
|
16
|
+
__all__ = ["Anva", "AnvaError"]
|
|
17
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Thin, dependency-free client for the Anva REST API.
|
|
2
|
+
|
|
3
|
+
Every method mirrors one endpoint and returns the decoded JSON body.
|
|
4
|
+
Errors raise AnvaError carrying the API's machine-readable code.
|
|
5
|
+
The events stream (WebSocket) needs the optional extra: pip install anva[ws].
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
from typing import Any, Dict, Iterator, Optional
|
|
14
|
+
|
|
15
|
+
DEFAULT_BASE_URL = "https://anva.ai"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AnvaError(Exception):
|
|
19
|
+
"""API error with the server's machine-readable code and HTTP status."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, status: int, code: str, message: str):
|
|
22
|
+
super().__init__(f"{code}: {message} (HTTP {status})")
|
|
23
|
+
self.status = status
|
|
24
|
+
self.code = code
|
|
25
|
+
self.message = message
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Anva:
|
|
29
|
+
"""Client for the Anva API.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
api_key: an API key from the dashboard (anva_key_...).
|
|
33
|
+
base_url: override for self-hosted / testing setups.
|
|
34
|
+
timeout: per-request timeout in seconds.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, api_key: str, *, base_url: str = DEFAULT_BASE_URL,
|
|
38
|
+
timeout: float = 30.0):
|
|
39
|
+
if not api_key or not api_key.strip():
|
|
40
|
+
raise ValueError("api_key is required")
|
|
41
|
+
self.api_key = api_key.strip()
|
|
42
|
+
self.base_url = base_url.rstrip("/")
|
|
43
|
+
self.timeout = timeout
|
|
44
|
+
|
|
45
|
+
# -- sessions -----------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
def create_session(self, character_id: str, *, llm_mode: Optional[str] = None,
|
|
48
|
+
webhook_url: Optional[str] = None,
|
|
49
|
+
webhook_secret: Optional[str] = None) -> Dict[str, Any]:
|
|
50
|
+
"""Create a live session. Returns session_id, session_token,
|
|
51
|
+
embed_url (iframe-ready) and events_ws_url."""
|
|
52
|
+
body: Dict[str, Any] = {"character_id": character_id}
|
|
53
|
+
if llm_mode:
|
|
54
|
+
body["llm_mode"] = llm_mode
|
|
55
|
+
if webhook_url:
|
|
56
|
+
body["webhook_url"] = webhook_url
|
|
57
|
+
if webhook_secret:
|
|
58
|
+
body["webhook_secret"] = webhook_secret
|
|
59
|
+
return self._request("POST", "/api/v1/sessions", body)
|
|
60
|
+
|
|
61
|
+
def get_session(self, session_id: str) -> Dict[str, Any]:
|
|
62
|
+
return self._request("GET", f"/api/v1/sessions/{_esc(session_id)}")
|
|
63
|
+
|
|
64
|
+
def end_session(self, session_id: str) -> Dict[str, Any]:
|
|
65
|
+
return self._request("DELETE", f"/api/v1/sessions/{_esc(session_id)}")
|
|
66
|
+
|
|
67
|
+
def send_message(self, session_id: str, text: str) -> Dict[str, Any]:
|
|
68
|
+
"""Have the avatar speak `text` to the user."""
|
|
69
|
+
return self._request(
|
|
70
|
+
"POST", f"/api/v1/sessions/{_esc(session_id)}/messages",
|
|
71
|
+
{"text": text})
|
|
72
|
+
|
|
73
|
+
def interrupt(self, session_id: str) -> Dict[str, Any]:
|
|
74
|
+
"""Stop the avatar mid-sentence."""
|
|
75
|
+
return self._request(
|
|
76
|
+
"POST", f"/api/v1/sessions/{_esc(session_id)}/interrupt", {})
|
|
77
|
+
|
|
78
|
+
def trigger_action(self, session_id: str, name: str) -> Dict[str, Any]:
|
|
79
|
+
return self._request(
|
|
80
|
+
"POST", f"/api/v1/sessions/{_esc(session_id)}/actions",
|
|
81
|
+
{"name": name})
|
|
82
|
+
|
|
83
|
+
def events_url(self, session_id: str) -> str:
|
|
84
|
+
"""The authenticated WebSocket URL for the session's event stream."""
|
|
85
|
+
ws_base = self.base_url.replace("http", "ws", 1)
|
|
86
|
+
return (f"{ws_base}/api/v1/sessions/{_esc(session_id)}/events"
|
|
87
|
+
f"?api_key={urllib.parse.quote(self.api_key)}")
|
|
88
|
+
|
|
89
|
+
def events(self, session_id: str) -> Iterator[Dict[str, Any]]:
|
|
90
|
+
"""Yield event dicts (transcripts, state changes) as they happen.
|
|
91
|
+
|
|
92
|
+
Requires the ws extra: pip install anva[ws]
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
from websockets.sync.client import connect
|
|
96
|
+
except ImportError as e: # pragma: no cover
|
|
97
|
+
raise RuntimeError(
|
|
98
|
+
"the events stream needs the websockets package: "
|
|
99
|
+
"pip install anva[ws]") from e
|
|
100
|
+
with connect(self.events_url(session_id)) as ws:
|
|
101
|
+
for raw in ws:
|
|
102
|
+
try:
|
|
103
|
+
yield json.loads(raw)
|
|
104
|
+
except (TypeError, ValueError):
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
# -- characters ---------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def list_characters(self) -> Dict[str, Any]:
|
|
110
|
+
return self._request("GET", "/api/v1/characters")
|
|
111
|
+
|
|
112
|
+
def create_character(self, name: str, *,
|
|
113
|
+
visual_character_id: Optional[str] = None,
|
|
114
|
+
system_prompt: Optional[str] = None,
|
|
115
|
+
voice_id: Optional[str] = None,
|
|
116
|
+
language_code: Optional[str] = None) -> Dict[str, Any]:
|
|
117
|
+
body: Dict[str, Any] = {"name": name}
|
|
118
|
+
if visual_character_id:
|
|
119
|
+
body["visual_character_id"] = visual_character_id
|
|
120
|
+
if system_prompt:
|
|
121
|
+
body["system_prompt"] = system_prompt
|
|
122
|
+
if voice_id:
|
|
123
|
+
body["voice_id"] = voice_id
|
|
124
|
+
if language_code:
|
|
125
|
+
body["language_code"] = language_code
|
|
126
|
+
return self._request("POST", "/api/v1/characters", body)
|
|
127
|
+
|
|
128
|
+
def get_character(self, character_id: str) -> Dict[str, Any]:
|
|
129
|
+
return self._request("GET", f"/api/v1/characters/{_esc(character_id)}")
|
|
130
|
+
|
|
131
|
+
def delete_character(self, character_id: str) -> Dict[str, Any]:
|
|
132
|
+
return self._request("DELETE", f"/api/v1/characters/{_esc(character_id)}")
|
|
133
|
+
|
|
134
|
+
# -- plumbing -----------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def _request(self, method: str, path: str,
|
|
137
|
+
body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
138
|
+
data = json.dumps(body).encode() if body is not None else None
|
|
139
|
+
req = urllib.request.Request(
|
|
140
|
+
self.base_url + path, data=data, method=method,
|
|
141
|
+
headers={
|
|
142
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
"User-Agent": "anva-python/0.1.0",
|
|
145
|
+
})
|
|
146
|
+
try:
|
|
147
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
148
|
+
raw = resp.read()
|
|
149
|
+
return json.loads(raw) if raw else {}
|
|
150
|
+
except urllib.error.HTTPError as e:
|
|
151
|
+
raw = e.read()
|
|
152
|
+
code, message = "request_failed", raw.decode(errors="replace")[:300]
|
|
153
|
+
try:
|
|
154
|
+
payload = json.loads(raw)
|
|
155
|
+
err = payload.get("error") or payload
|
|
156
|
+
code = err.get("code", code)
|
|
157
|
+
message = err.get("message", message)
|
|
158
|
+
except (TypeError, ValueError):
|
|
159
|
+
pass
|
|
160
|
+
raise AnvaError(e.code, code, message) from None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _esc(part: str) -> str:
|
|
164
|
+
return urllib.parse.quote(str(part), safe="")
|