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.
Files changed (261) hide show
  1. blaxel/__init__.py +8 -0
  2. blaxel/agents/__init__.py +5 -0
  3. blaxel/agents/chain.py +153 -0
  4. blaxel/agents/chat.py +286 -0
  5. blaxel/agents/decorator.py +208 -0
  6. blaxel/agents/thread.py +24 -0
  7. blaxel/agents/voice/openai.py +255 -0
  8. blaxel/agents/voice/utils.py +25 -0
  9. blaxel/api/__init__.py +1 -0
  10. blaxel/api/agents/__init__.py +0 -0
  11. blaxel/api/agents/create_agent.py +155 -0
  12. blaxel/api/agents/delete_agent.py +146 -0
  13. blaxel/api/agents/get_agent.py +146 -0
  14. blaxel/api/agents/get_agent_logs.py +151 -0
  15. blaxel/api/agents/get_agent_metrics.py +150 -0
  16. blaxel/api/agents/get_agent_trace_ids.py +201 -0
  17. blaxel/api/agents/list_agent_revisions.py +155 -0
  18. blaxel/api/agents/list_agents.py +127 -0
  19. blaxel/api/agents/update_agent.py +168 -0
  20. blaxel/api/configurations/__init__.py +0 -0
  21. blaxel/api/configurations/get_configuration.py +122 -0
  22. blaxel/api/default/__init__.py +0 -0
  23. blaxel/api/default/get_trace.py +150 -0
  24. blaxel/api/default/get_trace_ids.py +218 -0
  25. blaxel/api/default/get_trace_logs.py +186 -0
  26. blaxel/api/default/list_mcp_hub_definitions.py +127 -0
  27. blaxel/api/functions/__init__.py +0 -0
  28. blaxel/api/functions/create_function.py +155 -0
  29. blaxel/api/functions/delete_function.py +146 -0
  30. blaxel/api/functions/get_function.py +146 -0
  31. blaxel/api/functions/get_function_logs.py +151 -0
  32. blaxel/api/functions/get_function_metrics.py +150 -0
  33. blaxel/api/functions/get_function_trace_ids.py +201 -0
  34. blaxel/api/functions/list_function_revisions.py +158 -0
  35. blaxel/api/functions/list_functions.py +131 -0
  36. blaxel/api/functions/update_function.py +168 -0
  37. blaxel/api/integrations/__init__.py +0 -0
  38. blaxel/api/integrations/create_integration_connection.py +167 -0
  39. blaxel/api/integrations/delete_integration_connection.py +158 -0
  40. blaxel/api/integrations/get_integration.py +97 -0
  41. blaxel/api/integrations/get_integration_connection.py +158 -0
  42. blaxel/api/integrations/get_integration_connection_model.py +104 -0
  43. blaxel/api/integrations/get_integration_connection_model_endpoint_configurations.py +97 -0
  44. blaxel/api/integrations/list_integration_connection_models.py +97 -0
  45. blaxel/api/integrations/list_integration_connections.py +139 -0
  46. blaxel/api/integrations/update_integration_connection.py +180 -0
  47. blaxel/api/invitations/__init__.py +0 -0
  48. blaxel/api/invitations/list_all_pending_invitations.py +142 -0
  49. blaxel/api/knowledgebases/__init__.py +0 -0
  50. blaxel/api/knowledgebases/create_knowledgebase.py +163 -0
  51. blaxel/api/knowledgebases/delete_knowledgebase.py +154 -0
  52. blaxel/api/knowledgebases/get_knowledgebase.py +154 -0
  53. blaxel/api/knowledgebases/list_knowledgebase_revisions.py +158 -0
  54. blaxel/api/knowledgebases/list_knowledgebases.py +139 -0
  55. blaxel/api/knowledgebases/update_knowledgebase.py +176 -0
  56. blaxel/api/locations/__init__.py +0 -0
  57. blaxel/api/locations/list_locations.py +139 -0
  58. blaxel/api/metrics/__init__.py +0 -0
  59. blaxel/api/metrics/get_metrics.py +130 -0
  60. blaxel/api/models/__init__.py +0 -0
  61. blaxel/api/models/create_model.py +163 -0
  62. blaxel/api/models/delete_model.py +154 -0
  63. blaxel/api/models/get_model.py +154 -0
  64. blaxel/api/models/get_model_logs.py +155 -0
  65. blaxel/api/models/get_model_metrics.py +158 -0
  66. blaxel/api/models/get_model_trace_ids.py +201 -0
  67. blaxel/api/models/list_model_revisions.py +158 -0
  68. blaxel/api/models/list_models.py +135 -0
  69. blaxel/api/models/update_model.py +176 -0
  70. blaxel/api/policies/__init__.py +0 -0
  71. blaxel/api/policies/create_policy.py +167 -0
  72. blaxel/api/policies/delete_policy.py +154 -0
  73. blaxel/api/policies/get_policy.py +154 -0
  74. blaxel/api/policies/list_policies.py +139 -0
  75. blaxel/api/policies/update_policy.py +180 -0
  76. blaxel/api/privateclusters/__init__.py +0 -0
  77. blaxel/api/privateclusters/create_private_cluster.py +132 -0
  78. blaxel/api/privateclusters/delete_private_cluster.py +156 -0
  79. blaxel/api/privateclusters/get_private_cluster.py +159 -0
  80. blaxel/api/privateclusters/get_private_cluster_health.py +97 -0
  81. blaxel/api/privateclusters/list_private_clusters.py +140 -0
  82. blaxel/api/privateclusters/update_private_cluster.py +156 -0
  83. blaxel/api/privateclusters/update_private_cluster_health.py +97 -0
  84. blaxel/api/service_accounts/__init__.py +0 -0
  85. blaxel/api/service_accounts/create_api_key_for_service_account.py +177 -0
  86. blaxel/api/service_accounts/create_workspace_service_account.py +170 -0
  87. blaxel/api/service_accounts/delete_api_key_for_service_account.py +104 -0
  88. blaxel/api/service_accounts/delete_workspace_service_account.py +160 -0
  89. blaxel/api/service_accounts/get_workspace_service_accounts.py +141 -0
  90. blaxel/api/service_accounts/list_api_keys_for_service_account.py +163 -0
  91. blaxel/api/service_accounts/update_workspace_service_account.py +183 -0
  92. blaxel/api/store/__init__.py +0 -0
  93. blaxel/api/store/get_store_agent.py +146 -0
  94. blaxel/api/store/get_store_function.py +146 -0
  95. blaxel/api/store/list_store_agents.py +131 -0
  96. blaxel/api/store/list_store_functions.py +131 -0
  97. blaxel/api/workspaces/__init__.py +0 -0
  98. blaxel/api/workspaces/accept_workspace_invitation.py +161 -0
  99. blaxel/api/workspaces/create_worspace.py +163 -0
  100. blaxel/api/workspaces/decline_workspace_invitation.py +158 -0
  101. blaxel/api/workspaces/delete_workspace.py +154 -0
  102. blaxel/api/workspaces/get_workspace.py +154 -0
  103. blaxel/api/workspaces/invite_workspace_user.py +174 -0
  104. blaxel/api/workspaces/leave_workspace.py +161 -0
  105. blaxel/api/workspaces/list_workspace_users.py +139 -0
  106. blaxel/api/workspaces/list_workspaces.py +139 -0
  107. blaxel/api/workspaces/remove_workspace_user.py +101 -0
  108. blaxel/api/workspaces/update_workspace.py +176 -0
  109. blaxel/api/workspaces/update_workspace_user_role.py +187 -0
  110. blaxel/authentication/__init__.py +45 -0
  111. blaxel/authentication/apikey.py +50 -0
  112. blaxel/authentication/authentication.py +176 -0
  113. blaxel/authentication/clientcredentials.py +103 -0
  114. blaxel/authentication/credentials.py +295 -0
  115. blaxel/authentication/device_mode.py +197 -0
  116. blaxel/client.py +281 -0
  117. blaxel/common/__init__.py +17 -0
  118. blaxel/common/error.py +27 -0
  119. blaxel/common/instrumentation.py +317 -0
  120. blaxel/common/logger.py +60 -0
  121. blaxel/common/secrets.py +39 -0
  122. blaxel/common/settings.py +150 -0
  123. blaxel/common/slugify.py +18 -0
  124. blaxel/common/utils.py +34 -0
  125. blaxel/deploy/__init__.py +8 -0
  126. blaxel/deploy/deploy.py +316 -0
  127. blaxel/deploy/format.py +46 -0
  128. blaxel/deploy/parser.py +192 -0
  129. blaxel/errors.py +16 -0
  130. blaxel/functions/__init__.py +7 -0
  131. blaxel/functions/common.py +228 -0
  132. blaxel/functions/decorator.py +64 -0
  133. blaxel/functions/local/local.py +48 -0
  134. blaxel/functions/mcp/client.py +96 -0
  135. blaxel/functions/mcp/mcp.py +168 -0
  136. blaxel/functions/mcp/utils.py +56 -0
  137. blaxel/functions/remote/remote.py +183 -0
  138. blaxel/models/__init__.py +233 -0
  139. blaxel/models/acl.py +133 -0
  140. blaxel/models/agent.py +126 -0
  141. blaxel/models/agent_chain.py +88 -0
  142. blaxel/models/agent_spec.py +346 -0
  143. blaxel/models/api_key.py +142 -0
  144. blaxel/models/configuration.py +85 -0
  145. blaxel/models/continent.py +70 -0
  146. blaxel/models/core_event.py +97 -0
  147. blaxel/models/core_spec.py +249 -0
  148. blaxel/models/core_spec_configurations.py +77 -0
  149. blaxel/models/country.py +70 -0
  150. blaxel/models/create_api_key_for_service_account_body.py +69 -0
  151. blaxel/models/create_workspace_service_account_body.py +71 -0
  152. blaxel/models/create_workspace_service_account_response_200.py +105 -0
  153. blaxel/models/delete_workspace_service_account_response_200.py +96 -0
  154. blaxel/models/entrypoint.py +96 -0
  155. blaxel/models/entrypoint_env.py +45 -0
  156. blaxel/models/flavor.py +70 -0
  157. blaxel/models/form.py +120 -0
  158. blaxel/models/form_config.py +45 -0
  159. blaxel/models/form_oauthomitempty.py +45 -0
  160. blaxel/models/form_secrets.py +45 -0
  161. blaxel/models/function.py +126 -0
  162. blaxel/models/function_kit.py +97 -0
  163. blaxel/models/function_spec.py +310 -0
  164. blaxel/models/get_trace_ids_response_200.py +45 -0
  165. blaxel/models/get_trace_logs_response_200.py +45 -0
  166. blaxel/models/get_trace_response_200.py +45 -0
  167. blaxel/models/get_workspace_service_accounts_response_200_item.py +96 -0
  168. blaxel/models/histogram_bucket.py +79 -0
  169. blaxel/models/histogram_stats.py +88 -0
  170. blaxel/models/integration_connection.py +96 -0
  171. blaxel/models/integration_connection_spec.py +114 -0
  172. blaxel/models/integration_connection_spec_config.py +45 -0
  173. blaxel/models/integration_connection_spec_secret.py +45 -0
  174. blaxel/models/integration_model.py +162 -0
  175. blaxel/models/integration_repository.py +88 -0
  176. blaxel/models/invite_workspace_user_body.py +60 -0
  177. blaxel/models/knowledgebase.py +126 -0
  178. blaxel/models/knowledgebase_spec.py +163 -0
  179. blaxel/models/knowledgebase_spec_options.py +45 -0
  180. blaxel/models/last_n_requests_metric.py +79 -0
  181. blaxel/models/latency_metric.py +144 -0
  182. blaxel/models/location_response.py +113 -0
  183. blaxel/models/mcp_definition.py +188 -0
  184. blaxel/models/mcp_definition_entrypoint.py +45 -0
  185. blaxel/models/mcp_definition_form.py +45 -0
  186. blaxel/models/metadata.py +139 -0
  187. blaxel/models/metadata_labels.py +45 -0
  188. blaxel/models/metric.py +79 -0
  189. blaxel/models/metrics.py +169 -0
  190. blaxel/models/metrics_models.py +45 -0
  191. blaxel/models/metrics_request_total_per_code.py +45 -0
  192. blaxel/models/metrics_rps_per_code.py +45 -0
  193. blaxel/models/model.py +126 -0
  194. blaxel/models/model_private_cluster.py +79 -0
  195. blaxel/models/model_spec.py +249 -0
  196. blaxel/models/o_auth.py +72 -0
  197. blaxel/models/owner_fields.py +70 -0
  198. blaxel/models/pending_invitation.py +124 -0
  199. blaxel/models/pending_invitation_accept.py +85 -0
  200. blaxel/models/pending_invitation_render.py +147 -0
  201. blaxel/models/pending_invitation_render_invited_by.py +88 -0
  202. blaxel/models/pending_invitation_render_workspace.py +70 -0
  203. blaxel/models/pending_invitation_workspace_details.py +72 -0
  204. blaxel/models/pod_template_spec.py +45 -0
  205. blaxel/models/policy.py +96 -0
  206. blaxel/models/policy_location.py +70 -0
  207. blaxel/models/policy_max_tokens.py +106 -0
  208. blaxel/models/policy_spec.py +151 -0
  209. blaxel/models/private_cluster.py +183 -0
  210. blaxel/models/private_location.py +61 -0
  211. blaxel/models/repository.py +70 -0
  212. blaxel/models/request_duration_over_time_metric.py +97 -0
  213. blaxel/models/request_duration_over_time_metrics.py +80 -0
  214. blaxel/models/request_total_by_origin_metric.py +115 -0
  215. blaxel/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
  216. blaxel/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
  217. blaxel/models/request_total_metric.py +123 -0
  218. blaxel/models/request_total_metric_request_total_per_code.py +45 -0
  219. blaxel/models/request_total_metric_rps_per_code.py +45 -0
  220. blaxel/models/resource_log.py +79 -0
  221. blaxel/models/resource_metrics.py +270 -0
  222. blaxel/models/resource_metrics_request_total_per_code.py +45 -0
  223. blaxel/models/resource_metrics_rps_per_code.py +45 -0
  224. blaxel/models/revision_configuration.py +97 -0
  225. blaxel/models/revision_metadata.py +124 -0
  226. blaxel/models/runtime.py +196 -0
  227. blaxel/models/runtime_startup_probe.py +45 -0
  228. blaxel/models/serverless_config.py +80 -0
  229. blaxel/models/spec_configuration.py +70 -0
  230. blaxel/models/store_agent.py +178 -0
  231. blaxel/models/store_agent_labels.py +45 -0
  232. blaxel/models/store_configuration.py +151 -0
  233. blaxel/models/store_configuration_option.py +79 -0
  234. blaxel/models/store_function.py +211 -0
  235. blaxel/models/store_function_kit.py +97 -0
  236. blaxel/models/store_function_labels.py +45 -0
  237. blaxel/models/store_function_parameter.py +88 -0
  238. blaxel/models/time_fields.py +70 -0
  239. blaxel/models/token_rate_metric.py +88 -0
  240. blaxel/models/token_rate_metrics.py +120 -0
  241. blaxel/models/token_total_metric.py +106 -0
  242. blaxel/models/trace_ids_response.py +45 -0
  243. blaxel/models/update_workspace_service_account_body.py +69 -0
  244. blaxel/models/update_workspace_service_account_response_200.py +96 -0
  245. blaxel/models/update_workspace_user_role_body.py +60 -0
  246. blaxel/models/websocket_channel.py +88 -0
  247. blaxel/models/workspace.py +148 -0
  248. blaxel/models/workspace_labels.py +45 -0
  249. blaxel/models/workspace_user.py +115 -0
  250. blaxel/py.typed +1 -0
  251. blaxel/run.py +108 -0
  252. blaxel/serve/app.py +131 -0
  253. blaxel/serve/middlewares/__init__.py +10 -0
  254. blaxel/serve/middlewares/accesslog.py +32 -0
  255. blaxel/serve/middlewares/processtime.py +28 -0
  256. blaxel/types.py +46 -0
  257. blaxel-0.64.0.dist-info/METADATA +96 -0
  258. blaxel-0.64.0.dist-info/RECORD +261 -0
  259. blaxel-0.64.0.dist-info/WHEEL +4 -0
  260. blaxel-0.64.0.dist-info/entry_points.txt +2 -0
  261. 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