blaxel 0.64.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.
- blaxel/__init__.py +8 -0
- blaxel/agents/__init__.py +5 -0
- blaxel/agents/chain.py +153 -0
- blaxel/agents/chat.py +286 -0
- blaxel/agents/decorator.py +208 -0
- blaxel/agents/thread.py +24 -0
- blaxel/agents/voice/openai.py +255 -0
- blaxel/agents/voice/utils.py +25 -0
- blaxel/api/__init__.py +1 -0
- blaxel/api/agents/__init__.py +0 -0
- blaxel/api/agents/create_agent.py +155 -0
- blaxel/api/agents/delete_agent.py +146 -0
- blaxel/api/agents/get_agent.py +146 -0
- blaxel/api/agents/get_agent_logs.py +151 -0
- blaxel/api/agents/get_agent_metrics.py +150 -0
- blaxel/api/agents/get_agent_trace_ids.py +201 -0
- blaxel/api/agents/list_agent_revisions.py +155 -0
- blaxel/api/agents/list_agents.py +127 -0
- blaxel/api/agents/update_agent.py +168 -0
- blaxel/api/configurations/__init__.py +0 -0
- blaxel/api/configurations/get_configuration.py +122 -0
- blaxel/api/default/__init__.py +0 -0
- blaxel/api/default/get_trace.py +150 -0
- blaxel/api/default/get_trace_ids.py +218 -0
- blaxel/api/default/get_trace_logs.py +186 -0
- blaxel/api/default/list_mcp_hub_definitions.py +127 -0
- blaxel/api/functions/__init__.py +0 -0
- blaxel/api/functions/create_function.py +155 -0
- blaxel/api/functions/delete_function.py +146 -0
- blaxel/api/functions/get_function.py +146 -0
- blaxel/api/functions/get_function_logs.py +151 -0
- blaxel/api/functions/get_function_metrics.py +150 -0
- blaxel/api/functions/get_function_trace_ids.py +201 -0
- blaxel/api/functions/list_function_revisions.py +158 -0
- blaxel/api/functions/list_functions.py +131 -0
- blaxel/api/functions/update_function.py +168 -0
- blaxel/api/integrations/__init__.py +0 -0
- blaxel/api/integrations/create_integration_connection.py +167 -0
- blaxel/api/integrations/delete_integration_connection.py +158 -0
- blaxel/api/integrations/get_integration.py +97 -0
- blaxel/api/integrations/get_integration_connection.py +158 -0
- blaxel/api/integrations/get_integration_connection_model.py +104 -0
- blaxel/api/integrations/get_integration_connection_model_endpoint_configurations.py +97 -0
- blaxel/api/integrations/list_integration_connection_models.py +97 -0
- blaxel/api/integrations/list_integration_connections.py +139 -0
- blaxel/api/integrations/update_integration_connection.py +180 -0
- blaxel/api/invitations/__init__.py +0 -0
- blaxel/api/invitations/list_all_pending_invitations.py +142 -0
- blaxel/api/knowledgebases/__init__.py +0 -0
- blaxel/api/knowledgebases/create_knowledgebase.py +163 -0
- blaxel/api/knowledgebases/delete_knowledgebase.py +154 -0
- blaxel/api/knowledgebases/get_knowledgebase.py +154 -0
- blaxel/api/knowledgebases/list_knowledgebase_revisions.py +158 -0
- blaxel/api/knowledgebases/list_knowledgebases.py +139 -0
- blaxel/api/knowledgebases/update_knowledgebase.py +176 -0
- blaxel/api/locations/__init__.py +0 -0
- blaxel/api/locations/list_locations.py +139 -0
- blaxel/api/metrics/__init__.py +0 -0
- blaxel/api/metrics/get_metrics.py +130 -0
- blaxel/api/models/__init__.py +0 -0
- blaxel/api/models/create_model.py +163 -0
- blaxel/api/models/delete_model.py +154 -0
- blaxel/api/models/get_model.py +154 -0
- blaxel/api/models/get_model_logs.py +155 -0
- blaxel/api/models/get_model_metrics.py +158 -0
- blaxel/api/models/get_model_trace_ids.py +201 -0
- blaxel/api/models/list_model_revisions.py +158 -0
- blaxel/api/models/list_models.py +135 -0
- blaxel/api/models/update_model.py +176 -0
- blaxel/api/policies/__init__.py +0 -0
- blaxel/api/policies/create_policy.py +167 -0
- blaxel/api/policies/delete_policy.py +154 -0
- blaxel/api/policies/get_policy.py +154 -0
- blaxel/api/policies/list_policies.py +139 -0
- blaxel/api/policies/update_policy.py +180 -0
- blaxel/api/privateclusters/__init__.py +0 -0
- blaxel/api/privateclusters/create_private_cluster.py +132 -0
- blaxel/api/privateclusters/delete_private_cluster.py +156 -0
- blaxel/api/privateclusters/get_private_cluster.py +159 -0
- blaxel/api/privateclusters/get_private_cluster_health.py +97 -0
- blaxel/api/privateclusters/list_private_clusters.py +140 -0
- blaxel/api/privateclusters/update_private_cluster.py +156 -0
- blaxel/api/privateclusters/update_private_cluster_health.py +97 -0
- blaxel/api/service_accounts/__init__.py +0 -0
- blaxel/api/service_accounts/create_api_key_for_service_account.py +177 -0
- blaxel/api/service_accounts/create_workspace_service_account.py +170 -0
- blaxel/api/service_accounts/delete_api_key_for_service_account.py +104 -0
- blaxel/api/service_accounts/delete_workspace_service_account.py +160 -0
- blaxel/api/service_accounts/get_workspace_service_accounts.py +141 -0
- blaxel/api/service_accounts/list_api_keys_for_service_account.py +163 -0
- blaxel/api/service_accounts/update_workspace_service_account.py +183 -0
- blaxel/api/store/__init__.py +0 -0
- blaxel/api/store/get_store_agent.py +146 -0
- blaxel/api/store/get_store_function.py +146 -0
- blaxel/api/store/list_store_agents.py +131 -0
- blaxel/api/store/list_store_functions.py +131 -0
- blaxel/api/workspaces/__init__.py +0 -0
- blaxel/api/workspaces/accept_workspace_invitation.py +161 -0
- blaxel/api/workspaces/create_worspace.py +163 -0
- blaxel/api/workspaces/decline_workspace_invitation.py +158 -0
- blaxel/api/workspaces/delete_workspace.py +154 -0
- blaxel/api/workspaces/get_workspace.py +154 -0
- blaxel/api/workspaces/invite_workspace_user.py +174 -0
- blaxel/api/workspaces/leave_workspace.py +161 -0
- blaxel/api/workspaces/list_workspace_users.py +139 -0
- blaxel/api/workspaces/list_workspaces.py +139 -0
- blaxel/api/workspaces/remove_workspace_user.py +101 -0
- blaxel/api/workspaces/update_workspace.py +176 -0
- blaxel/api/workspaces/update_workspace_user_role.py +187 -0
- blaxel/authentication/__init__.py +45 -0
- blaxel/authentication/apikey.py +50 -0
- blaxel/authentication/authentication.py +176 -0
- blaxel/authentication/clientcredentials.py +103 -0
- blaxel/authentication/credentials.py +295 -0
- blaxel/authentication/device_mode.py +197 -0
- blaxel/client.py +281 -0
- blaxel/common/__init__.py +17 -0
- blaxel/common/error.py +27 -0
- blaxel/common/instrumentation.py +317 -0
- blaxel/common/logger.py +60 -0
- blaxel/common/secrets.py +39 -0
- blaxel/common/settings.py +150 -0
- blaxel/common/slugify.py +18 -0
- blaxel/common/utils.py +34 -0
- blaxel/deploy/__init__.py +8 -0
- blaxel/deploy/deploy.py +316 -0
- blaxel/deploy/format.py +46 -0
- blaxel/deploy/parser.py +192 -0
- blaxel/errors.py +16 -0
- blaxel/functions/__init__.py +7 -0
- blaxel/functions/common.py +228 -0
- blaxel/functions/decorator.py +64 -0
- blaxel/functions/local/local.py +48 -0
- blaxel/functions/mcp/client.py +96 -0
- blaxel/functions/mcp/mcp.py +168 -0
- blaxel/functions/mcp/utils.py +56 -0
- blaxel/functions/remote/remote.py +183 -0
- blaxel/models/__init__.py +233 -0
- blaxel/models/acl.py +133 -0
- blaxel/models/agent.py +126 -0
- blaxel/models/agent_chain.py +88 -0
- blaxel/models/agent_spec.py +346 -0
- blaxel/models/api_key.py +142 -0
- blaxel/models/configuration.py +85 -0
- blaxel/models/continent.py +70 -0
- blaxel/models/core_event.py +97 -0
- blaxel/models/core_spec.py +249 -0
- blaxel/models/core_spec_configurations.py +77 -0
- blaxel/models/country.py +70 -0
- blaxel/models/create_api_key_for_service_account_body.py +69 -0
- blaxel/models/create_workspace_service_account_body.py +71 -0
- blaxel/models/create_workspace_service_account_response_200.py +105 -0
- blaxel/models/delete_workspace_service_account_response_200.py +96 -0
- blaxel/models/entrypoint.py +96 -0
- blaxel/models/entrypoint_env.py +45 -0
- blaxel/models/flavor.py +70 -0
- blaxel/models/form.py +120 -0
- blaxel/models/form_config.py +45 -0
- blaxel/models/form_oauthomitempty.py +45 -0
- blaxel/models/form_secrets.py +45 -0
- blaxel/models/function.py +126 -0
- blaxel/models/function_kit.py +97 -0
- blaxel/models/function_spec.py +310 -0
- blaxel/models/get_trace_ids_response_200.py +45 -0
- blaxel/models/get_trace_logs_response_200.py +45 -0
- blaxel/models/get_trace_response_200.py +45 -0
- blaxel/models/get_workspace_service_accounts_response_200_item.py +96 -0
- blaxel/models/histogram_bucket.py +79 -0
- blaxel/models/histogram_stats.py +88 -0
- blaxel/models/integration_connection.py +96 -0
- blaxel/models/integration_connection_spec.py +114 -0
- blaxel/models/integration_connection_spec_config.py +45 -0
- blaxel/models/integration_connection_spec_secret.py +45 -0
- blaxel/models/integration_model.py +162 -0
- blaxel/models/integration_repository.py +88 -0
- blaxel/models/invite_workspace_user_body.py +60 -0
- blaxel/models/knowledgebase.py +126 -0
- blaxel/models/knowledgebase_spec.py +163 -0
- blaxel/models/knowledgebase_spec_options.py +45 -0
- blaxel/models/last_n_requests_metric.py +79 -0
- blaxel/models/latency_metric.py +144 -0
- blaxel/models/location_response.py +113 -0
- blaxel/models/mcp_definition.py +188 -0
- blaxel/models/mcp_definition_entrypoint.py +45 -0
- blaxel/models/mcp_definition_form.py +45 -0
- blaxel/models/metadata.py +139 -0
- blaxel/models/metadata_labels.py +45 -0
- blaxel/models/metric.py +79 -0
- blaxel/models/metrics.py +169 -0
- blaxel/models/metrics_models.py +45 -0
- blaxel/models/metrics_request_total_per_code.py +45 -0
- blaxel/models/metrics_rps_per_code.py +45 -0
- blaxel/models/model.py +126 -0
- blaxel/models/model_private_cluster.py +79 -0
- blaxel/models/model_spec.py +249 -0
- blaxel/models/o_auth.py +72 -0
- blaxel/models/owner_fields.py +70 -0
- blaxel/models/pending_invitation.py +124 -0
- blaxel/models/pending_invitation_accept.py +85 -0
- blaxel/models/pending_invitation_render.py +147 -0
- blaxel/models/pending_invitation_render_invited_by.py +88 -0
- blaxel/models/pending_invitation_render_workspace.py +70 -0
- blaxel/models/pending_invitation_workspace_details.py +72 -0
- blaxel/models/pod_template_spec.py +45 -0
- blaxel/models/policy.py +96 -0
- blaxel/models/policy_location.py +70 -0
- blaxel/models/policy_max_tokens.py +106 -0
- blaxel/models/policy_spec.py +151 -0
- blaxel/models/private_cluster.py +183 -0
- blaxel/models/private_location.py +61 -0
- blaxel/models/repository.py +70 -0
- blaxel/models/request_duration_over_time_metric.py +97 -0
- blaxel/models/request_duration_over_time_metrics.py +80 -0
- blaxel/models/request_total_by_origin_metric.py +115 -0
- blaxel/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
- blaxel/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
- blaxel/models/request_total_metric.py +123 -0
- blaxel/models/request_total_metric_request_total_per_code.py +45 -0
- blaxel/models/request_total_metric_rps_per_code.py +45 -0
- blaxel/models/resource_log.py +79 -0
- blaxel/models/resource_metrics.py +270 -0
- blaxel/models/resource_metrics_request_total_per_code.py +45 -0
- blaxel/models/resource_metrics_rps_per_code.py +45 -0
- blaxel/models/revision_configuration.py +97 -0
- blaxel/models/revision_metadata.py +124 -0
- blaxel/models/runtime.py +196 -0
- blaxel/models/runtime_startup_probe.py +45 -0
- blaxel/models/serverless_config.py +80 -0
- blaxel/models/spec_configuration.py +70 -0
- blaxel/models/store_agent.py +178 -0
- blaxel/models/store_agent_labels.py +45 -0
- blaxel/models/store_configuration.py +151 -0
- blaxel/models/store_configuration_option.py +79 -0
- blaxel/models/store_function.py +211 -0
- blaxel/models/store_function_kit.py +97 -0
- blaxel/models/store_function_labels.py +45 -0
- blaxel/models/store_function_parameter.py +88 -0
- blaxel/models/time_fields.py +70 -0
- blaxel/models/token_rate_metric.py +88 -0
- blaxel/models/token_rate_metrics.py +120 -0
- blaxel/models/token_total_metric.py +106 -0
- blaxel/models/trace_ids_response.py +45 -0
- blaxel/models/update_workspace_service_account_body.py +69 -0
- blaxel/models/update_workspace_service_account_response_200.py +96 -0
- blaxel/models/update_workspace_user_role_body.py +60 -0
- blaxel/models/websocket_channel.py +88 -0
- blaxel/models/workspace.py +148 -0
- blaxel/models/workspace_labels.py +45 -0
- blaxel/models/workspace_user.py +115 -0
- blaxel/py.typed +1 -0
- blaxel/run.py +108 -0
- blaxel/serve/app.py +131 -0
- blaxel/serve/middlewares/__init__.py +10 -0
- blaxel/serve/middlewares/accesslog.py +32 -0
- blaxel/serve/middlewares/processtime.py +28 -0
- blaxel/types.py +46 -0
- blaxel-0.64.0.dist-info/METADATA +96 -0
- blaxel-0.64.0.dist-info/RECORD +261 -0
- blaxel-0.64.0.dist-info/WHEEL +4 -0
- blaxel-0.64.0.dist-info/entry_points.txt +2 -0
- blaxel-0.64.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
import asyncio
|
2
|
+
import json
|
3
|
+
from contextlib import asynccontextmanager
|
4
|
+
from typing import Any, AsyncGenerator, AsyncIterator, Callable, Coroutine
|
5
|
+
|
6
|
+
import websockets
|
7
|
+
from langchain_core._api import beta
|
8
|
+
from langchain_core.tools import BaseTool
|
9
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
10
|
+
|
11
|
+
from .utils import amerge
|
12
|
+
|
13
|
+
EVENTS_TO_IGNORE = {
|
14
|
+
"response.function_call_arguments.delta",
|
15
|
+
"rate_limits.updated",
|
16
|
+
"response.audio_transcript.delta",
|
17
|
+
"response.created",
|
18
|
+
"response.content_part.added",
|
19
|
+
"response.content_part.done",
|
20
|
+
"conversation.item.created",
|
21
|
+
"response.audio.done",
|
22
|
+
"session.created",
|
23
|
+
"session.updated",
|
24
|
+
"response.done",
|
25
|
+
"response.output_item.done",
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
@asynccontextmanager
|
30
|
+
async def connect(*, headers: dict[str, Any], model: str, url: str) -> AsyncGenerator[
|
31
|
+
tuple[
|
32
|
+
Callable[[dict[str, Any] | str], Coroutine[Any, Any, None]],
|
33
|
+
AsyncIterator[dict[str, Any]],
|
34
|
+
],
|
35
|
+
None,
|
36
|
+
]:
|
37
|
+
"""
|
38
|
+
async with connect(model="gpt-4o-realtime-preview-2024-10-01") as websocket:
|
39
|
+
await websocket.send("Hello, world!")
|
40
|
+
async for message in websocket:
|
41
|
+
print(message)
|
42
|
+
"""
|
43
|
+
url += f"?model={model}"
|
44
|
+
|
45
|
+
websocket = await websockets.connect(url, extra_headers={**headers, "OpenAI-Beta": "realtime=v1"})
|
46
|
+
|
47
|
+
try:
|
48
|
+
|
49
|
+
async def send_event(event: dict[str, Any] | str) -> None:
|
50
|
+
formatted_event = json.dumps(event) if isinstance(event, dict) else event
|
51
|
+
await websocket.send(formatted_event)
|
52
|
+
|
53
|
+
async def event_stream() -> AsyncIterator[dict[str, Any]]:
|
54
|
+
async for raw_event in websocket:
|
55
|
+
yield json.loads(raw_event)
|
56
|
+
|
57
|
+
stream: AsyncIterator[dict[str, Any]] = event_stream()
|
58
|
+
|
59
|
+
yield send_event, stream
|
60
|
+
finally:
|
61
|
+
await websocket.close()
|
62
|
+
|
63
|
+
|
64
|
+
class VoiceToolExecutor(BaseModel):
|
65
|
+
"""
|
66
|
+
Can accept function calls and emits function call outputs to a stream.
|
67
|
+
"""
|
68
|
+
|
69
|
+
tools_by_name: dict[str, BaseTool]
|
70
|
+
_trigger_future: asyncio.Future = PrivateAttr(default_factory=asyncio.Future)
|
71
|
+
_lock: asyncio.Lock = PrivateAttr(default_factory=asyncio.Lock)
|
72
|
+
|
73
|
+
async def _trigger_func(self) -> dict: # returns a tool call
|
74
|
+
return await self._trigger_future
|
75
|
+
|
76
|
+
async def add_tool_call(self, tool_call: dict) -> None:
|
77
|
+
# lock to avoid simultaneous tool calls racing and missing
|
78
|
+
# _trigger_future being
|
79
|
+
async with self._lock:
|
80
|
+
if self._trigger_future.done():
|
81
|
+
# TODO: handle simultaneous tool calls better
|
82
|
+
raise ValueError("Tool call adding already in progress")
|
83
|
+
|
84
|
+
self._trigger_future.set_result(tool_call)
|
85
|
+
|
86
|
+
async def _create_tool_call_task(self, tool_call: dict) -> asyncio.Task[dict]:
|
87
|
+
tool = self.tools_by_name.get(tool_call["name"])
|
88
|
+
if tool is None:
|
89
|
+
# immediately yield error, do not add task
|
90
|
+
raise ValueError(
|
91
|
+
f"tool {tool_call['name']} not found. "
|
92
|
+
f"Must be one of {list(self.tools_by_name.keys())}"
|
93
|
+
)
|
94
|
+
|
95
|
+
# try to parse args
|
96
|
+
try:
|
97
|
+
args = json.loads(tool_call["arguments"])
|
98
|
+
except json.JSONDecodeError:
|
99
|
+
raise ValueError(
|
100
|
+
f"failed to parse arguments `{tool_call['arguments']}`. Must be valid JSON."
|
101
|
+
)
|
102
|
+
|
103
|
+
async def run_tool() -> dict:
|
104
|
+
result = await tool.ainvoke(args)
|
105
|
+
try:
|
106
|
+
result_str = json.dumps(result)
|
107
|
+
except TypeError:
|
108
|
+
# not json serializable, use str
|
109
|
+
result_str = str(result)
|
110
|
+
return {
|
111
|
+
"type": "conversation.item.create",
|
112
|
+
"item": {
|
113
|
+
"id": tool_call["call_id"],
|
114
|
+
"call_id": tool_call["call_id"],
|
115
|
+
"type": "function_call_output",
|
116
|
+
"output": result_str,
|
117
|
+
},
|
118
|
+
}
|
119
|
+
|
120
|
+
task = asyncio.create_task(run_tool())
|
121
|
+
return task
|
122
|
+
|
123
|
+
async def output_iterator(self) -> AsyncIterator[dict]: # yield events
|
124
|
+
trigger_task = asyncio.create_task(self._trigger_func())
|
125
|
+
tasks = set([trigger_task])
|
126
|
+
while True:
|
127
|
+
done, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
128
|
+
for task in done:
|
129
|
+
tasks.remove(task)
|
130
|
+
if task == trigger_task:
|
131
|
+
async with self._lock:
|
132
|
+
self._trigger_future = asyncio.Future()
|
133
|
+
trigger_task = asyncio.create_task(self._trigger_func())
|
134
|
+
tasks.add(trigger_task)
|
135
|
+
tool_call = task.result()
|
136
|
+
try:
|
137
|
+
new_task = await self._create_tool_call_task(tool_call)
|
138
|
+
tasks.add(new_task)
|
139
|
+
except ValueError as e:
|
140
|
+
yield {
|
141
|
+
"type": "conversation.item.create",
|
142
|
+
"item": {
|
143
|
+
"id": tool_call["call_id"],
|
144
|
+
"call_id": tool_call["call_id"],
|
145
|
+
"type": "function_call_output",
|
146
|
+
"output": (f"Error: {str(e)}"),
|
147
|
+
},
|
148
|
+
}
|
149
|
+
else:
|
150
|
+
yield task.result()
|
151
|
+
|
152
|
+
|
153
|
+
@beta()
|
154
|
+
class OpenAIVoiceReactAgent(BaseModel):
|
155
|
+
model: str
|
156
|
+
headers: dict[str, Any] = Field()
|
157
|
+
instructions: str | None = None
|
158
|
+
tools: list[BaseTool] | None = None
|
159
|
+
url: str = Field()
|
160
|
+
|
161
|
+
def bind_tools(self, tools: list[BaseTool]) -> None:
|
162
|
+
self.tools = tools
|
163
|
+
|
164
|
+
async def aconnect(
|
165
|
+
self,
|
166
|
+
input_stream: AsyncIterator[str],
|
167
|
+
send_output_chunk: Callable[[str], Coroutine[Any, Any, None]],
|
168
|
+
) -> None:
|
169
|
+
"""
|
170
|
+
Connect to the OpenAI API and send and receive messages.
|
171
|
+
|
172
|
+
input_stream: AsyncIterator[str]
|
173
|
+
Stream of input events to send to the model. Usually transports input_audio_buffer.append events from the microphone.
|
174
|
+
output: Callable[[str], None]
|
175
|
+
Callback to receive output events from the model. Usually sends response.audio.delta events to the speaker.
|
176
|
+
|
177
|
+
"""
|
178
|
+
# formatted_tools: list[BaseTool] = [
|
179
|
+
# tool if isinstance(tool, BaseTool) else tool_converter.wr(tool) # type: ignore
|
180
|
+
# for tool in self.tools or []
|
181
|
+
# ]
|
182
|
+
tools_by_name = {tool.name: tool for tool in self.tools}
|
183
|
+
tool_executor = VoiceToolExecutor(tools_by_name=tools_by_name)
|
184
|
+
|
185
|
+
async with connect(
|
186
|
+
model=self.model, headers=self.headers, url=self.url
|
187
|
+
) as (
|
188
|
+
model_send,
|
189
|
+
model_receive_stream,
|
190
|
+
):
|
191
|
+
# sent tools and instructions with initial chunk
|
192
|
+
tool_defs = [
|
193
|
+
{
|
194
|
+
"type": "function",
|
195
|
+
"name": tool.name,
|
196
|
+
"description": tool.description,
|
197
|
+
"parameters": {"type": "object", "properties": tool.args},
|
198
|
+
}
|
199
|
+
for tool in tools_by_name.values()
|
200
|
+
]
|
201
|
+
await model_send(
|
202
|
+
{
|
203
|
+
"type": "session.update",
|
204
|
+
"session": {
|
205
|
+
"instructions": self.instructions,
|
206
|
+
"input_audio_transcription": {
|
207
|
+
"model": "whisper-1",
|
208
|
+
},
|
209
|
+
"tools": tool_defs,
|
210
|
+
},
|
211
|
+
}
|
212
|
+
)
|
213
|
+
async for stream_key, data_raw in amerge(
|
214
|
+
input_mic=input_stream,
|
215
|
+
output_speaker=model_receive_stream,
|
216
|
+
tool_outputs=tool_executor.output_iterator(),
|
217
|
+
):
|
218
|
+
try:
|
219
|
+
data = (
|
220
|
+
json.loads(data_raw) if isinstance(data_raw, str) else data_raw
|
221
|
+
)
|
222
|
+
except json.JSONDecodeError:
|
223
|
+
print("error decoding data:", data_raw)
|
224
|
+
continue
|
225
|
+
|
226
|
+
if stream_key == "input_mic":
|
227
|
+
await model_send(data)
|
228
|
+
elif stream_key == "tool_outputs":
|
229
|
+
print("tool output", data)
|
230
|
+
await model_send(data)
|
231
|
+
await model_send({"type": "response.create", "response": {}})
|
232
|
+
elif stream_key == "output_speaker":
|
233
|
+
|
234
|
+
t = data["type"]
|
235
|
+
if t == "response.audio.delta":
|
236
|
+
await send_output_chunk(json.dumps(data))
|
237
|
+
elif t == "input_audio_buffer.speech_started":
|
238
|
+
print("interrupt")
|
239
|
+
send_output_chunk(json.dumps(data))
|
240
|
+
elif t == "error":
|
241
|
+
print("error:", data)
|
242
|
+
elif t == "response.function_call_arguments.done":
|
243
|
+
print("tool call", data)
|
244
|
+
await tool_executor.add_tool_call(data)
|
245
|
+
elif t == "response.audio_transcript.done":
|
246
|
+
print("model:", data["transcript"])
|
247
|
+
elif t == "conversation.item.input_audio_transcription.completed":
|
248
|
+
print("user:", data["transcript"])
|
249
|
+
elif t in EVENTS_TO_IGNORE:
|
250
|
+
pass
|
251
|
+
else:
|
252
|
+
print(t)
|
253
|
+
|
254
|
+
|
255
|
+
__all__ = ["OpenAIVoiceReactAgent"]
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import AsyncIterator, TypeVar
|
3
|
+
|
4
|
+
T = TypeVar("T")
|
5
|
+
|
6
|
+
|
7
|
+
async def amerge(**streams: AsyncIterator[T]) -> AsyncIterator[tuple[str, T]]:
|
8
|
+
"""Merge multiple streams into one stream."""
|
9
|
+
nexts: dict[asyncio.Task, str] = {
|
10
|
+
asyncio.create_task(anext(stream)): key for key, stream in streams.items()
|
11
|
+
}
|
12
|
+
while nexts:
|
13
|
+
done, _ = await asyncio.wait(nexts, return_when=asyncio.FIRST_COMPLETED)
|
14
|
+
for task in done:
|
15
|
+
key = nexts.pop(task)
|
16
|
+
stream = streams[key]
|
17
|
+
try:
|
18
|
+
yield key, task.result()
|
19
|
+
nexts[asyncio.create_task(anext(stream))] = key
|
20
|
+
except StopAsyncIteration:
|
21
|
+
pass
|
22
|
+
except Exception as e:
|
23
|
+
for task in nexts:
|
24
|
+
task.cancel()
|
25
|
+
raise e
|
blaxel/api/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
"""Contains methods for accessing the API"""
|
File without changes
|
@@ -0,0 +1,155 @@
|
|
1
|
+
from http import HTTPStatus
|
2
|
+
from typing import Any, Optional, Union
|
3
|
+
|
4
|
+
import httpx
|
5
|
+
|
6
|
+
from ... import errors
|
7
|
+
from ...client import AuthenticatedClient, Client
|
8
|
+
from ...models.agent import Agent
|
9
|
+
from ...types import Response
|
10
|
+
|
11
|
+
|
12
|
+
def _get_kwargs(
|
13
|
+
*,
|
14
|
+
body: Agent,
|
15
|
+
) -> dict[str, Any]:
|
16
|
+
headers: dict[str, Any] = {}
|
17
|
+
|
18
|
+
_kwargs: dict[str, Any] = {
|
19
|
+
"method": "post",
|
20
|
+
"url": "/agents",
|
21
|
+
}
|
22
|
+
|
23
|
+
_body = body.to_dict()
|
24
|
+
|
25
|
+
_kwargs["json"] = _body
|
26
|
+
headers["Content-Type"] = "application/json"
|
27
|
+
|
28
|
+
_kwargs["headers"] = headers
|
29
|
+
return _kwargs
|
30
|
+
|
31
|
+
|
32
|
+
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Agent]:
|
33
|
+
if response.status_code == 200:
|
34
|
+
response_200 = Agent.from_dict(response.json())
|
35
|
+
|
36
|
+
return response_200
|
37
|
+
if client.raise_on_unexpected_status:
|
38
|
+
raise errors.UnexpectedStatus(response.status_code, response.content)
|
39
|
+
else:
|
40
|
+
return None
|
41
|
+
|
42
|
+
|
43
|
+
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Agent]:
|
44
|
+
return Response(
|
45
|
+
status_code=HTTPStatus(response.status_code),
|
46
|
+
content=response.content,
|
47
|
+
headers=response.headers,
|
48
|
+
parsed=_parse_response(client=client, response=response),
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
def sync_detailed(
|
53
|
+
*,
|
54
|
+
client: AuthenticatedClient,
|
55
|
+
body: Agent,
|
56
|
+
) -> Response[Agent]:
|
57
|
+
"""Create agent by name
|
58
|
+
|
59
|
+
Args:
|
60
|
+
body (Agent): Agent
|
61
|
+
|
62
|
+
Raises:
|
63
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
64
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
Response[Agent]
|
68
|
+
"""
|
69
|
+
|
70
|
+
kwargs = _get_kwargs(
|
71
|
+
body=body,
|
72
|
+
)
|
73
|
+
|
74
|
+
response = client.get_httpx_client().request(
|
75
|
+
**kwargs,
|
76
|
+
)
|
77
|
+
|
78
|
+
return _build_response(client=client, response=response)
|
79
|
+
|
80
|
+
|
81
|
+
def sync(
|
82
|
+
*,
|
83
|
+
client: AuthenticatedClient,
|
84
|
+
body: Agent,
|
85
|
+
) -> Optional[Agent]:
|
86
|
+
"""Create agent by name
|
87
|
+
|
88
|
+
Args:
|
89
|
+
body (Agent): Agent
|
90
|
+
|
91
|
+
Raises:
|
92
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
93
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
Agent
|
97
|
+
"""
|
98
|
+
|
99
|
+
return sync_detailed(
|
100
|
+
client=client,
|
101
|
+
body=body,
|
102
|
+
).parsed
|
103
|
+
|
104
|
+
|
105
|
+
async def asyncio_detailed(
|
106
|
+
*,
|
107
|
+
client: AuthenticatedClient,
|
108
|
+
body: Agent,
|
109
|
+
) -> Response[Agent]:
|
110
|
+
"""Create agent by name
|
111
|
+
|
112
|
+
Args:
|
113
|
+
body (Agent): Agent
|
114
|
+
|
115
|
+
Raises:
|
116
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
117
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Response[Agent]
|
121
|
+
"""
|
122
|
+
|
123
|
+
kwargs = _get_kwargs(
|
124
|
+
body=body,
|
125
|
+
)
|
126
|
+
|
127
|
+
response = await client.get_async_httpx_client().request(**kwargs)
|
128
|
+
|
129
|
+
return _build_response(client=client, response=response)
|
130
|
+
|
131
|
+
|
132
|
+
async def asyncio(
|
133
|
+
*,
|
134
|
+
client: AuthenticatedClient,
|
135
|
+
body: Agent,
|
136
|
+
) -> Optional[Agent]:
|
137
|
+
"""Create agent by name
|
138
|
+
|
139
|
+
Args:
|
140
|
+
body (Agent): Agent
|
141
|
+
|
142
|
+
Raises:
|
143
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
144
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
Agent
|
148
|
+
"""
|
149
|
+
|
150
|
+
return (
|
151
|
+
await asyncio_detailed(
|
152
|
+
client=client,
|
153
|
+
body=body,
|
154
|
+
)
|
155
|
+
).parsed
|
@@ -0,0 +1,146 @@
|
|
1
|
+
from http import HTTPStatus
|
2
|
+
from typing import Any, Optional, Union
|
3
|
+
|
4
|
+
import httpx
|
5
|
+
|
6
|
+
from ... import errors
|
7
|
+
from ...client import AuthenticatedClient, Client
|
8
|
+
from ...models.agent import Agent
|
9
|
+
from ...types import Response
|
10
|
+
|
11
|
+
|
12
|
+
def _get_kwargs(
|
13
|
+
agent_name: str,
|
14
|
+
) -> dict[str, Any]:
|
15
|
+
_kwargs: dict[str, Any] = {
|
16
|
+
"method": "delete",
|
17
|
+
"url": f"/agents/{agent_name}",
|
18
|
+
}
|
19
|
+
|
20
|
+
return _kwargs
|
21
|
+
|
22
|
+
|
23
|
+
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Agent]:
|
24
|
+
if response.status_code == 200:
|
25
|
+
response_200 = Agent.from_dict(response.json())
|
26
|
+
|
27
|
+
return response_200
|
28
|
+
if client.raise_on_unexpected_status:
|
29
|
+
raise errors.UnexpectedStatus(response.status_code, response.content)
|
30
|
+
else:
|
31
|
+
return None
|
32
|
+
|
33
|
+
|
34
|
+
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Agent]:
|
35
|
+
return Response(
|
36
|
+
status_code=HTTPStatus(response.status_code),
|
37
|
+
content=response.content,
|
38
|
+
headers=response.headers,
|
39
|
+
parsed=_parse_response(client=client, response=response),
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
def sync_detailed(
|
44
|
+
agent_name: str,
|
45
|
+
*,
|
46
|
+
client: AuthenticatedClient,
|
47
|
+
) -> Response[Agent]:
|
48
|
+
"""Delete agent by name
|
49
|
+
|
50
|
+
Args:
|
51
|
+
agent_name (str):
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
55
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
Response[Agent]
|
59
|
+
"""
|
60
|
+
|
61
|
+
kwargs = _get_kwargs(
|
62
|
+
agent_name=agent_name,
|
63
|
+
)
|
64
|
+
|
65
|
+
response = client.get_httpx_client().request(
|
66
|
+
**kwargs,
|
67
|
+
)
|
68
|
+
|
69
|
+
return _build_response(client=client, response=response)
|
70
|
+
|
71
|
+
|
72
|
+
def sync(
|
73
|
+
agent_name: str,
|
74
|
+
*,
|
75
|
+
client: AuthenticatedClient,
|
76
|
+
) -> Optional[Agent]:
|
77
|
+
"""Delete agent by name
|
78
|
+
|
79
|
+
Args:
|
80
|
+
agent_name (str):
|
81
|
+
|
82
|
+
Raises:
|
83
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
84
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
Agent
|
88
|
+
"""
|
89
|
+
|
90
|
+
return sync_detailed(
|
91
|
+
agent_name=agent_name,
|
92
|
+
client=client,
|
93
|
+
).parsed
|
94
|
+
|
95
|
+
|
96
|
+
async def asyncio_detailed(
|
97
|
+
agent_name: str,
|
98
|
+
*,
|
99
|
+
client: AuthenticatedClient,
|
100
|
+
) -> Response[Agent]:
|
101
|
+
"""Delete agent by name
|
102
|
+
|
103
|
+
Args:
|
104
|
+
agent_name (str):
|
105
|
+
|
106
|
+
Raises:
|
107
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
108
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Response[Agent]
|
112
|
+
"""
|
113
|
+
|
114
|
+
kwargs = _get_kwargs(
|
115
|
+
agent_name=agent_name,
|
116
|
+
)
|
117
|
+
|
118
|
+
response = await client.get_async_httpx_client().request(**kwargs)
|
119
|
+
|
120
|
+
return _build_response(client=client, response=response)
|
121
|
+
|
122
|
+
|
123
|
+
async def asyncio(
|
124
|
+
agent_name: str,
|
125
|
+
*,
|
126
|
+
client: AuthenticatedClient,
|
127
|
+
) -> Optional[Agent]:
|
128
|
+
"""Delete agent by name
|
129
|
+
|
130
|
+
Args:
|
131
|
+
agent_name (str):
|
132
|
+
|
133
|
+
Raises:
|
134
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
135
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
Agent
|
139
|
+
"""
|
140
|
+
|
141
|
+
return (
|
142
|
+
await asyncio_detailed(
|
143
|
+
agent_name=agent_name,
|
144
|
+
client=client,
|
145
|
+
)
|
146
|
+
).parsed
|