deepflow-voicespeed-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.
- deepflow_voicespeed_cli/__init__.py +3 -0
- deepflow_voicespeed_cli/cli.py +181 -0
- deepflow_voicespeed_cli/client.py +142 -0
- deepflow_voicespeed_cli-0.1.0.dist-info/METADATA +42 -0
- deepflow_voicespeed_cli-0.1.0.dist-info/RECORD +8 -0
- deepflow_voicespeed_cli-0.1.0.dist-info/WHEEL +5 -0
- deepflow_voicespeed_cli-0.1.0.dist-info/entry_points.txt +2 -0
- deepflow_voicespeed_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any, Sequence
|
|
8
|
+
|
|
9
|
+
from .client import VoiceSpeedApiError, VoiceSpeedClient, VoiceSpeedRequestError
|
|
10
|
+
|
|
11
|
+
DEFAULT_BASE_URL = "http://localhost:5002"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CliError(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
19
|
+
parser = argparse.ArgumentParser(prog="voicespeed")
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--base-url",
|
|
22
|
+
default=os.environ.get("VOICESPEED_BASE_URL", DEFAULT_BASE_URL),
|
|
23
|
+
help="VoiceSpeed server base URL. Defaults to VOICESPEED_BASE_URL or localhost.",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--api-key",
|
|
27
|
+
default=os.environ.get("VOICESPEED_API_KEY"),
|
|
28
|
+
help="VoiceSpeed OpenAPI key. Defaults to VOICESPEED_API_KEY.",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
subparsers = parser.add_subparsers(dest="resource", required=True)
|
|
32
|
+
_add_agent_commands(subparsers)
|
|
33
|
+
_add_conversation_commands(subparsers)
|
|
34
|
+
return parser
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _add_agent_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
38
|
+
agents = subparsers.add_parser("agents", help="Manage OpenAPI agents")
|
|
39
|
+
actions = agents.add_subparsers(dest="action", required=True)
|
|
40
|
+
|
|
41
|
+
list_parser = actions.add_parser("list", help="List agents")
|
|
42
|
+
list_parser.add_argument("--status", choices=["enabled", "disabled"])
|
|
43
|
+
list_parser.add_argument("--page", type=int, default=1)
|
|
44
|
+
list_parser.add_argument("--per-page", type=int, default=20)
|
|
45
|
+
list_parser.set_defaults(handler=_handle_agents_list)
|
|
46
|
+
|
|
47
|
+
create_parser = actions.add_parser("create", help="Create an agent")
|
|
48
|
+
create_parser.add_argument("--name", required=True)
|
|
49
|
+
create_parser.add_argument("--description")
|
|
50
|
+
create_parser.set_defaults(handler=_handle_agents_create)
|
|
51
|
+
|
|
52
|
+
update_parser = actions.add_parser("update", help="Update an agent")
|
|
53
|
+
update_parser.add_argument("agent_id", type=int)
|
|
54
|
+
update_parser.add_argument("--name")
|
|
55
|
+
update_parser.add_argument("--description")
|
|
56
|
+
update_parser.set_defaults(handler=_handle_agents_update)
|
|
57
|
+
|
|
58
|
+
disable_parser = actions.add_parser("disable", help="Disable an agent")
|
|
59
|
+
disable_parser.add_argument("agent_id", type=int)
|
|
60
|
+
disable_parser.set_defaults(handler=_handle_agents_disable)
|
|
61
|
+
|
|
62
|
+
enable_parser = actions.add_parser("enable", help="Enable an agent")
|
|
63
|
+
enable_parser.add_argument("agent_id", type=int)
|
|
64
|
+
enable_parser.set_defaults(handler=_handle_agents_enable)
|
|
65
|
+
|
|
66
|
+
settings_parser = actions.add_parser(
|
|
67
|
+
"basic-settings",
|
|
68
|
+
help="Get agent basic engine settings",
|
|
69
|
+
)
|
|
70
|
+
settings_parser.add_argument("agent_id", type=int)
|
|
71
|
+
settings_parser.set_defaults(handler=_handle_agents_basic_settings)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _add_conversation_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
75
|
+
conversation = subparsers.add_parser("conversation", help="Run OpenAPI chat")
|
|
76
|
+
actions = conversation.add_subparsers(dest="action", required=True)
|
|
77
|
+
|
|
78
|
+
run_parser = actions.add_parser("run", help="Run one conversation turn")
|
|
79
|
+
run_parser.add_argument("agent_id", type=int)
|
|
80
|
+
run_parser.add_argument("--message", required=True)
|
|
81
|
+
run_parser.add_argument("--mode", choices=["text2text", "text2voice"], default="text2text")
|
|
82
|
+
run_parser.set_defaults(handler=_handle_conversation_run)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
86
|
+
parser = build_parser()
|
|
87
|
+
args = parser.parse_args(argv)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
client = _build_client(args)
|
|
91
|
+
result = args.handler(client, args)
|
|
92
|
+
_print_json(result)
|
|
93
|
+
return 0
|
|
94
|
+
except CliError as exc:
|
|
95
|
+
print(str(exc), file=sys.stderr)
|
|
96
|
+
return 1
|
|
97
|
+
except VoiceSpeedApiError as exc:
|
|
98
|
+
print(f"HTTP {exc.status_code}: {exc.body}", file=sys.stderr)
|
|
99
|
+
return 1
|
|
100
|
+
except VoiceSpeedRequestError as exc:
|
|
101
|
+
print(f"Request failed: {exc}", file=sys.stderr)
|
|
102
|
+
return 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _build_client(args: argparse.Namespace) -> VoiceSpeedClient:
|
|
106
|
+
if not args.api_key:
|
|
107
|
+
raise CliError("Missing API key. Use --api-key or VOICESPEED_API_KEY.")
|
|
108
|
+
return VoiceSpeedClient(base_url=args.base_url, api_key=args.api_key)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _print_json(value: Any) -> None:
|
|
112
|
+
if value is None:
|
|
113
|
+
return
|
|
114
|
+
print(json.dumps(value, ensure_ascii=False, indent=2))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _handle_agents_list(
|
|
118
|
+
client: VoiceSpeedClient,
|
|
119
|
+
args: argparse.Namespace,
|
|
120
|
+
) -> Any:
|
|
121
|
+
return client.list_agents(
|
|
122
|
+
status=args.status,
|
|
123
|
+
page=args.page,
|
|
124
|
+
per_page=args.per_page,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _handle_agents_create(
|
|
129
|
+
client: VoiceSpeedClient,
|
|
130
|
+
args: argparse.Namespace,
|
|
131
|
+
) -> Any:
|
|
132
|
+
return client.create_agent(name=args.name, description=args.description)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _handle_agents_update(
|
|
136
|
+
client: VoiceSpeedClient,
|
|
137
|
+
args: argparse.Namespace,
|
|
138
|
+
) -> Any:
|
|
139
|
+
if args.name is None and args.description is None:
|
|
140
|
+
raise CliError("At least one of --name or --description is required.")
|
|
141
|
+
return client.update_agent(
|
|
142
|
+
args.agent_id,
|
|
143
|
+
name=args.name,
|
|
144
|
+
description=args.description,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _handle_agents_disable(
|
|
149
|
+
client: VoiceSpeedClient,
|
|
150
|
+
args: argparse.Namespace,
|
|
151
|
+
) -> Any:
|
|
152
|
+
return client.set_agent_enabled(args.agent_id, enabled=False)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _handle_agents_enable(
|
|
156
|
+
client: VoiceSpeedClient,
|
|
157
|
+
args: argparse.Namespace,
|
|
158
|
+
) -> Any:
|
|
159
|
+
return client.set_agent_enabled(args.agent_id, enabled=True)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _handle_agents_basic_settings(
|
|
163
|
+
client: VoiceSpeedClient,
|
|
164
|
+
args: argparse.Namespace,
|
|
165
|
+
) -> Any:
|
|
166
|
+
return client.get_basic_settings(args.agent_id)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _handle_conversation_run(
|
|
170
|
+
client: VoiceSpeedClient,
|
|
171
|
+
args: argparse.Namespace,
|
|
172
|
+
) -> Any:
|
|
173
|
+
return client.run_conversation(
|
|
174
|
+
args.agent_id,
|
|
175
|
+
message=args.message,
|
|
176
|
+
mode=args.mode,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.error import HTTPError, URLError
|
|
7
|
+
from urllib.parse import urlencode
|
|
8
|
+
from urllib.request import Request, urlopen
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VoiceSpeedClientError(Exception):
|
|
12
|
+
"""Base error raised by the VoiceSpeed client."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VoiceSpeedApiError(VoiceSpeedClientError):
|
|
16
|
+
def __init__(self, status_code: int, body: str) -> None:
|
|
17
|
+
self.status_code = status_code
|
|
18
|
+
self.body = body
|
|
19
|
+
super().__init__(f"HTTP {status_code}: {body}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class VoiceSpeedRequestError(VoiceSpeedClientError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class VoiceSpeedClient:
|
|
28
|
+
base_url: str
|
|
29
|
+
api_key: str
|
|
30
|
+
timeout: float = 30.0
|
|
31
|
+
|
|
32
|
+
def __post_init__(self) -> None:
|
|
33
|
+
self.base_url = self.base_url.rstrip("/")
|
|
34
|
+
self.api_key = self.api_key.strip()
|
|
35
|
+
if not self.api_key:
|
|
36
|
+
raise ValueError("Missing API key")
|
|
37
|
+
|
|
38
|
+
def request(
|
|
39
|
+
self,
|
|
40
|
+
method: str,
|
|
41
|
+
path: str,
|
|
42
|
+
*,
|
|
43
|
+
query: dict[str, Any] | None = None,
|
|
44
|
+
payload: dict[str, Any] | None = None,
|
|
45
|
+
) -> Any:
|
|
46
|
+
url = f"{self.base_url}/api/v1/openapi{path}"
|
|
47
|
+
clean_query = {
|
|
48
|
+
key: value for key, value in (query or {}).items() if value is not None
|
|
49
|
+
}
|
|
50
|
+
if clean_query:
|
|
51
|
+
url = f"{url}?{urlencode(clean_query)}"
|
|
52
|
+
|
|
53
|
+
body = None
|
|
54
|
+
headers = {
|
|
55
|
+
"Accept": "application/json",
|
|
56
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
57
|
+
}
|
|
58
|
+
if payload is not None:
|
|
59
|
+
body = json.dumps(payload).encode("utf-8")
|
|
60
|
+
headers["Content-Type"] = "application/json"
|
|
61
|
+
|
|
62
|
+
req = Request(url, data=body, headers=headers, method=method.upper())
|
|
63
|
+
try:
|
|
64
|
+
with urlopen(req, timeout=self.timeout) as resp:
|
|
65
|
+
return self._parse_response(resp.read())
|
|
66
|
+
except HTTPError as exc:
|
|
67
|
+
raise VoiceSpeedApiError(exc.code, self._read_error_body(exc)) from exc
|
|
68
|
+
except URLError as exc:
|
|
69
|
+
raise VoiceSpeedRequestError(str(exc.reason)) from exc
|
|
70
|
+
except OSError as exc:
|
|
71
|
+
raise VoiceSpeedRequestError(str(exc)) from exc
|
|
72
|
+
|
|
73
|
+
def list_agents(
|
|
74
|
+
self,
|
|
75
|
+
*,
|
|
76
|
+
status: str | None = None,
|
|
77
|
+
page: int = 1,
|
|
78
|
+
per_page: int = 20,
|
|
79
|
+
) -> Any:
|
|
80
|
+
return self.request(
|
|
81
|
+
"GET",
|
|
82
|
+
"/agents",
|
|
83
|
+
query={"status": status, "page": page, "per_page": per_page},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def create_agent(self, *, name: str, description: str | None = None) -> Any:
|
|
87
|
+
return self.request(
|
|
88
|
+
"POST",
|
|
89
|
+
"/agents",
|
|
90
|
+
payload={"name": name, "description": description},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def update_agent(
|
|
94
|
+
self,
|
|
95
|
+
agent_id: int,
|
|
96
|
+
*,
|
|
97
|
+
name: str | None = None,
|
|
98
|
+
description: str | None = None,
|
|
99
|
+
) -> Any:
|
|
100
|
+
payload = {
|
|
101
|
+
key: value
|
|
102
|
+
for key, value in {"name": name, "description": description}.items()
|
|
103
|
+
if value is not None
|
|
104
|
+
}
|
|
105
|
+
return self.request("PATCH", f"/agents/{agent_id}", payload=payload)
|
|
106
|
+
|
|
107
|
+
def set_agent_enabled(self, agent_id: int, *, enabled: bool) -> Any:
|
|
108
|
+
action = "enable" if enabled else "disable"
|
|
109
|
+
return self.request("POST", f"/agents/{agent_id}/{action}")
|
|
110
|
+
|
|
111
|
+
def get_basic_settings(self, agent_id: int) -> Any:
|
|
112
|
+
return self.request("GET", f"/agents/{agent_id}/basic-settings")
|
|
113
|
+
|
|
114
|
+
def run_conversation(
|
|
115
|
+
self,
|
|
116
|
+
agent_id: int,
|
|
117
|
+
*,
|
|
118
|
+
message: str,
|
|
119
|
+
mode: str = "text2text",
|
|
120
|
+
) -> Any:
|
|
121
|
+
return self.request(
|
|
122
|
+
"POST",
|
|
123
|
+
f"/agents/{agent_id}/conversation",
|
|
124
|
+
payload={"message": message, "mode": mode},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _parse_response(data: bytes) -> Any:
|
|
129
|
+
if not data:
|
|
130
|
+
return None
|
|
131
|
+
text = data.decode("utf-8")
|
|
132
|
+
try:
|
|
133
|
+
return json.loads(text)
|
|
134
|
+
except json.JSONDecodeError:
|
|
135
|
+
return text
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _read_error_body(exc: HTTPError) -> str:
|
|
139
|
+
body = exc.read()
|
|
140
|
+
if not body:
|
|
141
|
+
return exc.reason
|
|
142
|
+
return body.decode("utf-8", errors="replace")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepflow-voicespeed-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command line client for VoiceSpeed OpenAPI
|
|
5
|
+
Home-page: https://github.com/DeepFlowAI/voicespeed
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Keywords: voicespeed,deepflow,cli,openapi
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# deepflow-voicespeed-cli
|
|
12
|
+
|
|
13
|
+
Command line client for VoiceSpeed OpenAPI.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install deepflow-voicespeed-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Configure
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export VOICESPEED_BASE_URL=http://localhost:5002
|
|
25
|
+
export VOICESPEED_API_KEY=YOUR_API_KEY
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Both values can also be passed with `--base-url` and `--api-key`.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
voicespeed agents list --status enabled
|
|
34
|
+
voicespeed agents create --name "Support" --description "Demo agent"
|
|
35
|
+
voicespeed agents update 1 --name "Support Pro"
|
|
36
|
+
voicespeed agents disable 1
|
|
37
|
+
voicespeed agents enable 1
|
|
38
|
+
voicespeed agents basic-settings 1
|
|
39
|
+
voicespeed conversation run 1 --message "Hello" --mode text2text
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
All successful commands print formatted JSON.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
deepflow_voicespeed_cli/__init__.py,sha256=FTA-PNbav1HDBMe-WNKrW53T1L5FK_jQ_XXcYHfZhco,69
|
|
2
|
+
deepflow_voicespeed_cli/cli.py,sha256=K3Kiy6Qecr7svbVBLMTNXqMHH3pOHHfTjtXQUIwv-wU,5698
|
|
3
|
+
deepflow_voicespeed_cli/client.py,sha256=03UFuwQa6mAsZjIH_w0NZp21XSzA6_2xnQqAm2anikA,4191
|
|
4
|
+
deepflow_voicespeed_cli-0.1.0.dist-info/METADATA,sha256=1T_z-WEpT_C67VNsJKwJxawifwJ9xBz1fNHx52144S4,995
|
|
5
|
+
deepflow_voicespeed_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
deepflow_voicespeed_cli-0.1.0.dist-info/entry_points.txt,sha256=865sc9SU_pNJhazfQNnO_Ch5xD7ZZVRCP7vLJQEmi-k,64
|
|
7
|
+
deepflow_voicespeed_cli-0.1.0.dist-info/top_level.txt,sha256=kUuQDQ_5K-JlkryPqCDbKmBXB1jJ0V6ogRVHVsNDE2c,24
|
|
8
|
+
deepflow_voicespeed_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepflow_voicespeed_cli
|