tavus-cli 0.1.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.
- tavus_cli-0.1.0.dist-info/METADATA +192 -0
- tavus_cli-0.1.0.dist-info/RECORD +32 -0
- tavus_cli-0.1.0.dist-info/WHEEL +4 -0
- tavus_cli-0.1.0.dist-info/entry_points.txt +3 -0
- tavus_mcp/__init__.py +4 -0
- tavus_mcp/cli/__init__.py +1 -0
- tavus_mcp/cli/main.py +1467 -0
- tavus_mcp/sdk/__init__.py +6 -0
- tavus_mcp/sdk/auth/__init__.py +1 -0
- tavus_mcp/sdk/auth/keyring_store.py +20 -0
- tavus_mcp/sdk/auth/oauth.py +131 -0
- tavus_mcp/sdk/auth/session.py +46 -0
- tavus_mcp/sdk/client/__init__.py +3 -0
- tavus_mcp/sdk/client/http.py +451 -0
- tavus_mcp/sdk/env.py +126 -0
- tavus_mcp/sdk/errors.py +46 -0
- tavus_mcp/sdk/patch.py +92 -0
- tavus_mcp/sdk/recipes/__init__.py +6 -0
- tavus_mcp/sdk/recipes/build_and_verify.py +618 -0
- tavus_mcp/sdk/recipes/options.py +67 -0
- tavus_mcp/sdk/recipes/quickstart.py +48 -0
- tavus_mcp/sdk/recipes/scaffold_embed.py +115 -0
- tavus_mcp/sdk/recipes/templates.py +58 -0
- tavus_mcp/sdk/recipes/tool_reuse.py +124 -0
- tavus_mcp/sdk/schemas/__init__.py +16 -0
- tavus_mcp/sdk/schemas/file_manifest.py +21 -0
- tavus_mcp/sdk/schemas/guardrail.py +84 -0
- tavus_mcp/sdk/schemas/objective.py +131 -0
- tavus_mcp/sdk/schemas/persona.py +174 -0
- tavus_mcp/sdk/schemas/pronunciation.py +73 -0
- tavus_mcp/sdk/schemas/tool.py +349 -0
- tavus_mcp/server.py +877 -0
tavus_mcp/cli/main.py
ADDED
|
@@ -0,0 +1,1467 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from tavus_mcp.sdk.auth import keyring_store
|
|
15
|
+
from tavus_mcp.sdk.auth.oauth import login_with_browser
|
|
16
|
+
from tavus_mcp.sdk.auth.session import get_session
|
|
17
|
+
from tavus_mcp.sdk.client.http import TavusClient
|
|
18
|
+
from tavus_mcp.sdk.env import load_config
|
|
19
|
+
from tavus_mcp.sdk.errors import TavusError
|
|
20
|
+
from tavus_mcp.sdk.patch import allowed_patch_paths, validate_patch_operations
|
|
21
|
+
from tavus_mcp.sdk.recipes.build_and_verify import build_and_verify as build_and_verify_recipe
|
|
22
|
+
from tavus_mcp.sdk.recipes.options import describe_persona_options
|
|
23
|
+
from tavus_mcp.sdk.recipes.quickstart import quickstart as quickstart_recipe
|
|
24
|
+
from tavus_mcp.sdk.recipes.scaffold_embed import scaffold_embed
|
|
25
|
+
|
|
26
|
+
HELP_CONTEXT = {"help_option_names": ["-h", "--help"]}
|
|
27
|
+
|
|
28
|
+
app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
29
|
+
auth_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
30
|
+
persona_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
31
|
+
replica_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
32
|
+
conversation_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
33
|
+
resource_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
34
|
+
guardrail_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
35
|
+
objective_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
36
|
+
tool_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
37
|
+
pronunciation_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
38
|
+
persona_tools_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
39
|
+
builder_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
40
|
+
builder_update_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
41
|
+
chat_app = typer.Typer(no_args_is_help=True, context_settings=HELP_CONTEXT)
|
|
42
|
+
app.add_typer(auth_app, name="auth")
|
|
43
|
+
app.add_typer(persona_app, name="persona")
|
|
44
|
+
persona_app.add_typer(persona_tools_app, name="tools")
|
|
45
|
+
app.add_typer(replica_app, name="replica")
|
|
46
|
+
app.add_typer(conversation_app, name="conversation")
|
|
47
|
+
app.add_typer(resource_app, name="resource")
|
|
48
|
+
app.add_typer(guardrail_app, name="guardrail")
|
|
49
|
+
app.add_typer(objective_app, name="objective")
|
|
50
|
+
app.add_typer(tool_app, name="tool")
|
|
51
|
+
app.add_typer(pronunciation_app, name="pronunciation-dictionary")
|
|
52
|
+
app.add_typer(builder_app, name="builder")
|
|
53
|
+
builder_app.add_typer(builder_update_app, name="update")
|
|
54
|
+
app.add_typer(chat_app, name="chat")
|
|
55
|
+
console = Console()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.callback()
|
|
59
|
+
def main(
|
|
60
|
+
env: str | None = typer.Option(
|
|
61
|
+
None,
|
|
62
|
+
"--env",
|
|
63
|
+
"-e",
|
|
64
|
+
help="Target Tavus environment. Defaults to PROD; use TEST for the test DB.",
|
|
65
|
+
),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Tavus CLI."""
|
|
68
|
+
if env:
|
|
69
|
+
os.environ["TAVUS_ENV"] = env
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _run(coro: Any) -> Any:
|
|
73
|
+
try:
|
|
74
|
+
return asyncio.run(coro)
|
|
75
|
+
except TavusError as error:
|
|
76
|
+
console.print(f"[red]{error}[/red]")
|
|
77
|
+
raise typer.Exit(1) from error
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _json(data: Any) -> None:
|
|
81
|
+
console.print_json(json.dumps(data, default=str))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _parse_value(value: str) -> Any:
|
|
85
|
+
try:
|
|
86
|
+
return json.loads(value)
|
|
87
|
+
except json.JSONDecodeError:
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@app.command()
|
|
92
|
+
def doctor(
|
|
93
|
+
skip_network: bool = typer.Option(False, help="Only validate local configuration."),
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Show selected environment, auth source, and API reachability."""
|
|
96
|
+
config = load_config()
|
|
97
|
+
session = get_session(config, required=False)
|
|
98
|
+
table = Table(title="Tavus MCP Doctor")
|
|
99
|
+
table.add_column("Key")
|
|
100
|
+
table.add_column("Value")
|
|
101
|
+
table.add_row("env", config.env.value)
|
|
102
|
+
table.add_row("env_file", str(config.env_file) if config.env_file else "(none)")
|
|
103
|
+
table.add_row("public_api_base_url", str(config.public_api_base_url))
|
|
104
|
+
table.add_row("portal_api_base_url", str(config.portal_api_base_url))
|
|
105
|
+
table.add_row("dev_portal_url", str(config.dev_portal_url))
|
|
106
|
+
table.add_row("auth_source", session.source if session else "(missing)")
|
|
107
|
+
console.print(table)
|
|
108
|
+
|
|
109
|
+
if skip_network:
|
|
110
|
+
return
|
|
111
|
+
if not session:
|
|
112
|
+
raise typer.Exit(1)
|
|
113
|
+
|
|
114
|
+
async def check() -> None:
|
|
115
|
+
async with TavusClient(config, session) as client:
|
|
116
|
+
await client.personas.list(limit=1)
|
|
117
|
+
await client.replicas.list(limit=1)
|
|
118
|
+
|
|
119
|
+
_run(check())
|
|
120
|
+
console.print("[green]Network check passed.[/green]")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@auth_app.command("login")
|
|
124
|
+
def auth_login(
|
|
125
|
+
api_key: str | None = typer.Option(
|
|
126
|
+
None,
|
|
127
|
+
help="Store an existing Tavus API key instead of opening Firebase login.",
|
|
128
|
+
),
|
|
129
|
+
name: str | None = typer.Option(None, help="Name for the minted API key."),
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Log in with Firebase and store an env-scoped Tavus API key."""
|
|
132
|
+
config = load_config()
|
|
133
|
+
if api_key:
|
|
134
|
+
keyring_store.set_api_key(config, api_key)
|
|
135
|
+
console.print(f"Stored API key for {config.env.value}.")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
key_name = name or f"tavus-cli ({platform.node() or 'unknown-host'} {config.env.value})"
|
|
139
|
+
result = _run(login_with_browser(config, key_name=key_name))
|
|
140
|
+
console.print(f"Logged in as {result.email or 'Tavus user'} for {config.env.value}.")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@auth_app.command("logout")
|
|
144
|
+
def auth_logout() -> None:
|
|
145
|
+
"""Delete the env-scoped API key from local keyring."""
|
|
146
|
+
config = load_config()
|
|
147
|
+
keyring_store.delete_api_key(config)
|
|
148
|
+
console.print(f"Deleted keyring API key for {config.env.value}.")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@auth_app.command("status")
|
|
152
|
+
def auth_status() -> None:
|
|
153
|
+
"""Show whether credentials are available for the selected env."""
|
|
154
|
+
config = load_config()
|
|
155
|
+
session = get_session(config, required=False)
|
|
156
|
+
_json(
|
|
157
|
+
{
|
|
158
|
+
"env": config.env.value,
|
|
159
|
+
"authenticated": session is not None,
|
|
160
|
+
"source": session.source if session else None,
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@persona_app.command("list")
|
|
166
|
+
def persona_list(
|
|
167
|
+
limit: int = typer.Option(25),
|
|
168
|
+
page: int = typer.Option(0),
|
|
169
|
+
persona_type: str | None = typer.Option(None),
|
|
170
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
171
|
+
) -> None:
|
|
172
|
+
"""List personas for the selected env/account."""
|
|
173
|
+
|
|
174
|
+
async def run() -> Any:
|
|
175
|
+
async with TavusClient.from_env() as client:
|
|
176
|
+
return await client.personas.list(limit=limit, page=page, persona_type=persona_type)
|
|
177
|
+
|
|
178
|
+
data = _run(run())
|
|
179
|
+
if json_output:
|
|
180
|
+
_json(data)
|
|
181
|
+
return
|
|
182
|
+
_print_rows(data, ["persona_id", "persona_name", "default_replica_id", "updated_at"])
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@persona_app.command("get")
|
|
186
|
+
def persona_get(persona_id: str, include_settings: bool = typer.Option(False)) -> None:
|
|
187
|
+
"""Get a persona as JSON."""
|
|
188
|
+
|
|
189
|
+
async def run() -> Any:
|
|
190
|
+
async with TavusClient.from_env() as client:
|
|
191
|
+
return await client.personas.get(
|
|
192
|
+
persona_id,
|
|
193
|
+
include_settings=str(include_settings).lower(),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
_json(_run(run()))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@persona_app.command("create")
|
|
200
|
+
def persona_create(
|
|
201
|
+
system_prompt: str | None = typer.Option(None, "--system-prompt"),
|
|
202
|
+
name: str = typer.Option("Agentic Tavus Persona", "--name"),
|
|
203
|
+
replica_id: str | None = typer.Option(None),
|
|
204
|
+
pipeline_mode: str = typer.Option(
|
|
205
|
+
"full", "--pipeline-mode", help="full | speech-to-speech | echo"
|
|
206
|
+
),
|
|
207
|
+
greeting: str | None = typer.Option(None, "--greeting"),
|
|
208
|
+
context: str | None = typer.Option(None, "--context"),
|
|
209
|
+
layers_file: Path | None = typer.Option(
|
|
210
|
+
None, "--layers-file", help="JSON file with layer overrides."
|
|
211
|
+
),
|
|
212
|
+
memories: list[str] | None = typer.Option(
|
|
213
|
+
None, "--memory", help="Initial seed memory. Repeat for multiple."
|
|
214
|
+
),
|
|
215
|
+
objectives_id: str | None = typer.Option(None, "--objectives-id"),
|
|
216
|
+
guardrails_id: str | None = typer.Option(
|
|
217
|
+
None, "--guardrails-id", help="Legacy guardrail-set id."
|
|
218
|
+
),
|
|
219
|
+
guardrail_ids: list[str] | None = typer.Option(
|
|
220
|
+
None, "--guardrail-id", help="Repeat for multiple. New flat shape."
|
|
221
|
+
),
|
|
222
|
+
guardrail_tags: list[str] | None = typer.Option(
|
|
223
|
+
None, "--guardrail-tag", help="Repeat for multiple. New flat shape."
|
|
224
|
+
),
|
|
225
|
+
document_ids: list[str] | None = typer.Option(
|
|
226
|
+
None, "--document-id", help="Repeat for multiple."
|
|
227
|
+
),
|
|
228
|
+
document_tags: list[str] | None = typer.Option(
|
|
229
|
+
None, "--document-tag", help="Repeat for multiple."
|
|
230
|
+
),
|
|
231
|
+
is_template: bool = typer.Option(
|
|
232
|
+
False, "--template/--no-template", help="Flag as a clonable template."
|
|
233
|
+
),
|
|
234
|
+
) -> None:
|
|
235
|
+
"""Create a persona. ``--system-prompt`` is required for the default
|
|
236
|
+
``--pipeline-mode=full``; omit it for ``speech-to-speech`` or ``echo``.
|
|
237
|
+
Attach tools post-create with ``tavus persona tools attach``."""
|
|
238
|
+
|
|
239
|
+
async def run() -> Any:
|
|
240
|
+
async with TavusClient.from_env() as client:
|
|
241
|
+
body: dict[str, Any] = {"persona_name": name}
|
|
242
|
+
if pipeline_mode and pipeline_mode != "full":
|
|
243
|
+
body["pipeline_mode"] = pipeline_mode
|
|
244
|
+
if system_prompt is not None:
|
|
245
|
+
body["system_prompt"] = system_prompt
|
|
246
|
+
if replica_id:
|
|
247
|
+
body["default_replica_id"] = replica_id
|
|
248
|
+
if greeting is not None:
|
|
249
|
+
body["greeting"] = greeting
|
|
250
|
+
if context is not None:
|
|
251
|
+
body["context"] = context
|
|
252
|
+
if layers_file:
|
|
253
|
+
body["layers"] = json.loads(layers_file.read_text())
|
|
254
|
+
if memories:
|
|
255
|
+
body["memories"] = list(memories)
|
|
256
|
+
if objectives_id:
|
|
257
|
+
body["objectives_id"] = objectives_id
|
|
258
|
+
if guardrails_id:
|
|
259
|
+
body["guardrails_id"] = guardrails_id
|
|
260
|
+
if guardrail_ids:
|
|
261
|
+
body["guardrail_ids"] = list(guardrail_ids)
|
|
262
|
+
if guardrail_tags:
|
|
263
|
+
body["guardrail_tags"] = list(guardrail_tags)
|
|
264
|
+
if document_ids:
|
|
265
|
+
body["document_ids"] = list(document_ids)
|
|
266
|
+
if document_tags:
|
|
267
|
+
body["document_tags"] = list(document_tags)
|
|
268
|
+
if is_template:
|
|
269
|
+
body["is_template"] = True
|
|
270
|
+
return await client.personas.create(body)
|
|
271
|
+
|
|
272
|
+
_json(_run(run()))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@persona_app.command("delete")
|
|
276
|
+
def persona_delete(persona_id: str) -> None:
|
|
277
|
+
"""Delete a persona."""
|
|
278
|
+
|
|
279
|
+
async def run() -> Any:
|
|
280
|
+
async with TavusClient.from_env() as client:
|
|
281
|
+
return await client.personas.delete(persona_id)
|
|
282
|
+
|
|
283
|
+
_json(_run(run()))
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@persona_app.command("patch")
|
|
287
|
+
def persona_patch(
|
|
288
|
+
persona_id: str,
|
|
289
|
+
op: str = typer.Option("replace", help="JSON Patch op."),
|
|
290
|
+
path: str = typer.Option(..., help="JSON Pointer path, e.g. /persona_name."),
|
|
291
|
+
value: str | None = typer.Option(None, help="JSON value; strings may be passed as plain text."),
|
|
292
|
+
patch_file: Path | None = typer.Option(None, help="JSON patch file. Overrides op/path/value."),
|
|
293
|
+
dry_run: bool = typer.Option(False, help="Validate but do not send."),
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Apply a validated JSON Patch to a persona."""
|
|
296
|
+
if patch_file:
|
|
297
|
+
ops = json.loads(patch_file.read_text())
|
|
298
|
+
else:
|
|
299
|
+
item: dict[str, Any] = {"op": op, "path": path}
|
|
300
|
+
if value is not None:
|
|
301
|
+
item["value"] = _parse_value(value)
|
|
302
|
+
ops = [item]
|
|
303
|
+
validated = validate_patch_operations(ops)
|
|
304
|
+
if dry_run:
|
|
305
|
+
_json({"valid": True, "ops": validated})
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
async def run() -> Any:
|
|
309
|
+
async with TavusClient.from_env() as client:
|
|
310
|
+
return await client.personas.patch(persona_id, validated)
|
|
311
|
+
|
|
312
|
+
_json(_run(run()))
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@persona_app.command("options")
|
|
316
|
+
def persona_options(persona_id: str) -> None:
|
|
317
|
+
"""Return the persona plus valid account resources and patchable paths."""
|
|
318
|
+
|
|
319
|
+
async def run() -> Any:
|
|
320
|
+
async with TavusClient.from_env() as client:
|
|
321
|
+
return await describe_persona_options(client, persona_id)
|
|
322
|
+
|
|
323
|
+
_json(_run(run()))
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@persona_app.command("paths")
|
|
327
|
+
def persona_paths() -> None:
|
|
328
|
+
"""List locally known JSON Patch paths for personas."""
|
|
329
|
+
_json(sorted(allowed_patch_paths()))
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@persona_app.command("build")
|
|
333
|
+
def persona_build(
|
|
334
|
+
prompt: str | None = typer.Option(
|
|
335
|
+
None,
|
|
336
|
+
"--prompt",
|
|
337
|
+
help="Initial creator prompt, e.g. 'I want to make a persona for an office greeter...'.",
|
|
338
|
+
),
|
|
339
|
+
goal: str | None = typer.Option(None, "--goal", hidden=True),
|
|
340
|
+
replica_id: str | None = typer.Option(
|
|
341
|
+
None,
|
|
342
|
+
"--replica-id",
|
|
343
|
+
help="Override the default replica selected by the builder backend.",
|
|
344
|
+
),
|
|
345
|
+
max_rounds: int = typer.Option(
|
|
346
|
+
4,
|
|
347
|
+
"--max-rounds",
|
|
348
|
+
min=1,
|
|
349
|
+
help="Maximum builder question/answer rounds before publishing.",
|
|
350
|
+
),
|
|
351
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
352
|
+
answer: list[str] | None = typer.Option(None, "--answer", hidden=True),
|
|
353
|
+
model: str = typer.Option("claude-sonnet-4-6", "--model", hidden=True),
|
|
354
|
+
inner_model: str | None = typer.Option(None, "--inner-model", hidden=True),
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Build a persona through the same conversational builder flow used by
|
|
357
|
+
creator-studio, then verify the published persona through CVI chat mode."""
|
|
358
|
+
|
|
359
|
+
creator_prompt = (prompt or goal or "").strip()
|
|
360
|
+
if not creator_prompt:
|
|
361
|
+
raise typer.BadParameter("Provide --prompt.", param_hint="--prompt")
|
|
362
|
+
|
|
363
|
+
io_console = Console(stderr=json_output)
|
|
364
|
+
|
|
365
|
+
def print_builder_reply(
|
|
366
|
+
reply: dict[str, Any], round_number: int, _transcript: list[dict[str, Any]]
|
|
367
|
+
) -> None:
|
|
368
|
+
text = str(reply.get("text") or "").strip()
|
|
369
|
+
if text:
|
|
370
|
+
io_console.print(f"\n[bold]Builder[/bold] [{round_number}/{max_rounds}]: {text}")
|
|
371
|
+
suggestions = [str(item) for item in (reply.get("suggestions") or []) if item]
|
|
372
|
+
if suggestions:
|
|
373
|
+
io_console.print("[dim]Suggestions:[/dim] " + " | ".join(suggestions))
|
|
374
|
+
|
|
375
|
+
def ask_creator(
|
|
376
|
+
_reply: dict[str, Any], _round_number: int, _transcript: list[dict[str, Any]]
|
|
377
|
+
) -> str | None:
|
|
378
|
+
try:
|
|
379
|
+
value = io_console.input("[bold]You[/bold] (blank to finish): ")
|
|
380
|
+
except (EOFError, KeyboardInterrupt):
|
|
381
|
+
return None
|
|
382
|
+
return value.strip() or None
|
|
383
|
+
|
|
384
|
+
async def run() -> Any:
|
|
385
|
+
async with TavusClient.from_env() as client:
|
|
386
|
+
return await build_and_verify_recipe(
|
|
387
|
+
client,
|
|
388
|
+
prompt=creator_prompt,
|
|
389
|
+
replica_id=replica_id,
|
|
390
|
+
model=model,
|
|
391
|
+
inner_model=inner_model,
|
|
392
|
+
max_rounds=max_rounds,
|
|
393
|
+
answers=list(answer) if answer else None,
|
|
394
|
+
answer_provider=None if answer else ask_creator,
|
|
395
|
+
on_builder_reply=print_builder_reply,
|
|
396
|
+
auto_refine=True,
|
|
397
|
+
max_refine_rounds=1,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
data = _run(run())
|
|
401
|
+
if json_output:
|
|
402
|
+
_json(data)
|
|
403
|
+
return
|
|
404
|
+
verdict = data.get("verdict") or {}
|
|
405
|
+
console.print(f"[bold]builder_id[/bold]: {data.get('builder_id')}")
|
|
406
|
+
console.print(f"[bold]persona_id[/bold]: {data.get('persona_id')}")
|
|
407
|
+
if data.get("replica_id"):
|
|
408
|
+
console.print(f"[bold]replica_id[/bold]: {data.get('replica_id')}")
|
|
409
|
+
if data.get("persona_url"):
|
|
410
|
+
console.print(f"[bold]persona_url[/bold]: {data.get('persona_url')}")
|
|
411
|
+
console.print(f"[bold]overall[/bold]: {verdict.get('overall', '?')}")
|
|
412
|
+
if data.get("refine_rounds_used"):
|
|
413
|
+
console.print(f"[dim]refine_rounds_used={data['refine_rounds_used']}[/dim]")
|
|
414
|
+
summary = verdict.get("summary")
|
|
415
|
+
if summary:
|
|
416
|
+
console.print(f"\n{summary}")
|
|
417
|
+
if data.get("system_prompt"):
|
|
418
|
+
console.print("\n[bold]system_prompt[/bold]:")
|
|
419
|
+
console.print(data["system_prompt"])
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@persona_app.command("preview")
|
|
423
|
+
def persona_preview(
|
|
424
|
+
persona_id: str,
|
|
425
|
+
replica_id: str | None = typer.Option(None),
|
|
426
|
+
name: str | None = typer.Option(None, "--name"),
|
|
427
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
428
|
+
) -> None:
|
|
429
|
+
"""Start a full preview conversation against a persona and print the
|
|
430
|
+
conversation_url so a human can verify visually."""
|
|
431
|
+
|
|
432
|
+
async def run() -> Any:
|
|
433
|
+
async with TavusClient.from_env() as client:
|
|
434
|
+
body: dict[str, Any] = {"persona_id": persona_id}
|
|
435
|
+
if replica_id:
|
|
436
|
+
body["replica_id"] = replica_id
|
|
437
|
+
else:
|
|
438
|
+
persona = await client.personas.get(persona_id)
|
|
439
|
+
default_replica = (
|
|
440
|
+
persona.get("default_replica_id") if isinstance(persona, dict) else None
|
|
441
|
+
)
|
|
442
|
+
if default_replica:
|
|
443
|
+
body["replica_id"] = default_replica
|
|
444
|
+
if name:
|
|
445
|
+
body["conversation_name"] = name
|
|
446
|
+
return await client.conversations.create(body)
|
|
447
|
+
|
|
448
|
+
data = _run(run())
|
|
449
|
+
if json_output:
|
|
450
|
+
_json(data)
|
|
451
|
+
return
|
|
452
|
+
url = data.get("conversation_url") if isinstance(data, dict) else None
|
|
453
|
+
if url:
|
|
454
|
+
console.print(url)
|
|
455
|
+
else:
|
|
456
|
+
_json(data)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@replica_app.command("list")
|
|
460
|
+
def replica_list(
|
|
461
|
+
limit: int = typer.Option(25),
|
|
462
|
+
stock: bool = typer.Option(False, "--stock"),
|
|
463
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
464
|
+
) -> None:
|
|
465
|
+
"""List replicas."""
|
|
466
|
+
|
|
467
|
+
async def run() -> Any:
|
|
468
|
+
async with TavusClient.from_env() as client:
|
|
469
|
+
return await client.replicas.list(limit=limit, replica_type="system" if stock else None)
|
|
470
|
+
|
|
471
|
+
data = _run(run())
|
|
472
|
+
if json_output:
|
|
473
|
+
_json(data)
|
|
474
|
+
return
|
|
475
|
+
_print_rows(data, ["replica_id", "replica_name", "status", "model_name"])
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@conversation_app.command("create")
|
|
479
|
+
def conversation_create(
|
|
480
|
+
persona_id: str | None = typer.Option(None),
|
|
481
|
+
replica_id: str | None = typer.Option(None),
|
|
482
|
+
name: str | None = typer.Option(None),
|
|
483
|
+
) -> None:
|
|
484
|
+
"""Create a conversation."""
|
|
485
|
+
|
|
486
|
+
async def run() -> Any:
|
|
487
|
+
body = {"persona_id": persona_id, "replica_id": replica_id, "conversation_name": name}
|
|
488
|
+
async with TavusClient.from_env() as client:
|
|
489
|
+
return await client.conversations.create({k: v for k, v in body.items() if v})
|
|
490
|
+
|
|
491
|
+
_json(_run(run()))
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@conversation_app.command("end")
|
|
495
|
+
def conversation_end(conversation_id: str) -> None:
|
|
496
|
+
"""End a conversation."""
|
|
497
|
+
|
|
498
|
+
async def run() -> Any:
|
|
499
|
+
async with TavusClient.from_env() as client:
|
|
500
|
+
return await client.conversations.end(conversation_id)
|
|
501
|
+
|
|
502
|
+
_json(_run(run()))
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@builder_app.command("create")
|
|
506
|
+
def builder_create(
|
|
507
|
+
name: str = typer.Option(..., "--name"),
|
|
508
|
+
greeting: str | None = typer.Option(None),
|
|
509
|
+
persona_id: str | None = typer.Option(None),
|
|
510
|
+
model: str | None = typer.Option(None, help="claude or gpt-oss-120b"),
|
|
511
|
+
) -> None:
|
|
512
|
+
"""Start a builder session. The CLI mirrors the dev-portal creator-studio
|
|
513
|
+
flow — give the session a name and optionally seed a greeting or attach
|
|
514
|
+
an existing persona."""
|
|
515
|
+
|
|
516
|
+
async def run() -> Any:
|
|
517
|
+
body: dict[str, Any] = {"name": name}
|
|
518
|
+
if greeting is not None:
|
|
519
|
+
body["greeting"] = greeting
|
|
520
|
+
if persona_id:
|
|
521
|
+
body["persona_id"] = persona_id
|
|
522
|
+
if model:
|
|
523
|
+
body["model"] = model
|
|
524
|
+
async with TavusClient.from_env() as client:
|
|
525
|
+
return await client.builders.create(body)
|
|
526
|
+
|
|
527
|
+
_json(_run(run()))
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@builder_app.command("list")
|
|
531
|
+
def builder_list(
|
|
532
|
+
limit: int | None = typer.Option(None),
|
|
533
|
+
page: int | None = typer.Option(None),
|
|
534
|
+
persona_id: str | None = typer.Option(None),
|
|
535
|
+
name: str | None = typer.Option(None),
|
|
536
|
+
status: str | None = typer.Option(None),
|
|
537
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
538
|
+
) -> None:
|
|
539
|
+
"""List builder sessions for the authenticated account."""
|
|
540
|
+
|
|
541
|
+
async def run() -> Any:
|
|
542
|
+
async with TavusClient.from_env() as client:
|
|
543
|
+
return await client.builders.list(
|
|
544
|
+
limit=limit, page=page, persona_id=persona_id, name=name, status=status
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
data = _run(run())
|
|
548
|
+
if json_output:
|
|
549
|
+
_json(data)
|
|
550
|
+
return
|
|
551
|
+
sessions = data.get("sessions", data) if isinstance(data, dict) else data
|
|
552
|
+
if not isinstance(sessions, list):
|
|
553
|
+
_json(data)
|
|
554
|
+
return
|
|
555
|
+
columns = ("uuid", "name", "persona_id", "status", "updated_at")
|
|
556
|
+
table = Table()
|
|
557
|
+
for column in columns:
|
|
558
|
+
table.add_column(column)
|
|
559
|
+
for row in sessions:
|
|
560
|
+
table.add_row(*[str(row.get(column, "")) for column in columns])
|
|
561
|
+
console.print(table)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
@builder_app.command("get")
|
|
565
|
+
def builder_get(builder_id: str) -> None:
|
|
566
|
+
"""Fetch a single builder session."""
|
|
567
|
+
|
|
568
|
+
async def run() -> Any:
|
|
569
|
+
async with TavusClient.from_env() as client:
|
|
570
|
+
return await client.builders.get(builder_id)
|
|
571
|
+
|
|
572
|
+
_json(_run(run()))
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
@builder_app.command("delete")
|
|
576
|
+
def builder_delete(builder_id: str) -> None:
|
|
577
|
+
"""Soft-delete a builder session."""
|
|
578
|
+
|
|
579
|
+
async def run() -> Any:
|
|
580
|
+
async with TavusClient.from_env() as client:
|
|
581
|
+
return await client.builders.delete(builder_id)
|
|
582
|
+
|
|
583
|
+
_json(_run(run()))
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@builder_app.command("chat")
|
|
587
|
+
def builder_chat(
|
|
588
|
+
builder_id: str,
|
|
589
|
+
message: str = typer.Option(..., "--message"),
|
|
590
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
591
|
+
) -> None:
|
|
592
|
+
"""Send a chat turn to the builder. Prints the assistant text, any
|
|
593
|
+
suggestion chips, then a footer with ``draft_ready`` and the targets the
|
|
594
|
+
builder LLM signaled (use ``tavus builder update <target> ...`` next)."""
|
|
595
|
+
|
|
596
|
+
async def run() -> Any:
|
|
597
|
+
async with TavusClient.from_env() as client:
|
|
598
|
+
return await client.builders.chat(builder_id, message)
|
|
599
|
+
|
|
600
|
+
data = _run(run())
|
|
601
|
+
if json_output:
|
|
602
|
+
_json(data)
|
|
603
|
+
return
|
|
604
|
+
text = data.get("text") or ""
|
|
605
|
+
suggestions = data.get("suggestions") or []
|
|
606
|
+
targets = data.get("targets") or []
|
|
607
|
+
draft_ready = data.get("draft_ready", False)
|
|
608
|
+
if text:
|
|
609
|
+
console.print(text)
|
|
610
|
+
if suggestions:
|
|
611
|
+
console.print("\n[bold]Suggestions[/bold]")
|
|
612
|
+
for i, suggestion in enumerate(suggestions, 1):
|
|
613
|
+
console.print(f" {i}. {suggestion}")
|
|
614
|
+
console.print(f"\n[dim]draft_ready={draft_ready} targets={targets or '[]'}[/dim]")
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
@builder_app.command("history")
|
|
618
|
+
def builder_history(
|
|
619
|
+
builder_id: str,
|
|
620
|
+
limit: int = typer.Option(50),
|
|
621
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
622
|
+
) -> None:
|
|
623
|
+
"""Print the chat transcript for a builder session."""
|
|
624
|
+
|
|
625
|
+
async def run() -> Any:
|
|
626
|
+
async with TavusClient.from_env() as client:
|
|
627
|
+
return await client.builders.chat_history(builder_id, limit=limit)
|
|
628
|
+
|
|
629
|
+
data = _run(run())
|
|
630
|
+
if json_output:
|
|
631
|
+
_json(data)
|
|
632
|
+
return
|
|
633
|
+
messages = data.get("messages", data) if isinstance(data, dict) else data
|
|
634
|
+
if not isinstance(messages, list):
|
|
635
|
+
_json(data)
|
|
636
|
+
return
|
|
637
|
+
for entry in messages:
|
|
638
|
+
role = entry.get("role", "?")
|
|
639
|
+
content = entry.get("content", "")
|
|
640
|
+
console.print(f"[bold]{role}[/bold]: {content}")
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@builder_app.command("append-messages")
|
|
644
|
+
def builder_append_messages(
|
|
645
|
+
builder_id: str,
|
|
646
|
+
file: Path = typer.Option(..., "--file", help="JSON file with a messages array."),
|
|
647
|
+
) -> None:
|
|
648
|
+
"""Append raw {role, content} messages to a session without invoking the LLM."""
|
|
649
|
+
messages = json.loads(file.read_text())
|
|
650
|
+
|
|
651
|
+
async def run() -> Any:
|
|
652
|
+
async with TavusClient.from_env() as client:
|
|
653
|
+
return await client.builders.append_messages(builder_id, messages)
|
|
654
|
+
|
|
655
|
+
_json(_run(run()))
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
@builder_app.command("publish")
|
|
659
|
+
def builder_publish(builder_id: str) -> None:
|
|
660
|
+
"""Mark a builder session complete and publish its persona."""
|
|
661
|
+
|
|
662
|
+
async def run() -> Any:
|
|
663
|
+
async with TavusClient.from_env() as client:
|
|
664
|
+
return await client.builders.publish(builder_id)
|
|
665
|
+
|
|
666
|
+
_json(_run(run()))
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
@builder_update_app.command("objectives")
|
|
670
|
+
def builder_update_objectives(
|
|
671
|
+
builder_id: str,
|
|
672
|
+
message: str = typer.Option(..., "--message"),
|
|
673
|
+
) -> None:
|
|
674
|
+
"""Run an LLM update on the persona's objectives list."""
|
|
675
|
+
|
|
676
|
+
async def run() -> Any:
|
|
677
|
+
async with TavusClient.from_env() as client:
|
|
678
|
+
return await client.builders.update_objectives(builder_id, message)
|
|
679
|
+
|
|
680
|
+
_json(_run(run()))
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
@builder_update_app.command("guardrails")
|
|
684
|
+
def builder_update_guardrails(
|
|
685
|
+
builder_id: str,
|
|
686
|
+
message: str = typer.Option(..., "--message"),
|
|
687
|
+
) -> None:
|
|
688
|
+
"""Run an LLM update on the persona's guardrails list."""
|
|
689
|
+
|
|
690
|
+
async def run() -> Any:
|
|
691
|
+
async with TavusClient.from_env() as client:
|
|
692
|
+
return await client.builders.update_guardrails(builder_id, message)
|
|
693
|
+
|
|
694
|
+
_json(_run(run()))
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
@builder_update_app.command("greeting")
|
|
698
|
+
def builder_update_greeting(
|
|
699
|
+
builder_id: str,
|
|
700
|
+
message: str = typer.Option(..., "--message"),
|
|
701
|
+
) -> None:
|
|
702
|
+
"""Run an LLM refinement of the persona's greeting."""
|
|
703
|
+
|
|
704
|
+
async def run() -> Any:
|
|
705
|
+
async with TavusClient.from_env() as client:
|
|
706
|
+
return await client.builders.update_greeting(builder_id, message)
|
|
707
|
+
|
|
708
|
+
_json(_run(run()))
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
@builder_update_app.command("personality")
|
|
712
|
+
def builder_update_personality(
|
|
713
|
+
builder_id: str,
|
|
714
|
+
message: str = typer.Option(..., "--message"),
|
|
715
|
+
persona_name: bool = typer.Option(False, "--name", help="Also update persona_name."),
|
|
716
|
+
system_prompt: bool = typer.Option(False, "--system-prompt", help="Also update system_prompt."),
|
|
717
|
+
) -> None:
|
|
718
|
+
"""Run an LLM refinement of persona name and/or system prompt."""
|
|
719
|
+
|
|
720
|
+
async def run() -> Any:
|
|
721
|
+
async with TavusClient.from_env() as client:
|
|
722
|
+
return await client.builders.update_personality(
|
|
723
|
+
builder_id,
|
|
724
|
+
message,
|
|
725
|
+
persona_name=persona_name,
|
|
726
|
+
system_prompt=system_prompt,
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
_json(_run(run()))
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
@chat_app.command("start")
|
|
733
|
+
def chat_start(
|
|
734
|
+
persona_id: str = typer.Option(..., "--persona-id"),
|
|
735
|
+
greeting: str | None = typer.Option(None),
|
|
736
|
+
name: str | None = typer.Option(None, "--name"),
|
|
737
|
+
) -> None:
|
|
738
|
+
"""Start a text-only conversation against a persona."""
|
|
739
|
+
|
|
740
|
+
async def run() -> Any:
|
|
741
|
+
body: dict[str, Any] = {"persona_id": persona_id, "chat": True}
|
|
742
|
+
if greeting:
|
|
743
|
+
body["custom_greeting"] = greeting
|
|
744
|
+
if name:
|
|
745
|
+
body["conversation_name"] = name
|
|
746
|
+
async with TavusClient.from_env() as client:
|
|
747
|
+
return await client.conversations.create(body)
|
|
748
|
+
|
|
749
|
+
_json(_run(run()))
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
@chat_app.command("turn")
|
|
753
|
+
def chat_turn(
|
|
754
|
+
conversation_id: str,
|
|
755
|
+
message: str = typer.Option(..., "--message"),
|
|
756
|
+
timeout: float = typer.Option(20.0, "--timeout"),
|
|
757
|
+
) -> None:
|
|
758
|
+
"""Send one user turn and print the persona's reply."""
|
|
759
|
+
|
|
760
|
+
async def run() -> Any:
|
|
761
|
+
async with TavusClient.from_env() as client:
|
|
762
|
+
return await client.conversations.chat_turn(
|
|
763
|
+
conversation_id, message, timeout_s=timeout
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
console.print(_run(run()))
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
@chat_app.command("end")
|
|
770
|
+
def chat_end(conversation_id: str) -> None:
|
|
771
|
+
"""End a chat-mode conversation."""
|
|
772
|
+
|
|
773
|
+
async def run() -> Any:
|
|
774
|
+
async with TavusClient.from_env() as client:
|
|
775
|
+
return await client.conversations.end(conversation_id)
|
|
776
|
+
|
|
777
|
+
_json(_run(run()))
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
@app.command()
|
|
781
|
+
def quickstart(
|
|
782
|
+
system_prompt: str = typer.Option(...),
|
|
783
|
+
name: str = typer.Option("Agentic Tavus Persona", "--name"),
|
|
784
|
+
replica_id: str | None = typer.Option(None),
|
|
785
|
+
) -> None:
|
|
786
|
+
"""Create a persona and conversation using a stock replica by default."""
|
|
787
|
+
|
|
788
|
+
async def run() -> Any:
|
|
789
|
+
async with TavusClient.from_env() as client:
|
|
790
|
+
return await quickstart_recipe(
|
|
791
|
+
client,
|
|
792
|
+
system_prompt=system_prompt,
|
|
793
|
+
persona_name=name,
|
|
794
|
+
replica_id=replica_id,
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
_json(_run(run()))
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
@app.command()
|
|
801
|
+
def embed(
|
|
802
|
+
conversation_url: str = typer.Option(...),
|
|
803
|
+
target: str = typer.Option("iframe"),
|
|
804
|
+
write: bool = typer.Option(False, help="Write files. Default prints manifest only."),
|
|
805
|
+
) -> None:
|
|
806
|
+
"""Create an embed file manifest, optionally writing it locally."""
|
|
807
|
+
manifest = scaffold_embed(conversation_url=conversation_url, target=target) # type: ignore[arg-type]
|
|
808
|
+
if not write:
|
|
809
|
+
_json(manifest.model_dump())
|
|
810
|
+
return
|
|
811
|
+
for entry in manifest.files:
|
|
812
|
+
path = Path(entry.path)
|
|
813
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
814
|
+
path.write_text(entry.content)
|
|
815
|
+
console.print(f"Wrote {path}")
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
@resource_app.command("list")
|
|
819
|
+
def resource_list(resource: str, limit: int = typer.Option(25)) -> None:
|
|
820
|
+
"""List a supported resource: guardrails, objectives, documents, voices, tools."""
|
|
821
|
+
|
|
822
|
+
async def run() -> Any:
|
|
823
|
+
async with TavusClient.from_env() as client:
|
|
824
|
+
selected = _resource(client, resource)
|
|
825
|
+
return await selected.list(limit=limit)
|
|
826
|
+
|
|
827
|
+
_json(_run(run()))
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
@resource_app.command("get")
|
|
831
|
+
def resource_get(resource: str, resource_id: str) -> None:
|
|
832
|
+
"""Get a supported resource."""
|
|
833
|
+
|
|
834
|
+
async def run() -> Any:
|
|
835
|
+
async with TavusClient.from_env() as client:
|
|
836
|
+
selected = _resource(client, resource)
|
|
837
|
+
return await selected.get(resource_id)
|
|
838
|
+
|
|
839
|
+
_json(_run(run()))
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
@guardrail_app.command("list")
|
|
843
|
+
def guardrail_list(
|
|
844
|
+
limit: int = typer.Option(25),
|
|
845
|
+
page: int = typer.Option(1),
|
|
846
|
+
type: str = typer.Option("user", help="user | system | all"),
|
|
847
|
+
name_or_uuid: str | None = typer.Option(None),
|
|
848
|
+
tags: str | None = typer.Option(None, help="Comma-separated tag filter."),
|
|
849
|
+
legacy: bool = typer.Option(False, help="Return the legacy set-shape list."),
|
|
850
|
+
verbose: bool = typer.Option(False, help="Include persona_refs + guardrail_type."),
|
|
851
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
852
|
+
) -> None:
|
|
853
|
+
"""List guardrails. Defaults to the new flat (per-rule) shape."""
|
|
854
|
+
|
|
855
|
+
async def run() -> Any:
|
|
856
|
+
params: dict[str, Any] = {
|
|
857
|
+
"limit": limit,
|
|
858
|
+
"page": page,
|
|
859
|
+
"type": type,
|
|
860
|
+
"legacy": "true" if legacy else "false",
|
|
861
|
+
}
|
|
862
|
+
if name_or_uuid:
|
|
863
|
+
params["name_or_uuid"] = name_or_uuid
|
|
864
|
+
if tags:
|
|
865
|
+
params["tags"] = tags
|
|
866
|
+
if verbose:
|
|
867
|
+
params["verbose"] = "true"
|
|
868
|
+
async with TavusClient.from_env() as client:
|
|
869
|
+
return await client.guardrails.list(**params)
|
|
870
|
+
|
|
871
|
+
data = _run(run())
|
|
872
|
+
if json_output:
|
|
873
|
+
_json(data)
|
|
874
|
+
return
|
|
875
|
+
_print_rows(
|
|
876
|
+
data, ["uuid", "guardrail_name", "modality", "tags", "updated_at"]
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
@guardrail_app.command("get")
|
|
881
|
+
def guardrail_get(
|
|
882
|
+
guardrail_id: str,
|
|
883
|
+
verbose: bool = typer.Option(False),
|
|
884
|
+
legacy: bool | None = typer.Option(None),
|
|
885
|
+
) -> None:
|
|
886
|
+
async def run() -> Any:
|
|
887
|
+
params: dict[str, Any] = {}
|
|
888
|
+
if verbose:
|
|
889
|
+
params["verbose"] = "true"
|
|
890
|
+
if legacy is not None:
|
|
891
|
+
params["legacy"] = "true" if legacy else "false"
|
|
892
|
+
async with TavusClient.from_env() as client:
|
|
893
|
+
return await client.guardrails.get(guardrail_id, **params)
|
|
894
|
+
|
|
895
|
+
_json(_run(run()))
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
@guardrail_app.command("create")
|
|
899
|
+
def guardrail_create(
|
|
900
|
+
name: str = typer.Option(..., "--name", help="guardrail_name"),
|
|
901
|
+
prompt: str = typer.Option(..., "--prompt", help="guardrail_prompt"),
|
|
902
|
+
modality: str = typer.Option("verbal", help="verbal | visual | audio"),
|
|
903
|
+
callback_url: str = typer.Option(""),
|
|
904
|
+
tool_call: str | None = typer.Option(None, help="JSON object."),
|
|
905
|
+
app_message: bool = typer.Option(True, "--app-message/--no-app-message"),
|
|
906
|
+
tags: list[str] | None = typer.Option(None, "--tag", help="Repeat for multiple."),
|
|
907
|
+
) -> None:
|
|
908
|
+
"""Create a flat (non-set) guardrail."""
|
|
909
|
+
|
|
910
|
+
async def run() -> Any:
|
|
911
|
+
body: dict[str, Any] = {
|
|
912
|
+
"guardrail_name": name,
|
|
913
|
+
"guardrail_prompt": prompt,
|
|
914
|
+
"modality": modality,
|
|
915
|
+
"callback_url": callback_url,
|
|
916
|
+
"app_message": app_message,
|
|
917
|
+
"tags": list(tags) if tags else [],
|
|
918
|
+
}
|
|
919
|
+
if tool_call:
|
|
920
|
+
body["tool_call"] = json.loads(tool_call)
|
|
921
|
+
async with TavusClient.from_env() as client:
|
|
922
|
+
return await client.guardrails.create(body)
|
|
923
|
+
|
|
924
|
+
_json(_run(run()))
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
@guardrail_app.command("patch")
|
|
928
|
+
def guardrail_patch(
|
|
929
|
+
guardrail_id: str,
|
|
930
|
+
file: Path | None = typer.Option(
|
|
931
|
+
None, "--file", help="JSON file with fields to update."
|
|
932
|
+
),
|
|
933
|
+
name: str | None = typer.Option(None, "--name"),
|
|
934
|
+
prompt: str | None = typer.Option(None, "--prompt"),
|
|
935
|
+
modality: str | None = typer.Option(None),
|
|
936
|
+
callback_url: str | None = typer.Option(None),
|
|
937
|
+
tool_call: str | None = typer.Option(None, help="JSON object."),
|
|
938
|
+
app_message: bool | None = typer.Option(None, "--app-message/--no-app-message"),
|
|
939
|
+
tags: list[str] | None = typer.Option(None, "--tag"),
|
|
940
|
+
) -> None:
|
|
941
|
+
"""Patch a guardrail. RQH replaces each supplied field whole."""
|
|
942
|
+
|
|
943
|
+
async def run() -> Any:
|
|
944
|
+
if file:
|
|
945
|
+
body = json.loads(file.read_text())
|
|
946
|
+
else:
|
|
947
|
+
body = {}
|
|
948
|
+
if name is not None:
|
|
949
|
+
body["guardrail_name"] = name
|
|
950
|
+
if prompt is not None:
|
|
951
|
+
body["guardrail_prompt"] = prompt
|
|
952
|
+
if modality is not None:
|
|
953
|
+
body["modality"] = modality
|
|
954
|
+
if callback_url is not None:
|
|
955
|
+
body["callback_url"] = callback_url
|
|
956
|
+
if tool_call is not None:
|
|
957
|
+
body["tool_call"] = json.loads(tool_call)
|
|
958
|
+
if app_message is not None:
|
|
959
|
+
body["app_message"] = app_message
|
|
960
|
+
if tags is not None:
|
|
961
|
+
body["tags"] = list(tags)
|
|
962
|
+
async with TavusClient.from_env() as client:
|
|
963
|
+
return await client.guardrails.patch(guardrail_id, body)
|
|
964
|
+
|
|
965
|
+
_json(_run(run()))
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
@guardrail_app.command("delete")
|
|
969
|
+
def guardrail_delete(guardrail_id: str) -> None:
|
|
970
|
+
async def run() -> Any:
|
|
971
|
+
async with TavusClient.from_env() as client:
|
|
972
|
+
return await client.guardrails.delete(guardrail_id)
|
|
973
|
+
|
|
974
|
+
_json(_run(run()))
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
@guardrail_app.command("tags")
|
|
978
|
+
def guardrail_tags(
|
|
979
|
+
search: str | None = typer.Option(None),
|
|
980
|
+
page: int | None = typer.Option(None),
|
|
981
|
+
limit: int | None = typer.Option(None),
|
|
982
|
+
) -> None:
|
|
983
|
+
"""List tags applied to the account's guardrails."""
|
|
984
|
+
|
|
985
|
+
async def run() -> Any:
|
|
986
|
+
async with TavusClient.from_env() as client:
|
|
987
|
+
return await client.guardrails.tags(
|
|
988
|
+
search=search, page=page, limit=limit
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
_json(_run(run()))
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
@objective_app.command("list")
|
|
995
|
+
def objective_list(
|
|
996
|
+
limit: int = typer.Option(25),
|
|
997
|
+
page: int = typer.Option(1),
|
|
998
|
+
type: str = typer.Option("user", help="user | system | all"),
|
|
999
|
+
name_or_uuid: str | None = typer.Option(None),
|
|
1000
|
+
sort: str = typer.Option("ascending"),
|
|
1001
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
1002
|
+
) -> None:
|
|
1003
|
+
"""List objective sets."""
|
|
1004
|
+
|
|
1005
|
+
async def run() -> Any:
|
|
1006
|
+
params: dict[str, Any] = {
|
|
1007
|
+
"limit": limit,
|
|
1008
|
+
"page": page,
|
|
1009
|
+
"type": type,
|
|
1010
|
+
"sort": sort,
|
|
1011
|
+
}
|
|
1012
|
+
if name_or_uuid:
|
|
1013
|
+
params["name_or_uuid"] = name_or_uuid
|
|
1014
|
+
async with TavusClient.from_env() as client:
|
|
1015
|
+
return await client.objectives.list(**params)
|
|
1016
|
+
|
|
1017
|
+
data = _run(run())
|
|
1018
|
+
if json_output:
|
|
1019
|
+
_json(data)
|
|
1020
|
+
return
|
|
1021
|
+
_print_rows(
|
|
1022
|
+
data, ["objectives_id", "objectives_name", "updated_at"]
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
@objective_app.command("get")
|
|
1027
|
+
def objective_get(objectives_id: str) -> None:
|
|
1028
|
+
async def run() -> Any:
|
|
1029
|
+
async with TavusClient.from_env() as client:
|
|
1030
|
+
return await client.objectives.get(objectives_id)
|
|
1031
|
+
|
|
1032
|
+
_json(_run(run()))
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
@objective_app.command("create")
|
|
1036
|
+
def objective_create(
|
|
1037
|
+
file: Path = typer.Option(
|
|
1038
|
+
...,
|
|
1039
|
+
"--file",
|
|
1040
|
+
help="JSON file with {name, data, allow_loops}.",
|
|
1041
|
+
),
|
|
1042
|
+
) -> None:
|
|
1043
|
+
"""Create an objective set from a JSON file."""
|
|
1044
|
+
|
|
1045
|
+
async def run() -> Any:
|
|
1046
|
+
body = json.loads(file.read_text())
|
|
1047
|
+
async with TavusClient.from_env() as client:
|
|
1048
|
+
return await client.objectives.create(body)
|
|
1049
|
+
|
|
1050
|
+
_json(_run(run()))
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
@objective_app.command("patch")
|
|
1054
|
+
def objective_patch(
|
|
1055
|
+
objectives_id: str,
|
|
1056
|
+
file: Path = typer.Option(
|
|
1057
|
+
..., "--file", help="JSON file with a JSON Patch ops array."
|
|
1058
|
+
),
|
|
1059
|
+
) -> None:
|
|
1060
|
+
"""Patch an objective set with JSON Patch ops."""
|
|
1061
|
+
|
|
1062
|
+
async def run() -> Any:
|
|
1063
|
+
ops = json.loads(file.read_text())
|
|
1064
|
+
async with TavusClient.from_env() as client:
|
|
1065
|
+
return await client.objectives.patch(objectives_id, ops)
|
|
1066
|
+
|
|
1067
|
+
_json(_run(run()))
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
@objective_app.command("delete")
|
|
1071
|
+
def objective_delete(objectives_id: str) -> None:
|
|
1072
|
+
async def run() -> Any:
|
|
1073
|
+
async with TavusClient.from_env() as client:
|
|
1074
|
+
return await client.objectives.delete(objectives_id)
|
|
1075
|
+
|
|
1076
|
+
_json(_run(run()))
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
@objective_app.command("validate")
|
|
1080
|
+
def objective_validate(
|
|
1081
|
+
file: Path = typer.Option(
|
|
1082
|
+
..., "--file", help="JSON file with {name, data, allow_loops}."
|
|
1083
|
+
),
|
|
1084
|
+
) -> None:
|
|
1085
|
+
"""Validate an objective-set payload without persisting."""
|
|
1086
|
+
|
|
1087
|
+
async def run() -> Any:
|
|
1088
|
+
body = json.loads(file.read_text())
|
|
1089
|
+
async with TavusClient.from_env() as client:
|
|
1090
|
+
return await client.objectives.validate(body)
|
|
1091
|
+
|
|
1092
|
+
_json(_run(run()))
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
@objective_app.command("example")
|
|
1096
|
+
def objective_example() -> None:
|
|
1097
|
+
"""Print a starter JSON body for ``tavus objective create --file``."""
|
|
1098
|
+
_json(
|
|
1099
|
+
{
|
|
1100
|
+
"name": "support_intake",
|
|
1101
|
+
"allow_loops": False,
|
|
1102
|
+
"data": [
|
|
1103
|
+
{
|
|
1104
|
+
"objective_name": "greet",
|
|
1105
|
+
"objective_prompt": "Greet the caller and ask for their name.",
|
|
1106
|
+
"output_variables": ["caller_name"],
|
|
1107
|
+
"next_required_objective": "diagnose",
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
"objective_name": "diagnose",
|
|
1111
|
+
"objective_prompt": "Ask what problem they're calling about.",
|
|
1112
|
+
"next_conditional_objectives": {
|
|
1113
|
+
"billing": "the caller mentions billing, charges, refund",
|
|
1114
|
+
"technical": "the caller mentions an error, outage, broken feature",
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
"objective_name": "billing",
|
|
1119
|
+
"objective_prompt": "Hand off to billing support.",
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
"objective_name": "technical",
|
|
1123
|
+
"objective_prompt": "Collect error details and offer a callback.",
|
|
1124
|
+
},
|
|
1125
|
+
],
|
|
1126
|
+
}
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
@tool_app.command("list")
|
|
1131
|
+
def tool_list(
|
|
1132
|
+
limit: int = typer.Option(25),
|
|
1133
|
+
page: int = typer.Option(1),
|
|
1134
|
+
type: str = typer.Option("user", help="user | system | all"),
|
|
1135
|
+
name_or_uuid: str | None = typer.Option(None),
|
|
1136
|
+
sort: str = typer.Option("ascending"),
|
|
1137
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
1138
|
+
) -> None:
|
|
1139
|
+
"""List tools."""
|
|
1140
|
+
|
|
1141
|
+
async def run() -> Any:
|
|
1142
|
+
params: dict[str, Any] = {
|
|
1143
|
+
"limit": limit,
|
|
1144
|
+
"page": page,
|
|
1145
|
+
"type": type,
|
|
1146
|
+
"sort": sort,
|
|
1147
|
+
}
|
|
1148
|
+
if name_or_uuid:
|
|
1149
|
+
params["name_or_uuid"] = name_or_uuid
|
|
1150
|
+
async with TavusClient.from_env() as client:
|
|
1151
|
+
return await client.tools.list(**params)
|
|
1152
|
+
|
|
1153
|
+
data = _run(run())
|
|
1154
|
+
if json_output:
|
|
1155
|
+
_json(data)
|
|
1156
|
+
return
|
|
1157
|
+
_print_rows(data, ["tool_id", "name", "origin", "on_call", "updated_at"])
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
@tool_app.command("get")
|
|
1161
|
+
def tool_get(tool_id: str) -> None:
|
|
1162
|
+
async def run() -> Any:
|
|
1163
|
+
async with TavusClient.from_env() as client:
|
|
1164
|
+
return await client.tools.get(tool_id)
|
|
1165
|
+
|
|
1166
|
+
_json(_run(run()))
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
@tool_app.command("create")
|
|
1170
|
+
def tool_create(
|
|
1171
|
+
file: Path = typer.Option(
|
|
1172
|
+
...,
|
|
1173
|
+
"--file",
|
|
1174
|
+
help="JSON file with the full tool body (name/description/parameters/delivery/...).",
|
|
1175
|
+
),
|
|
1176
|
+
) -> None:
|
|
1177
|
+
"""Create a tool from a JSON file. The file mirrors the `POST /v2/tools`
|
|
1178
|
+
body shape — see ``ToolCreate`` in the SDK for fields, or
|
|
1179
|
+
``tavus tool example`` for a starter."""
|
|
1180
|
+
|
|
1181
|
+
async def run() -> Any:
|
|
1182
|
+
body = json.loads(file.read_text())
|
|
1183
|
+
async with TavusClient.from_env() as client:
|
|
1184
|
+
return await client.tools.create(body)
|
|
1185
|
+
|
|
1186
|
+
_json(_run(run()))
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
@tool_app.command("patch")
|
|
1190
|
+
def tool_patch(
|
|
1191
|
+
tool_id: str,
|
|
1192
|
+
file: Path = typer.Option(
|
|
1193
|
+
..., "--file", help="JSON file with fields to update."
|
|
1194
|
+
),
|
|
1195
|
+
) -> None:
|
|
1196
|
+
"""Patch a tool from a JSON file. RQH rejects PATCHes that echo back
|
|
1197
|
+
the scrubbed-secret placeholder (``********``) from a prior GET — omit
|
|
1198
|
+
secret fields you don't intend to change."""
|
|
1199
|
+
|
|
1200
|
+
async def run() -> Any:
|
|
1201
|
+
body = json.loads(file.read_text())
|
|
1202
|
+
async with TavusClient.from_env() as client:
|
|
1203
|
+
return await client.tools.patch(tool_id, body)
|
|
1204
|
+
|
|
1205
|
+
_json(_run(run()))
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
@tool_app.command("delete")
|
|
1209
|
+
def tool_delete(tool_id: str) -> None:
|
|
1210
|
+
async def run() -> Any:
|
|
1211
|
+
async with TavusClient.from_env() as client:
|
|
1212
|
+
return await client.tools.delete(tool_id)
|
|
1213
|
+
|
|
1214
|
+
_json(_run(run()))
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
@tool_app.command("example")
|
|
1218
|
+
def tool_example(
|
|
1219
|
+
delivery: str = typer.Option(
|
|
1220
|
+
"app_message", help="app_message | http | http_oauth2"
|
|
1221
|
+
),
|
|
1222
|
+
) -> None:
|
|
1223
|
+
"""Print a starter JSON body for ``tavus tool create --file``. Edit the
|
|
1224
|
+
output and save to a file, then pass it to ``create``."""
|
|
1225
|
+
examples = {
|
|
1226
|
+
"app_message": {
|
|
1227
|
+
"name": "weather_lookup",
|
|
1228
|
+
"description": "Get the current weather for a city.",
|
|
1229
|
+
"parameters": {
|
|
1230
|
+
"type": "object",
|
|
1231
|
+
"properties": {
|
|
1232
|
+
"city": {"type": "string", "description": "City name."}
|
|
1233
|
+
},
|
|
1234
|
+
"required": ["city"],
|
|
1235
|
+
},
|
|
1236
|
+
"delivery": {"app_message": True},
|
|
1237
|
+
"origin": "llm",
|
|
1238
|
+
"on_call": "generate_filler",
|
|
1239
|
+
"on_resolve": "generate_response",
|
|
1240
|
+
},
|
|
1241
|
+
"http": {
|
|
1242
|
+
"name": "weather_lookup",
|
|
1243
|
+
"description": "Look up current weather via a third-party HTTPS endpoint.",
|
|
1244
|
+
"parameters": {
|
|
1245
|
+
"type": "object",
|
|
1246
|
+
"properties": {
|
|
1247
|
+
"city": {"type": "string", "description": "City name."}
|
|
1248
|
+
},
|
|
1249
|
+
"required": ["city"],
|
|
1250
|
+
},
|
|
1251
|
+
"delivery": {
|
|
1252
|
+
"api": {
|
|
1253
|
+
"url": "https://api.example.com/weather/{city}",
|
|
1254
|
+
"method": "GET",
|
|
1255
|
+
"timeout": 10,
|
|
1256
|
+
"headers": {"Accept": "application/json"},
|
|
1257
|
+
"auth": {"type": "bearer", "token": "sk-..."},
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
"origin": "llm",
|
|
1261
|
+
"on_call": "generate_filler",
|
|
1262
|
+
"on_resolve": "generate_response",
|
|
1263
|
+
},
|
|
1264
|
+
"http_oauth2": {
|
|
1265
|
+
"name": "salesforce_lead_create",
|
|
1266
|
+
"description": "Create a Salesforce lead via OAuth2 client credentials.",
|
|
1267
|
+
"parameters": {
|
|
1268
|
+
"type": "object",
|
|
1269
|
+
"properties": {
|
|
1270
|
+
"email": {"type": "string"},
|
|
1271
|
+
"name": {"type": "string"},
|
|
1272
|
+
},
|
|
1273
|
+
"required": ["email", "name"],
|
|
1274
|
+
},
|
|
1275
|
+
"delivery": {
|
|
1276
|
+
"api": {
|
|
1277
|
+
"url": "https://acme.my.salesforce.com/services/data/v59.0/sobjects/Lead",
|
|
1278
|
+
"method": "POST",
|
|
1279
|
+
"body_template": {"Email": "{email}", "LastName": "{name}"},
|
|
1280
|
+
"auth": {
|
|
1281
|
+
"type": "oauth2_client_credentials",
|
|
1282
|
+
"token_url": "https://acme.my.salesforce.com/services/oauth2/token",
|
|
1283
|
+
"client_id": "...",
|
|
1284
|
+
"client_secret": "...",
|
|
1285
|
+
},
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
"origin": "llm",
|
|
1289
|
+
"on_call": "generate_filler",
|
|
1290
|
+
"on_resolve": "fire_and_forget",
|
|
1291
|
+
},
|
|
1292
|
+
}
|
|
1293
|
+
if delivery not in examples:
|
|
1294
|
+
raise typer.BadParameter(
|
|
1295
|
+
f"delivery must be one of {sorted(examples)}", param_hint="--delivery"
|
|
1296
|
+
)
|
|
1297
|
+
_json(examples[delivery])
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
@pronunciation_app.command("list")
|
|
1301
|
+
def pronunciation_list(
|
|
1302
|
+
limit: int = typer.Option(25),
|
|
1303
|
+
page: int = typer.Option(0),
|
|
1304
|
+
sort: str = typer.Option("desc"),
|
|
1305
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
1306
|
+
) -> None:
|
|
1307
|
+
"""List pronunciation dictionaries — referenced from
|
|
1308
|
+
``layers.tts.pronunciation_dictionary_id``."""
|
|
1309
|
+
|
|
1310
|
+
async def run() -> Any:
|
|
1311
|
+
async with TavusClient.from_env() as client:
|
|
1312
|
+
return await client.pronunciation_dictionaries.list(
|
|
1313
|
+
limit=limit, page=page, sort=sort
|
|
1314
|
+
)
|
|
1315
|
+
|
|
1316
|
+
data = _run(run())
|
|
1317
|
+
if json_output:
|
|
1318
|
+
_json(data)
|
|
1319
|
+
return
|
|
1320
|
+
_print_rows(data, ["uuid", "name", "updated_at"])
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
@pronunciation_app.command("get")
|
|
1324
|
+
def pronunciation_get(dictionary_id: str) -> None:
|
|
1325
|
+
async def run() -> Any:
|
|
1326
|
+
async with TavusClient.from_env() as client:
|
|
1327
|
+
return await client.pronunciation_dictionaries.get(dictionary_id)
|
|
1328
|
+
|
|
1329
|
+
_json(_run(run()))
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
@pronunciation_app.command("create")
|
|
1333
|
+
def pronunciation_create(
|
|
1334
|
+
file: Path = typer.Option(
|
|
1335
|
+
..., "--file", help="JSON file with {name, rules}."
|
|
1336
|
+
),
|
|
1337
|
+
) -> None:
|
|
1338
|
+
"""Create a pronunciation dictionary from a JSON file. Rules are
|
|
1339
|
+
``{text, pronunciation, type, alphabet?, case_sensitive?, word_boundaries?}``."""
|
|
1340
|
+
|
|
1341
|
+
async def run() -> Any:
|
|
1342
|
+
body = json.loads(file.read_text())
|
|
1343
|
+
async with TavusClient.from_env() as client:
|
|
1344
|
+
return await client.pronunciation_dictionaries.create(body)
|
|
1345
|
+
|
|
1346
|
+
_json(_run(run()))
|
|
1347
|
+
|
|
1348
|
+
|
|
1349
|
+
@pronunciation_app.command("patch")
|
|
1350
|
+
def pronunciation_patch(
|
|
1351
|
+
dictionary_id: str,
|
|
1352
|
+
file: Path = typer.Option(
|
|
1353
|
+
..., "--file", help="JSON file with fields to update."
|
|
1354
|
+
),
|
|
1355
|
+
) -> None:
|
|
1356
|
+
"""Patch a pronunciation dictionary. Supplying ``rules`` replaces the
|
|
1357
|
+
full list (RQH does not merge rule arrays)."""
|
|
1358
|
+
|
|
1359
|
+
async def run() -> Any:
|
|
1360
|
+
body = json.loads(file.read_text())
|
|
1361
|
+
async with TavusClient.from_env() as client:
|
|
1362
|
+
return await client.pronunciation_dictionaries.patch(
|
|
1363
|
+
dictionary_id, body
|
|
1364
|
+
)
|
|
1365
|
+
|
|
1366
|
+
_json(_run(run()))
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
@pronunciation_app.command("delete")
|
|
1370
|
+
def pronunciation_delete(dictionary_id: str) -> None:
|
|
1371
|
+
async def run() -> Any:
|
|
1372
|
+
async with TavusClient.from_env() as client:
|
|
1373
|
+
return await client.pronunciation_dictionaries.delete(dictionary_id)
|
|
1374
|
+
|
|
1375
|
+
_json(_run(run()))
|
|
1376
|
+
|
|
1377
|
+
|
|
1378
|
+
@pronunciation_app.command("example")
|
|
1379
|
+
def pronunciation_example() -> None:
|
|
1380
|
+
"""Print a starter JSON body for ``pronunciation-dictionary create``."""
|
|
1381
|
+
_json(
|
|
1382
|
+
{
|
|
1383
|
+
"name": "tavus-defaults",
|
|
1384
|
+
"rules": [
|
|
1385
|
+
{
|
|
1386
|
+
"text": "tavus",
|
|
1387
|
+
"pronunciation": "TAH-vus",
|
|
1388
|
+
"type": "alias",
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
"text": "kubernetes",
|
|
1392
|
+
"pronunciation": "ˌkuːbərˈnɛtɪz",
|
|
1393
|
+
"type": "ipa",
|
|
1394
|
+
},
|
|
1395
|
+
],
|
|
1396
|
+
}
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
@persona_tools_app.command("list")
|
|
1401
|
+
def persona_tools_list(persona_id: str) -> None:
|
|
1402
|
+
"""List tools attached to a persona."""
|
|
1403
|
+
|
|
1404
|
+
async def run() -> Any:
|
|
1405
|
+
async with TavusClient.from_env() as client:
|
|
1406
|
+
return await client.personas.list_tools(persona_id)
|
|
1407
|
+
|
|
1408
|
+
_json(_run(run()))
|
|
1409
|
+
|
|
1410
|
+
|
|
1411
|
+
@persona_tools_app.command("attach")
|
|
1412
|
+
def persona_tools_attach(
|
|
1413
|
+
persona_id: str,
|
|
1414
|
+
tool_ids: list[str] = typer.Argument(..., help="One or more tool_id values."),
|
|
1415
|
+
) -> None:
|
|
1416
|
+
"""Attach existing tools to a persona by tool_id."""
|
|
1417
|
+
|
|
1418
|
+
async def run() -> Any:
|
|
1419
|
+
async with TavusClient.from_env() as client:
|
|
1420
|
+
return await client.personas.attach_tools(persona_id, list(tool_ids))
|
|
1421
|
+
|
|
1422
|
+
_json(_run(run()))
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
@persona_tools_app.command("detach")
|
|
1426
|
+
def persona_tools_detach(persona_id: str, tool_id: str) -> None:
|
|
1427
|
+
"""Detach a tool from a persona."""
|
|
1428
|
+
|
|
1429
|
+
async def run() -> Any:
|
|
1430
|
+
async with TavusClient.from_env() as client:
|
|
1431
|
+
return await client.personas.detach_tool(persona_id, tool_id)
|
|
1432
|
+
|
|
1433
|
+
_json(_run(run()))
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
def _resource(client: TavusClient, resource: str) -> Any:
|
|
1437
|
+
normalized = resource.strip().lower().replace("-", "_")
|
|
1438
|
+
aliases = {
|
|
1439
|
+
"guardrail": "guardrails",
|
|
1440
|
+
"objective": "objectives",
|
|
1441
|
+
"document": "documents",
|
|
1442
|
+
"voice": "voices",
|
|
1443
|
+
"tool": "tools",
|
|
1444
|
+
}
|
|
1445
|
+
attr = aliases.get(normalized, normalized)
|
|
1446
|
+
if attr not in {"guardrails", "objectives", "documents", "voices", "tools"}:
|
|
1447
|
+
raise typer.BadParameter(
|
|
1448
|
+
"resource must be guardrails, objectives, documents, voices, or tools"
|
|
1449
|
+
)
|
|
1450
|
+
return getattr(client, attr)
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def _print_rows(data: Any, columns: list[str]) -> None:
|
|
1454
|
+
rows = data.get("data", data) if isinstance(data, dict) else data
|
|
1455
|
+
if not isinstance(rows, list):
|
|
1456
|
+
_json(data)
|
|
1457
|
+
return
|
|
1458
|
+
table = Table()
|
|
1459
|
+
for column in columns:
|
|
1460
|
+
table.add_column(column)
|
|
1461
|
+
for row in rows:
|
|
1462
|
+
table.add_row(*[str(row.get(column, "")) for column in columns])
|
|
1463
|
+
console.print(table)
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
if __name__ == "__main__":
|
|
1467
|
+
app()
|