hud-python 0.3.5__py3-none-any.whl → 0.4.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.

Potentially problematic release.


This version of hud-python might be problematic. Click here for more details.

Files changed (192) hide show
  1. hud/__init__.py +22 -89
  2. hud/agents/__init__.py +17 -0
  3. hud/agents/art.py +101 -0
  4. hud/agents/base.py +599 -0
  5. hud/{mcp → agents}/claude.py +373 -321
  6. hud/{mcp → agents}/langchain.py +250 -250
  7. hud/agents/misc/__init__.py +7 -0
  8. hud/{agent → agents}/misc/response_agent.py +80 -80
  9. hud/{mcp → agents}/openai.py +352 -334
  10. hud/agents/openai_chat_generic.py +154 -0
  11. hud/{mcp → agents}/tests/__init__.py +1 -1
  12. hud/agents/tests/test_base.py +742 -0
  13. hud/agents/tests/test_claude.py +324 -0
  14. hud/{mcp → agents}/tests/test_client.py +363 -324
  15. hud/{mcp → agents}/tests/test_openai.py +237 -238
  16. hud/cli/__init__.py +617 -0
  17. hud/cli/__main__.py +8 -0
  18. hud/cli/analyze.py +371 -0
  19. hud/cli/analyze_metadata.py +230 -0
  20. hud/cli/build.py +427 -0
  21. hud/cli/clone.py +185 -0
  22. hud/cli/cursor.py +92 -0
  23. hud/cli/debug.py +392 -0
  24. hud/cli/docker_utils.py +83 -0
  25. hud/cli/init.py +281 -0
  26. hud/cli/interactive.py +353 -0
  27. hud/cli/mcp_server.py +756 -0
  28. hud/cli/pull.py +336 -0
  29. hud/cli/push.py +379 -0
  30. hud/cli/remote_runner.py +311 -0
  31. hud/cli/runner.py +160 -0
  32. hud/cli/tests/__init__.py +3 -0
  33. hud/cli/tests/test_analyze.py +284 -0
  34. hud/cli/tests/test_cli_init.py +265 -0
  35. hud/cli/tests/test_cli_main.py +27 -0
  36. hud/cli/tests/test_clone.py +142 -0
  37. hud/cli/tests/test_cursor.py +253 -0
  38. hud/cli/tests/test_debug.py +453 -0
  39. hud/cli/tests/test_mcp_server.py +139 -0
  40. hud/cli/tests/test_utils.py +388 -0
  41. hud/cli/utils.py +263 -0
  42. hud/clients/README.md +143 -0
  43. hud/clients/__init__.py +16 -0
  44. hud/clients/base.py +354 -0
  45. hud/clients/fastmcp.py +202 -0
  46. hud/clients/mcp_use.py +278 -0
  47. hud/clients/tests/__init__.py +1 -0
  48. hud/clients/tests/test_client_integration.py +111 -0
  49. hud/clients/tests/test_fastmcp.py +342 -0
  50. hud/clients/tests/test_protocol.py +188 -0
  51. hud/clients/utils/__init__.py +1 -0
  52. hud/clients/utils/retry_transport.py +160 -0
  53. hud/datasets.py +322 -192
  54. hud/misc/__init__.py +1 -0
  55. hud/{agent → misc}/claude_plays_pokemon.py +292 -283
  56. hud/otel/__init__.py +35 -0
  57. hud/otel/collector.py +142 -0
  58. hud/otel/config.py +164 -0
  59. hud/otel/context.py +536 -0
  60. hud/otel/exporters.py +366 -0
  61. hud/otel/instrumentation.py +97 -0
  62. hud/otel/processors.py +118 -0
  63. hud/otel/tests/__init__.py +1 -0
  64. hud/otel/tests/test_processors.py +197 -0
  65. hud/server/__init__.py +5 -5
  66. hud/server/context.py +114 -0
  67. hud/server/helper/__init__.py +5 -0
  68. hud/server/low_level.py +132 -0
  69. hud/server/server.py +166 -0
  70. hud/server/tests/__init__.py +3 -0
  71. hud/settings.py +73 -79
  72. hud/shared/__init__.py +5 -0
  73. hud/{exceptions.py → shared/exceptions.py} +180 -180
  74. hud/{server → shared}/requests.py +264 -264
  75. hud/shared/tests/test_exceptions.py +157 -0
  76. hud/{server → shared}/tests/test_requests.py +275 -275
  77. hud/telemetry/__init__.py +25 -30
  78. hud/telemetry/instrument.py +379 -0
  79. hud/telemetry/job.py +309 -141
  80. hud/telemetry/replay.py +74 -0
  81. hud/telemetry/trace.py +83 -0
  82. hud/tools/__init__.py +33 -34
  83. hud/tools/base.py +365 -65
  84. hud/tools/bash.py +161 -137
  85. hud/tools/computer/__init__.py +15 -13
  86. hud/tools/computer/anthropic.py +437 -420
  87. hud/tools/computer/hud.py +376 -334
  88. hud/tools/computer/openai.py +295 -292
  89. hud/tools/computer/settings.py +82 -0
  90. hud/tools/edit.py +314 -290
  91. hud/tools/executors/__init__.py +30 -30
  92. hud/tools/executors/base.py +539 -532
  93. hud/tools/executors/pyautogui.py +621 -619
  94. hud/tools/executors/tests/__init__.py +1 -1
  95. hud/tools/executors/tests/test_base_executor.py +338 -338
  96. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  97. hud/tools/executors/xdo.py +511 -503
  98. hud/tools/{playwright_tool.py → playwright.py} +412 -379
  99. hud/tools/tests/__init__.py +3 -3
  100. hud/tools/tests/test_base.py +282 -0
  101. hud/tools/tests/test_bash.py +158 -152
  102. hud/tools/tests/test_bash_extended.py +197 -0
  103. hud/tools/tests/test_computer.py +425 -52
  104. hud/tools/tests/test_computer_actions.py +34 -34
  105. hud/tools/tests/test_edit.py +259 -240
  106. hud/tools/tests/test_init.py +27 -27
  107. hud/tools/tests/test_playwright_tool.py +183 -183
  108. hud/tools/tests/test_tools.py +145 -157
  109. hud/tools/tests/test_utils.py +156 -156
  110. hud/tools/types.py +72 -0
  111. hud/tools/utils.py +50 -50
  112. hud/types.py +136 -89
  113. hud/utils/__init__.py +10 -16
  114. hud/utils/async_utils.py +65 -0
  115. hud/utils/design.py +168 -0
  116. hud/utils/mcp.py +55 -0
  117. hud/utils/progress.py +149 -149
  118. hud/utils/telemetry.py +66 -66
  119. hud/utils/tests/test_async_utils.py +173 -0
  120. hud/utils/tests/test_init.py +17 -21
  121. hud/utils/tests/test_progress.py +261 -225
  122. hud/utils/tests/test_telemetry.py +82 -37
  123. hud/utils/tests/test_version.py +8 -8
  124. hud/version.py +7 -7
  125. hud_python-0.4.0.dist-info/METADATA +474 -0
  126. hud_python-0.4.0.dist-info/RECORD +132 -0
  127. hud_python-0.4.0.dist-info/entry_points.txt +3 -0
  128. {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/licenses/LICENSE +21 -21
  129. hud/adapters/__init__.py +0 -8
  130. hud/adapters/claude/__init__.py +0 -5
  131. hud/adapters/claude/adapter.py +0 -180
  132. hud/adapters/claude/tests/__init__.py +0 -1
  133. hud/adapters/claude/tests/test_adapter.py +0 -519
  134. hud/adapters/common/__init__.py +0 -6
  135. hud/adapters/common/adapter.py +0 -178
  136. hud/adapters/common/tests/test_adapter.py +0 -289
  137. hud/adapters/common/types.py +0 -446
  138. hud/adapters/operator/__init__.py +0 -5
  139. hud/adapters/operator/adapter.py +0 -108
  140. hud/adapters/operator/tests/__init__.py +0 -1
  141. hud/adapters/operator/tests/test_adapter.py +0 -370
  142. hud/agent/__init__.py +0 -19
  143. hud/agent/base.py +0 -126
  144. hud/agent/claude.py +0 -271
  145. hud/agent/langchain.py +0 -215
  146. hud/agent/misc/__init__.py +0 -3
  147. hud/agent/operator.py +0 -268
  148. hud/agent/tests/__init__.py +0 -1
  149. hud/agent/tests/test_base.py +0 -202
  150. hud/env/__init__.py +0 -11
  151. hud/env/client.py +0 -35
  152. hud/env/docker_client.py +0 -349
  153. hud/env/environment.py +0 -446
  154. hud/env/local_docker_client.py +0 -358
  155. hud/env/remote_client.py +0 -212
  156. hud/env/remote_docker_client.py +0 -292
  157. hud/gym.py +0 -130
  158. hud/job.py +0 -773
  159. hud/mcp/__init__.py +0 -17
  160. hud/mcp/base.py +0 -631
  161. hud/mcp/client.py +0 -312
  162. hud/mcp/tests/test_base.py +0 -512
  163. hud/mcp/tests/test_claude.py +0 -294
  164. hud/task.py +0 -149
  165. hud/taskset.py +0 -237
  166. hud/telemetry/_trace.py +0 -347
  167. hud/telemetry/context.py +0 -230
  168. hud/telemetry/exporter.py +0 -575
  169. hud/telemetry/instrumentation/__init__.py +0 -3
  170. hud/telemetry/instrumentation/mcp.py +0 -259
  171. hud/telemetry/instrumentation/registry.py +0 -59
  172. hud/telemetry/mcp_models.py +0 -270
  173. hud/telemetry/tests/__init__.py +0 -1
  174. hud/telemetry/tests/test_context.py +0 -210
  175. hud/telemetry/tests/test_trace.py +0 -312
  176. hud/tools/helper/README.md +0 -56
  177. hud/tools/helper/__init__.py +0 -9
  178. hud/tools/helper/mcp_server.py +0 -78
  179. hud/tools/helper/server_initialization.py +0 -115
  180. hud/tools/helper/utils.py +0 -58
  181. hud/trajectory.py +0 -94
  182. hud/utils/agent.py +0 -37
  183. hud/utils/common.py +0 -256
  184. hud/utils/config.py +0 -120
  185. hud/utils/deprecation.py +0 -115
  186. hud/utils/misc.py +0 -53
  187. hud/utils/tests/test_common.py +0 -277
  188. hud/utils/tests/test_config.py +0 -129
  189. hud_python-0.3.5.dist-info/METADATA +0 -284
  190. hud_python-0.3.5.dist-info/RECORD +0 -120
  191. /hud/{adapters/common → shared}/tests/__init__.py +0 -0
  192. {hud_python-0.3.5.dist-info → hud_python-0.4.0.dist-info}/WHEEL +0 -0
hud/env/environment.py DELETED
@@ -1,446 +0,0 @@
1
- """Base classes for environment implementations."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
- from typing import TYPE_CHECKING, Any
7
-
8
- from pydantic import BaseModel
9
-
10
- from hud.env.client import Client
11
- from hud.env.remote_client import RemoteClient, SetupRequest
12
- from hud.task import Task
13
- from hud.utils.agent import format_agent_prompt
14
- from hud.utils.common import FunctionConfig, FunctionConfigs, Observation
15
- from hud.utils.config import (
16
- LOCAL_EVALUATORS,
17
- REMOTE_EVALUATE,
18
- REMOTE_FUNCTION_PREFIX,
19
- expand_config,
20
- )
21
- from hud.utils.telemetry import stream
22
-
23
- logger = logging.getLogger("hud.environment")
24
-
25
- if TYPE_CHECKING:
26
- from hud.adapters.common import CLA
27
- from hud.agent import Agent
28
-
29
-
30
- class Environment(BaseModel):
31
- """
32
- Environment base class that provides common functionality for all environment implementations.
33
- This class uses the primitives provided by EnvClient to implement core environment operations.
34
- """
35
-
36
- metadata: dict[str, Any]
37
- client: Client
38
- url: str | None = None
39
- live_url: str | None = None
40
- # The task id to use for the environment reset
41
- task: Task | None = None
42
- build_data: dict[str, Any]
43
-
44
- # The task run id
45
- task_run_id: str | None = None
46
-
47
- # final response
48
- final_response: str | None = None
49
-
50
- # environment prompt information
51
- environment_prompt: str | None = None
52
-
53
- async def _invoke_all(self, configs: FunctionConfigs) -> list[Any]:
54
- # Execute each config and collect results
55
- configs_all = [configs] if not isinstance(configs, list) else configs
56
- results = []
57
- for config in configs_all:
58
- for expanded_config in expand_config(config):
59
- result, stdout, stderr = await self.client.invoke(expanded_config)
60
- results.append(result)
61
- if stdout:
62
- logger.info(
63
- "%s produced stdout:\n%s",
64
- expanded_config.function,
65
- stdout.decode(),
66
- )
67
- if stderr:
68
- logger.warning(
69
- "%s produced stderr:\n%s",
70
- expanded_config.function,
71
- stderr.decode(),
72
- )
73
- return results
74
-
75
- async def _setup(self, config: FunctionConfigs | None = None) -> None:
76
- """
77
- Setup the environment.
78
- No-op if no config or task is provided.
79
-
80
- Args:
81
- config: The configuration to use for the setup
82
- """
83
- if isinstance(self.client, RemoteClient):
84
- await self.get_urls()
85
-
86
- setup_request = SetupRequest()
87
-
88
- if self.task:
89
- setup_request.task_id = self.task.id
90
- setup_request.config = self.task.config
91
- setup_request.metadata = _format_task_metadata(self.task)
92
- if self.task.setup:
93
- setup_request.setup = expand_config(self.task.setup)[0]
94
- elif config:
95
- setup_request.setup = expand_config(config)[0]
96
- else:
97
- raise ValueError("No task or config provided for remote environment")
98
-
99
- result = await self.client.setup(setup_request)
100
-
101
- if result and result.get("id"):
102
- self.task_run_id = result.get("id")
103
- logger.info("View the live trace at https://app.hud.so/trace/%s", self.task_run_id)
104
- else:
105
- logger.warning("No task run id found in the result")
106
- else:
107
- if config is not None:
108
- await self._invoke_all(config)
109
- elif self.task and self.task.setup is not None:
110
- await self._invoke_all(self.task.setup)
111
-
112
- async def evaluate(
113
- self,
114
- config: FunctionConfigs | None = None,
115
- metadata: dict[str, Any] | None = None,
116
- ) -> Any:
117
- """
118
- Evaluate the environment.
119
-
120
- Args:
121
- config: The configuration to use for the evaluation
122
-
123
- Returns:
124
- Any: Result of the evaluation
125
- """
126
- if isinstance(self.client, RemoteClient):
127
- results = await self._invoke_all(
128
- create_remote_config(self, config, REMOTE_EVALUATE, metadata)
129
- )
130
- else:
131
- if config is not None:
132
- results = await self._invoke_all(config)
133
- elif self.task and self.task.evaluate is not None:
134
- results = await self._invoke_all(self.task.evaluate)
135
- else:
136
- raise ValueError("No config or task provided for local environment")
137
- if len(results) == 1:
138
- return results[0]
139
- else:
140
- return results
141
-
142
- async def reset(self) -> tuple[Observation, dict[str, Any]]:
143
- """
144
- Reset the environment and return the first observation with the agent prompt.
145
-
146
- Args:
147
- None
148
-
149
- Returns:
150
- Observation: The first observation from the environment with the agent prompt
151
- info: Dictionary of information about the environment
152
- """
153
- # await self._setup(configs)
154
- obs, _, _, info = await self.step()
155
-
156
- if self.build_data.get("environment_prompt"):
157
- self.environment_prompt = self.build_data["environment_prompt"]
158
-
159
- # Format the agent prompt with the environment prompt and the task prompt
160
- obs.text = format_agent_prompt(self.environment_prompt, self.task)
161
-
162
- return obs, info
163
-
164
- async def step(
165
- self,
166
- actions: CLA | list[CLA] | None = None,
167
- verbose: bool = False,
168
- ) -> tuple[Observation, float, bool, dict[str, Any]]:
169
- """Execute a step in the environment.
170
-
171
- Args:
172
- action: The action to execute
173
-
174
- Returns:
175
- Any: Result of the step execution
176
- """
177
- if not isinstance(actions, list) and actions is not None:
178
- actions = [actions]
179
- if actions is None or len(actions) == 0:
180
- actions = []
181
- args = [[action.model_dump() for action in actions]]
182
-
183
- # TODO: Move this into the server side
184
- self._maybe_store_response(actions)
185
-
186
- result, stdout, stderr = await self.client.invoke(
187
- FunctionConfig(function="step", args=args)
188
- )
189
- if verbose:
190
- if stdout:
191
- logger.info("Step produced stdout: %s", stdout.decode())
192
- if stderr:
193
- logger.warning("Step produced stderr: %s", stderr.decode())
194
-
195
- observation = Observation.model_validate(result["observation"], strict=True)
196
-
197
- info = result.get("info", {})
198
-
199
- return observation, 0, False, info
200
-
201
- def _maybe_store_response(self, actions: list[CLA]) -> bool:
202
- """Store the final response into the environment.
203
-
204
- Args:
205
- actions: The action(s) to check
206
-
207
- Returns:
208
- bool: True if the response was submitted, False otherwise
209
- """
210
- if len(actions) > 0 and actions[-1].type == "response":
211
- self.final_response = actions[-1].text
212
- return True
213
- return False
214
-
215
- async def get_urls(self) -> dict[str, Any]:
216
- """Get URLs for the environment.
217
-
218
- Returns:
219
- dict: Dictionary of URLs for accessing the environment
220
- """
221
- data, _, _ = await self.client.invoke(FunctionConfig(function="get_urls", args=[]))
222
-
223
- self.url = data.get("url")
224
- self.live_url = data.get("live_url")
225
-
226
- return {
227
- "url": self.url,
228
- "live_url": self.live_url,
229
- }
230
-
231
- async def close(self) -> None:
232
- """Close the environment.
233
-
234
- This should release any resources and clean up the environment.
235
- """
236
- await self.client.close()
237
-
238
- async def stream(self) -> str | None:
239
- if not self.live_url:
240
- await self.get_urls()
241
- if self.live_url is None:
242
- logger.warning("No live URL found")
243
- return None
244
- return stream(self.live_url)
245
-
246
- async def run(self, agent: Agent, max_steps: int = 27, verbose: bool = True) -> Any:
247
- """Run an agent in the environment.
248
-
249
- Args:
250
- agent: The agent to run
251
- """
252
- if verbose:
253
- logger.info("Running agent in environment...")
254
- obs, _ = await self.reset()
255
- for i in range(max_steps):
256
- action, done = await agent.predict(obs, verbose=verbose)
257
- if verbose:
258
- logger.info(
259
- "Step %d: Action: %s",
260
- i,
261
- [str(a) for a in action] if len(action) > 1 else str(action[0]),
262
- )
263
- obs, reward, terminated, info = await self.step(action)
264
- if verbose:
265
- logger.info("Step %d: Observation: %s", i, obs)
266
- if done or terminated:
267
- break
268
- result = await self.evaluate()
269
- if verbose:
270
- logger.info("Evaluation result: %s", result)
271
- return result
272
-
273
-
274
- def _format_task_metadata(task: Task) -> dict[str, Any]:
275
- metadata = {}
276
- if task.metadata:
277
- for key, value in task.metadata.items():
278
- metadata[str(key)] = value
279
- if task.sensitive_data:
280
- metadata["sensitive_data"] = task.sensitive_data
281
- return metadata
282
-
283
-
284
- def create_remote_config(
285
- env: Environment | None = None,
286
- config: FunctionConfigs | None = None,
287
- function: str | None = None,
288
- metadata: dict[str, Any] | None = None,
289
- ) -> list[FunctionConfig]:
290
- """
291
- Create a remote configuration for setup or evaluate, determining the final
292
- function call structure based on the provided task or explicit config.
293
-
294
- This function orchestrates how setup and evaluate steps defined in a Task
295
- or passed directly are prepared for remote execution via `env._invoke_all`.
296
-
297
- Args:
298
- env: Environment object, potentially containing a task definition.
299
- Used to access `env.task` and `env.final_response`.
300
- config: Direct configuration override (e.g., passed to `env.evaluate(config=...)`).
301
- Can be in various FunctionConfigs formats.
302
- function: The top-level function context, typically "setup" or "evaluate".
303
-
304
- Returns:
305
- list[FunctionConfig]: A list containing a single FunctionConfig object
306
- ready for remote invocation via `client.invoke`.
307
- The specific function/arguments are chosen based on this priority:
308
- 1. Explicit `config` parameter (if provided).
309
- 2. Specific `task` attribute (e.g., `task.evaluate`).
310
- 3. General `task.config` dictionary.
311
- 4. Default private function using `task.id`
312
- (e.g., `private_evaluate(task.id)`).
313
- 5. Base `function` name with minimal/default arguments.
314
-
315
- Logic & Examples (Assuming `function="evaluate"` for examples):
316
-
317
- 1) Explicit `config` provided: The `config` is expanded and becomes the `args`
318
- for the top-level `function` call. If the environment has a final_response,
319
- it's appended to these args.
320
- - Example Input:
321
- `env` (with `final_response="Paris"`)
322
- `config=("contains_text", "Paris")`
323
- `function="evaluate"`
324
- - Example Output:
325
- `[FunctionConfig(function='evaluate', args=[
326
- FunctionConfig(function='contains_text', args=['Paris', 'Paris'])
327
- ])]`
328
-
329
- 2) No explicit `config`, Task has the attribute (e.g., `task.evaluate`):
330
- The Task's attribute value (e.g., `task.evaluate`) is expanded and becomes the `args`
331
- for the top-level `function` call. Task ID is added if present. `final_response` is
332
- appended if present.
333
- - Example Input:
334
- `env` (`task=Task(id="t1", evaluate=("check_answer",), ...)`, `final_response="42"`)
335
- `config=None`
336
- `function="evaluate"`
337
- - Example Output:
338
- `[FunctionConfig(function='evaluate', args=[FunctionConfig(function='check_answer',
339
- args=['42'], id='t1')])]`
340
-
341
- 3) No explicit `config`, no specific Task attribute, Task has `task.config`:
342
- The `task.config` dictionary becomes the single argument for the top-level
343
- `function` call. Task ID is added to the config dict if present. `final_response` is
344
- appended if present.
345
- - Example Input:
346
- `env` (with `task=Task(id="t2", config={"expected": "val"}, ...)`)
347
- `config=None`
348
- `function="evaluate"`
349
- - Example Output:
350
- `[FunctionConfig(function='evaluate', args=[{"expected": "val", "id": "t2"}])]`
351
-
352
- 4) No explicit `config`, no specific Task attribute, no `task.config`, Task has `task.id`:
353
- Calls a private function (`private_<function>`) on the remote end, passing
354
- the `task.id` as the only argument.
355
- - Example Input:
356
- `env` (with `task=Task(id="t3", ...)`)
357
- `config=None`
358
- `function="evaluate"`
359
- - Example Output:
360
- `[FunctionConfig(function='private_evaluate', args=['t3'])]`
361
-
362
- 5) No explicit `config` and no relevant Task info:
363
- Calls the top-level `function` with empty args.
364
- - Example Input:
365
- `env` (with `task=Task(...)`)
366
- `config=None`
367
- `function="evaluate"`
368
- - Example Output:
369
- `[FunctionConfig(function='evaluate', args=[])]`
370
- """
371
- # If no function provided, just expand the config and return it directly
372
- if metadata is None:
373
- metadata = {}
374
- if function is None:
375
- if config:
376
- return expand_config(config)
377
- raise ValueError("Either function or config must be provided")
378
-
379
- # Case 1: Explicit config provided
380
- if config:
381
- if not isinstance(config, dict):
382
- expanded_configs = expand_config(config)
383
- if env and env.final_response and expanded_configs[0].args[0] in LOCAL_EVALUATORS:
384
- # Ensure args is a list before appending
385
- if not isinstance(expanded_configs[0].args, list):
386
- expanded_configs[0].args = [expanded_configs[0].args]
387
- expanded_configs[0].args.append(env.final_response) # for remote responses
388
- return [FunctionConfig(function=function, args=expanded_configs, metadata=metadata)]
389
- else:
390
- return [FunctionConfig(function=function, args=[config], metadata=metadata)]
391
-
392
- # Otherwise, use the environment's task
393
- task = env.task if env else None
394
-
395
- # Must have a task for the remaining cases
396
- if task is None:
397
- raise ValueError("Either task or config must be provided")
398
-
399
- metadata = _format_task_metadata(task)
400
-
401
- # Case 2: Task has the specified function attribute
402
- task_config = getattr(task, function, None)
403
- if task_config:
404
- expanded_configs = expand_config(task_config)
405
- if task.id:
406
- expanded_configs[0].id = task.id # for remote IDs
407
- if env and env.final_response and expanded_configs[0].function in LOCAL_EVALUATORS:
408
- # Ensure args is a list before appending
409
- if not isinstance(expanded_configs[0].args, list):
410
- expanded_configs[0].args = [expanded_configs[0].args]
411
- expanded_configs[0].args.append(env.final_response) # for remote responses
412
- return [FunctionConfig(function=function, args=expanded_configs, metadata=metadata)]
413
-
414
- # Case 3: Check for task.config
415
- if hasattr(task, "config") and task.config:
416
- # Ensure task.config is a dictionary before adding id
417
- final_args = task.config.copy() if isinstance(task.config, dict) else {}
418
- if task.id:
419
- final_args["id"] = task.id # for remote IDs
420
- if env and env.final_response:
421
- # Append response, ensuring args exists and is a list
422
- if "args" not in final_args:
423
- final_args["args"] = []
424
- if not isinstance(final_args["args"], list):
425
- final_args["args"] = [final_args["args"]]
426
- final_args["args"].append(env.final_response)
427
- return [FunctionConfig(function=function, args=[final_args], metadata=metadata)]
428
-
429
- # Case 4: Use task.id
430
- if task.id:
431
- args_list = [task.id]
432
- if env and env.final_response:
433
- args_list.append(env.final_response) # Append final response
434
- return [
435
- FunctionConfig(
436
- function=f"{REMOTE_FUNCTION_PREFIX}{function}",
437
- args=args_list,
438
- metadata=metadata,
439
- )
440
- ]
441
-
442
- # Case 5: No valid configuration found
443
- args_list = []
444
- if env and env.final_response:
445
- args_list.append(env.final_response)
446
- return [FunctionConfig(function=function, args=args_list, metadata=metadata)]