cua-agent 0.3.2__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 cua-agent might be problematic. Click here for more details.
- agent/__init__.py +21 -12
- agent/__main__.py +21 -0
- agent/adapters/__init__.py +9 -0
- agent/adapters/huggingfacelocal_adapter.py +229 -0
- agent/agent.py +594 -0
- agent/callbacks/__init__.py +19 -0
- agent/callbacks/base.py +153 -0
- agent/callbacks/budget_manager.py +44 -0
- agent/callbacks/image_retention.py +139 -0
- agent/callbacks/logging.py +247 -0
- agent/callbacks/pii_anonymization.py +259 -0
- agent/callbacks/telemetry.py +210 -0
- agent/callbacks/trajectory_saver.py +305 -0
- agent/cli.py +297 -0
- agent/computer_handler.py +107 -0
- agent/decorators.py +90 -0
- agent/loops/__init__.py +11 -0
- agent/loops/anthropic.py +728 -0
- agent/loops/omniparser.py +339 -0
- agent/loops/openai.py +95 -0
- agent/loops/uitars.py +688 -0
- agent/responses.py +207 -0
- agent/telemetry.py +135 -14
- agent/types.py +79 -0
- agent/ui/__init__.py +7 -1
- agent/ui/__main__.py +2 -13
- agent/ui/gradio/__init__.py +6 -19
- agent/ui/gradio/app.py +94 -1313
- agent/ui/gradio/ui_components.py +721 -0
- cua_agent-0.4.0.dist-info/METADATA +424 -0
- cua_agent-0.4.0.dist-info/RECORD +33 -0
- agent/core/__init__.py +0 -27
- agent/core/agent.py +0 -210
- agent/core/base.py +0 -217
- agent/core/callbacks.py +0 -200
- agent/core/experiment.py +0 -249
- agent/core/factory.py +0 -122
- agent/core/messages.py +0 -332
- agent/core/provider_config.py +0 -21
- agent/core/telemetry.py +0 -142
- agent/core/tools/__init__.py +0 -21
- agent/core/tools/base.py +0 -74
- agent/core/tools/bash.py +0 -52
- agent/core/tools/collection.py +0 -46
- agent/core/tools/computer.py +0 -113
- agent/core/tools/edit.py +0 -67
- agent/core/tools/manager.py +0 -56
- agent/core/tools.py +0 -32
- agent/core/types.py +0 -88
- agent/core/visualization.py +0 -197
- agent/providers/__init__.py +0 -4
- agent/providers/anthropic/__init__.py +0 -6
- agent/providers/anthropic/api/client.py +0 -360
- agent/providers/anthropic/api/logging.py +0 -150
- agent/providers/anthropic/api_handler.py +0 -140
- agent/providers/anthropic/callbacks/__init__.py +0 -5
- agent/providers/anthropic/callbacks/manager.py +0 -65
- agent/providers/anthropic/loop.py +0 -568
- agent/providers/anthropic/prompts.py +0 -23
- agent/providers/anthropic/response_handler.py +0 -226
- agent/providers/anthropic/tools/__init__.py +0 -33
- agent/providers/anthropic/tools/base.py +0 -88
- agent/providers/anthropic/tools/bash.py +0 -66
- agent/providers/anthropic/tools/collection.py +0 -34
- agent/providers/anthropic/tools/computer.py +0 -396
- agent/providers/anthropic/tools/edit.py +0 -326
- agent/providers/anthropic/tools/manager.py +0 -54
- agent/providers/anthropic/tools/run.py +0 -42
- agent/providers/anthropic/types.py +0 -16
- agent/providers/anthropic/utils.py +0 -381
- agent/providers/omni/__init__.py +0 -8
- agent/providers/omni/api_handler.py +0 -42
- agent/providers/omni/clients/anthropic.py +0 -103
- agent/providers/omni/clients/base.py +0 -35
- agent/providers/omni/clients/oaicompat.py +0 -195
- agent/providers/omni/clients/ollama.py +0 -122
- agent/providers/omni/clients/openai.py +0 -155
- agent/providers/omni/clients/utils.py +0 -25
- agent/providers/omni/image_utils.py +0 -34
- agent/providers/omni/loop.py +0 -990
- agent/providers/omni/parser.py +0 -307
- agent/providers/omni/prompts.py +0 -64
- agent/providers/omni/tools/__init__.py +0 -30
- agent/providers/omni/tools/base.py +0 -29
- agent/providers/omni/tools/bash.py +0 -74
- agent/providers/omni/tools/computer.py +0 -179
- agent/providers/omni/tools/manager.py +0 -61
- agent/providers/omni/utils.py +0 -236
- agent/providers/openai/__init__.py +0 -6
- agent/providers/openai/api_handler.py +0 -456
- agent/providers/openai/loop.py +0 -472
- agent/providers/openai/response_handler.py +0 -205
- agent/providers/openai/tools/__init__.py +0 -15
- agent/providers/openai/tools/base.py +0 -79
- agent/providers/openai/tools/computer.py +0 -326
- agent/providers/openai/tools/manager.py +0 -106
- agent/providers/openai/types.py +0 -36
- agent/providers/openai/utils.py +0 -98
- agent/providers/uitars/__init__.py +0 -1
- agent/providers/uitars/clients/base.py +0 -35
- agent/providers/uitars/clients/mlxvlm.py +0 -263
- agent/providers/uitars/clients/oaicompat.py +0 -214
- agent/providers/uitars/loop.py +0 -660
- agent/providers/uitars/prompts.py +0 -63
- agent/providers/uitars/tools/__init__.py +0 -1
- agent/providers/uitars/tools/computer.py +0 -283
- agent/providers/uitars/tools/manager.py +0 -60
- agent/providers/uitars/utils.py +0 -264
- cua_agent-0.3.2.dist-info/METADATA +0 -295
- cua_agent-0.3.2.dist-info/RECORD +0 -87
- {cua_agent-0.3.2.dist-info → cua_agent-0.4.0.dist-info}/WHEEL +0 -0
- {cua_agent-0.3.2.dist-info → cua_agent-0.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,568 +0,0 @@
|
|
|
1
|
-
"""Anthropic-specific agent loop implementation."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import asyncio
|
|
5
|
-
from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple, cast
|
|
6
|
-
from anthropic.types.beta import (
|
|
7
|
-
BetaMessage,
|
|
8
|
-
BetaMessageParam,
|
|
9
|
-
BetaTextBlock,
|
|
10
|
-
BetaContentBlockParam,
|
|
11
|
-
)
|
|
12
|
-
import base64
|
|
13
|
-
from datetime import datetime
|
|
14
|
-
|
|
15
|
-
# Computer
|
|
16
|
-
from computer import Computer
|
|
17
|
-
|
|
18
|
-
# Base imports
|
|
19
|
-
from ...core.base import BaseLoop
|
|
20
|
-
from ...core.messages import StandardMessageManager, ImageRetentionConfig
|
|
21
|
-
from ...core.types import AgentResponse
|
|
22
|
-
|
|
23
|
-
# Anthropic provider-specific imports
|
|
24
|
-
from .api.client import AnthropicClientFactory, BaseAnthropicClient
|
|
25
|
-
from .tools.manager import ToolManager
|
|
26
|
-
from .prompts import SYSTEM_PROMPT
|
|
27
|
-
from .types import LLMProvider
|
|
28
|
-
from .tools import ToolResult
|
|
29
|
-
from .utils import to_anthropic_format, to_agent_response_format
|
|
30
|
-
|
|
31
|
-
# Import the new modules we created
|
|
32
|
-
from .api_handler import AnthropicAPIHandler
|
|
33
|
-
from .response_handler import AnthropicResponseHandler
|
|
34
|
-
from .callbacks.manager import CallbackManager
|
|
35
|
-
|
|
36
|
-
# Constants
|
|
37
|
-
COMPUTER_USE_BETA_FLAG = "computer-use-2025-01-24"
|
|
38
|
-
PROMPT_CACHING_BETA_FLAG = "prompt-caching-2024-07-31"
|
|
39
|
-
|
|
40
|
-
logger = logging.getLogger(__name__)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class AnthropicLoop(BaseLoop):
|
|
44
|
-
"""Anthropic-specific implementation of the agent loop.
|
|
45
|
-
|
|
46
|
-
This class extends BaseLoop to provide specialized support for Anthropic's Claude models
|
|
47
|
-
with their unique tool-use capabilities, custom message formatting, and
|
|
48
|
-
callback-driven approach to handling responses.
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
###########################################
|
|
52
|
-
# INITIALIZATION AND CONFIGURATION
|
|
53
|
-
###########################################
|
|
54
|
-
|
|
55
|
-
def __init__(
|
|
56
|
-
self,
|
|
57
|
-
api_key: str,
|
|
58
|
-
computer: Computer,
|
|
59
|
-
model: str = "claude-3-7-sonnet-20250219",
|
|
60
|
-
only_n_most_recent_images: Optional[int] = 2,
|
|
61
|
-
base_dir: Optional[str] = "trajectories",
|
|
62
|
-
max_retries: int = 3,
|
|
63
|
-
retry_delay: float = 1.0,
|
|
64
|
-
save_trajectory: bool = True,
|
|
65
|
-
**kwargs,
|
|
66
|
-
):
|
|
67
|
-
"""Initialize the Anthropic loop.
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
api_key: Anthropic API key
|
|
71
|
-
model: Model name (fixed to claude-3-7-sonnet-20250219)
|
|
72
|
-
computer: Computer instance
|
|
73
|
-
only_n_most_recent_images: Maximum number of recent screenshots to include in API requests
|
|
74
|
-
base_dir: Base directory for saving experiment data
|
|
75
|
-
max_retries: Maximum number of retries for API calls
|
|
76
|
-
retry_delay: Delay between retries in seconds
|
|
77
|
-
save_trajectory: Whether to save trajectory data
|
|
78
|
-
"""
|
|
79
|
-
# Initialize base class with core config
|
|
80
|
-
super().__init__(
|
|
81
|
-
computer=computer,
|
|
82
|
-
model=model,
|
|
83
|
-
api_key=api_key,
|
|
84
|
-
max_retries=max_retries,
|
|
85
|
-
retry_delay=retry_delay,
|
|
86
|
-
base_dir=base_dir,
|
|
87
|
-
save_trajectory=save_trajectory,
|
|
88
|
-
only_n_most_recent_images=only_n_most_recent_images,
|
|
89
|
-
**kwargs,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# Initialize message manager
|
|
93
|
-
self.message_manager = StandardMessageManager(
|
|
94
|
-
config=ImageRetentionConfig(num_images_to_keep=only_n_most_recent_images)
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
# Anthropic-specific attributes
|
|
98
|
-
self.provider = LLMProvider.ANTHROPIC
|
|
99
|
-
self.client = None
|
|
100
|
-
self.retry_count = 0
|
|
101
|
-
self.tool_manager = None
|
|
102
|
-
self.callback_manager = None
|
|
103
|
-
self.queue = asyncio.Queue() # Initialize queue
|
|
104
|
-
self.loop_task = None # Store the loop task for cancellation
|
|
105
|
-
|
|
106
|
-
# Initialize handlers
|
|
107
|
-
self.api_handler = AnthropicAPIHandler(self)
|
|
108
|
-
self.response_handler = AnthropicResponseHandler(self)
|
|
109
|
-
|
|
110
|
-
###########################################
|
|
111
|
-
# CLIENT INITIALIZATION - IMPLEMENTING ABSTRACT METHOD
|
|
112
|
-
###########################################
|
|
113
|
-
|
|
114
|
-
async def initialize_client(self) -> None:
|
|
115
|
-
"""Initialize the Anthropic API client and tools.
|
|
116
|
-
|
|
117
|
-
Implements abstract method from BaseLoop to set up the Anthropic-specific
|
|
118
|
-
client, tool manager, message manager, and callback handlers.
|
|
119
|
-
"""
|
|
120
|
-
try:
|
|
121
|
-
logger.info(f"Initializing Anthropic client with model {self.model}...")
|
|
122
|
-
|
|
123
|
-
# Initialize client
|
|
124
|
-
self.client = AnthropicClientFactory.create_client(
|
|
125
|
-
provider=self.provider, api_key=self.api_key, model=self.model
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
# Initialize callback manager with our callback handlers
|
|
129
|
-
self.callback_manager = CallbackManager(
|
|
130
|
-
content_callback=self._handle_content,
|
|
131
|
-
tool_callback=self._handle_tool_result,
|
|
132
|
-
api_callback=self._handle_api_interaction,
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
# Initialize tool manager
|
|
136
|
-
self.tool_manager = ToolManager(self.computer)
|
|
137
|
-
await self.tool_manager.initialize()
|
|
138
|
-
|
|
139
|
-
logger.info(f"Initialized Anthropic client with model {self.model}")
|
|
140
|
-
except Exception as e:
|
|
141
|
-
logger.error(f"Error initializing Anthropic client: {str(e)}")
|
|
142
|
-
self.client = None
|
|
143
|
-
raise RuntimeError(f"Failed to initialize Anthropic client: {str(e)}")
|
|
144
|
-
|
|
145
|
-
###########################################
|
|
146
|
-
# MAIN LOOP - IMPLEMENTING ABSTRACT METHOD
|
|
147
|
-
###########################################
|
|
148
|
-
|
|
149
|
-
async def run(self, messages: List[Dict[str, Any]]) -> AsyncGenerator[AgentResponse, None]:
|
|
150
|
-
"""Run the agent loop with provided messages.
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
messages: List of message objects in standard OpenAI format
|
|
154
|
-
|
|
155
|
-
Yields:
|
|
156
|
-
Agent response format
|
|
157
|
-
"""
|
|
158
|
-
try:
|
|
159
|
-
logger.info("Starting Anthropic loop run")
|
|
160
|
-
|
|
161
|
-
# Create queue for response streaming
|
|
162
|
-
queue = asyncio.Queue()
|
|
163
|
-
|
|
164
|
-
# Ensure client is initialized
|
|
165
|
-
if self.client is None or self.tool_manager is None:
|
|
166
|
-
logger.info("Initializing client...")
|
|
167
|
-
await self.initialize_client()
|
|
168
|
-
if self.client is None:
|
|
169
|
-
raise RuntimeError("Failed to initialize client")
|
|
170
|
-
logger.info("Client initialized successfully")
|
|
171
|
-
|
|
172
|
-
# Start loop in background task
|
|
173
|
-
self.loop_task = asyncio.create_task(self._run_loop(queue, messages))
|
|
174
|
-
|
|
175
|
-
# Process and yield messages as they arrive
|
|
176
|
-
while True:
|
|
177
|
-
try:
|
|
178
|
-
item = await queue.get()
|
|
179
|
-
if item is None: # Stop signal
|
|
180
|
-
break
|
|
181
|
-
yield item
|
|
182
|
-
queue.task_done()
|
|
183
|
-
except Exception as e:
|
|
184
|
-
logger.error(f"Error processing queue item: {str(e)}")
|
|
185
|
-
continue
|
|
186
|
-
|
|
187
|
-
# Wait for loop to complete
|
|
188
|
-
await self.loop_task
|
|
189
|
-
|
|
190
|
-
# Send completion message
|
|
191
|
-
yield {
|
|
192
|
-
"role": "assistant",
|
|
193
|
-
"content": "Task completed successfully.",
|
|
194
|
-
"metadata": {"title": "✅ Complete"},
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
except Exception as e:
|
|
198
|
-
logger.error(f"Error executing task: {str(e)}")
|
|
199
|
-
yield {
|
|
200
|
-
"role": "assistant",
|
|
201
|
-
"content": f"Error: {str(e)}",
|
|
202
|
-
"metadata": {"title": "❌ Error"},
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async def cancel(self) -> None:
|
|
206
|
-
"""Cancel the currently running agent loop task.
|
|
207
|
-
|
|
208
|
-
This method stops the ongoing processing in the agent loop
|
|
209
|
-
by cancelling the loop_task if it exists and is running.
|
|
210
|
-
"""
|
|
211
|
-
if self.loop_task and not self.loop_task.done():
|
|
212
|
-
logger.info("Cancelling Anthropic loop task")
|
|
213
|
-
self.loop_task.cancel()
|
|
214
|
-
try:
|
|
215
|
-
# Wait for the task to be cancelled with a timeout
|
|
216
|
-
await asyncio.wait_for(self.loop_task, timeout=2.0)
|
|
217
|
-
except asyncio.TimeoutError:
|
|
218
|
-
logger.warning("Timeout while waiting for loop task to cancel")
|
|
219
|
-
except asyncio.CancelledError:
|
|
220
|
-
logger.info("Loop task cancelled successfully")
|
|
221
|
-
except Exception as e:
|
|
222
|
-
logger.error(f"Error while cancelling loop task: {str(e)}")
|
|
223
|
-
finally:
|
|
224
|
-
# Put None in the queue to signal any waiting consumers to stop
|
|
225
|
-
await self.queue.put(None)
|
|
226
|
-
logger.info("Anthropic loop task cancelled")
|
|
227
|
-
else:
|
|
228
|
-
logger.info("No active Anthropic loop task to cancel")
|
|
229
|
-
|
|
230
|
-
###########################################
|
|
231
|
-
# AGENT LOOP IMPLEMENTATION
|
|
232
|
-
###########################################
|
|
233
|
-
|
|
234
|
-
async def _run_loop(self, queue: asyncio.Queue, messages: List[Dict[str, Any]]) -> None:
|
|
235
|
-
"""Run the agent loop with provided messages.
|
|
236
|
-
|
|
237
|
-
Args:
|
|
238
|
-
queue: Queue for response streaming
|
|
239
|
-
messages: List of messages in standard OpenAI format
|
|
240
|
-
"""
|
|
241
|
-
try:
|
|
242
|
-
while True:
|
|
243
|
-
# Capture screenshot
|
|
244
|
-
try:
|
|
245
|
-
# Take screenshot - always returns raw PNG bytes
|
|
246
|
-
screenshot = await self.computer.interface.screenshot()
|
|
247
|
-
logger.info("Screenshot captured successfully")
|
|
248
|
-
|
|
249
|
-
# Convert PNG bytes to base64
|
|
250
|
-
base64_image = base64.b64encode(screenshot).decode("utf-8")
|
|
251
|
-
logger.info(f"Screenshot converted to base64 (size: {len(base64_image)} bytes)")
|
|
252
|
-
|
|
253
|
-
# Save screenshot if requested
|
|
254
|
-
if self.save_trajectory and self.experiment_manager:
|
|
255
|
-
try:
|
|
256
|
-
self._save_screenshot(base64_image, action_type="state")
|
|
257
|
-
logger.info("Screenshot saved to trajectory")
|
|
258
|
-
except Exception as e:
|
|
259
|
-
logger.error(f"Error saving screenshot: {str(e)}")
|
|
260
|
-
|
|
261
|
-
# Create screenshot message
|
|
262
|
-
screen_info_msg = {
|
|
263
|
-
"role": "user",
|
|
264
|
-
"content": [
|
|
265
|
-
{
|
|
266
|
-
"type": "image",
|
|
267
|
-
"source": {
|
|
268
|
-
"type": "base64",
|
|
269
|
-
"media_type": "image/png",
|
|
270
|
-
"data": base64_image,
|
|
271
|
-
},
|
|
272
|
-
}
|
|
273
|
-
],
|
|
274
|
-
}
|
|
275
|
-
# Add screenshot to messages
|
|
276
|
-
messages.append(screen_info_msg)
|
|
277
|
-
logger.info("Screenshot message added to conversation")
|
|
278
|
-
|
|
279
|
-
except Exception as e:
|
|
280
|
-
logger.error(f"Error capturing or processing screenshot: {str(e)}")
|
|
281
|
-
raise
|
|
282
|
-
|
|
283
|
-
# Create new turn directory for this API call
|
|
284
|
-
self._create_turn_dir()
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
# Apply image retention policy
|
|
288
|
-
self.message_manager.messages = messages.copy()
|
|
289
|
-
prepared_messages = self.message_manager.get_messages()
|
|
290
|
-
# Convert standard messages to Anthropic format using utility function
|
|
291
|
-
anthropic_messages, system_content = to_anthropic_format(prepared_messages)
|
|
292
|
-
|
|
293
|
-
# Use API handler to make API call with Anthropic format
|
|
294
|
-
response = await self.api_handler.make_api_call(
|
|
295
|
-
messages=cast(List[BetaMessageParam], anthropic_messages),
|
|
296
|
-
system_prompt=system_content or SYSTEM_PROMPT,
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
# Use response handler to handle the response and get new messages
|
|
300
|
-
new_messages, should_continue = await self.response_handler.handle_response(
|
|
301
|
-
response, messages
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Add new messages to the parent's message history
|
|
305
|
-
messages.extend(new_messages)
|
|
306
|
-
|
|
307
|
-
openai_compatible_response = await to_agent_response_format(
|
|
308
|
-
response,
|
|
309
|
-
messages,
|
|
310
|
-
model=self.model,
|
|
311
|
-
)
|
|
312
|
-
# Log standardized response for ease of parsing
|
|
313
|
-
self._log_api_call("agent_response", request=None, response=openai_compatible_response)
|
|
314
|
-
await queue.put(openai_compatible_response)
|
|
315
|
-
|
|
316
|
-
if not should_continue:
|
|
317
|
-
break
|
|
318
|
-
|
|
319
|
-
# Signal completion
|
|
320
|
-
await queue.put(None)
|
|
321
|
-
|
|
322
|
-
except Exception as e:
|
|
323
|
-
logger.error(f"Error in _run_loop: {str(e)}")
|
|
324
|
-
await queue.put(
|
|
325
|
-
{
|
|
326
|
-
"role": "assistant",
|
|
327
|
-
"content": f"Error in agent loop: {str(e)}",
|
|
328
|
-
"metadata": {"title": "❌ Error"},
|
|
329
|
-
}
|
|
330
|
-
)
|
|
331
|
-
await queue.put(None)
|
|
332
|
-
|
|
333
|
-
###########################################
|
|
334
|
-
# RESPONSE AND CALLBACK HANDLING
|
|
335
|
-
###########################################
|
|
336
|
-
|
|
337
|
-
async def _handle_response(self, response: BetaMessage, messages: List[Dict[str, Any]]) -> bool:
|
|
338
|
-
"""Handle a response from the Anthropic API.
|
|
339
|
-
|
|
340
|
-
Args:
|
|
341
|
-
response: The response from the Anthropic API
|
|
342
|
-
messages: The message history
|
|
343
|
-
|
|
344
|
-
Returns:
|
|
345
|
-
bool: Whether to continue the conversation
|
|
346
|
-
"""
|
|
347
|
-
try:
|
|
348
|
-
# Convert response to standard format
|
|
349
|
-
openai_compatible_response = await to_agent_response_format(
|
|
350
|
-
response,
|
|
351
|
-
messages,
|
|
352
|
-
model=self.model,
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
# Put the response on the queue
|
|
356
|
-
await self.queue.put(openai_compatible_response)
|
|
357
|
-
|
|
358
|
-
if self.callback_manager is None:
|
|
359
|
-
raise RuntimeError(
|
|
360
|
-
"Callback manager not initialized. Call initialize_client() first."
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
# Handle tool use blocks and collect ALL results before adding to messages
|
|
364
|
-
tool_result_content = []
|
|
365
|
-
has_tool_use = False
|
|
366
|
-
|
|
367
|
-
for content_block in response.content:
|
|
368
|
-
# Notify callback of content
|
|
369
|
-
self.callback_manager.on_content(cast(BetaContentBlockParam, content_block))
|
|
370
|
-
|
|
371
|
-
# Handle tool use - carefully check and access attributes
|
|
372
|
-
if hasattr(content_block, "type") and content_block.type == "tool_use":
|
|
373
|
-
has_tool_use = True
|
|
374
|
-
if self.tool_manager is None:
|
|
375
|
-
raise RuntimeError(
|
|
376
|
-
"Tool manager not initialized. Call initialize_client() first."
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
# Safely get attributes
|
|
380
|
-
tool_name = getattr(content_block, "name", "")
|
|
381
|
-
tool_input = getattr(content_block, "input", {})
|
|
382
|
-
tool_id = getattr(content_block, "id", "")
|
|
383
|
-
|
|
384
|
-
result = await self.tool_manager.execute_tool(
|
|
385
|
-
name=tool_name,
|
|
386
|
-
tool_input=cast(Dict[str, Any], tool_input),
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
# Create tool result
|
|
390
|
-
tool_result = self._make_tool_result(cast(ToolResult, result), tool_id)
|
|
391
|
-
tool_result_content.append(tool_result)
|
|
392
|
-
|
|
393
|
-
# Notify callback of tool result
|
|
394
|
-
self.callback_manager.on_tool_result(cast(ToolResult, result), tool_id)
|
|
395
|
-
|
|
396
|
-
# If we had any tool_use blocks, we MUST add the tool_result message
|
|
397
|
-
# even if there were errors or no actual results
|
|
398
|
-
if has_tool_use:
|
|
399
|
-
# If somehow we have no tool results but had tool uses, add synthetic error results
|
|
400
|
-
if not tool_result_content:
|
|
401
|
-
logger.warning(
|
|
402
|
-
"Had tool uses but no tool results, adding synthetic error results"
|
|
403
|
-
)
|
|
404
|
-
for content_block in response.content:
|
|
405
|
-
if hasattr(content_block, "type") and content_block.type == "tool_use":
|
|
406
|
-
tool_id = getattr(content_block, "id", "")
|
|
407
|
-
if tool_id:
|
|
408
|
-
tool_result_content.append(
|
|
409
|
-
{
|
|
410
|
-
"type": "tool_result",
|
|
411
|
-
"tool_use_id": tool_id,
|
|
412
|
-
"content": {
|
|
413
|
-
"type": "error",
|
|
414
|
-
"text": "Tool execution was skipped or failed",
|
|
415
|
-
},
|
|
416
|
-
"is_error": True,
|
|
417
|
-
}
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
# Add ALL tool results as a SINGLE user message
|
|
421
|
-
messages.append({"role": "user", "content": tool_result_content})
|
|
422
|
-
return True
|
|
423
|
-
else:
|
|
424
|
-
# No tool uses, we're done
|
|
425
|
-
self.callback_manager.on_content({"type": "text", "text": "<DONE>"})
|
|
426
|
-
return False
|
|
427
|
-
|
|
428
|
-
except Exception as e:
|
|
429
|
-
logger.error(f"Error handling response: {str(e)}")
|
|
430
|
-
messages.append(
|
|
431
|
-
{
|
|
432
|
-
"role": "assistant",
|
|
433
|
-
"content": f"Error: {str(e)}",
|
|
434
|
-
}
|
|
435
|
-
)
|
|
436
|
-
return False
|
|
437
|
-
|
|
438
|
-
def _response_to_blocks(self, response: BetaMessage) -> List[Dict[str, Any]]:
|
|
439
|
-
"""Convert Anthropic API response to standard blocks format.
|
|
440
|
-
|
|
441
|
-
Args:
|
|
442
|
-
response: API response message
|
|
443
|
-
|
|
444
|
-
Returns:
|
|
445
|
-
List of content blocks in standard format
|
|
446
|
-
"""
|
|
447
|
-
result = []
|
|
448
|
-
for block in response.content:
|
|
449
|
-
if isinstance(block, BetaTextBlock):
|
|
450
|
-
result.append({"type": "text", "text": block.text})
|
|
451
|
-
elif hasattr(block, "type") and block.type == "tool_use":
|
|
452
|
-
# Safely access attributes after confirming it's a tool_use
|
|
453
|
-
result.append(
|
|
454
|
-
{
|
|
455
|
-
"type": "tool_use",
|
|
456
|
-
"id": getattr(block, "id", ""),
|
|
457
|
-
"name": getattr(block, "name", ""),
|
|
458
|
-
"input": getattr(block, "input", {}),
|
|
459
|
-
}
|
|
460
|
-
)
|
|
461
|
-
else:
|
|
462
|
-
# For other block types, convert to dict
|
|
463
|
-
block_dict = {}
|
|
464
|
-
for key, value in vars(block).items():
|
|
465
|
-
if not key.startswith("_"):
|
|
466
|
-
block_dict[key] = value
|
|
467
|
-
result.append(block_dict)
|
|
468
|
-
|
|
469
|
-
return result
|
|
470
|
-
|
|
471
|
-
def _make_tool_result(self, result: ToolResult, tool_use_id: str) -> Dict[str, Any]:
|
|
472
|
-
"""Convert a tool result to standard format.
|
|
473
|
-
|
|
474
|
-
Args:
|
|
475
|
-
result: Tool execution result
|
|
476
|
-
tool_use_id: ID of the tool use
|
|
477
|
-
|
|
478
|
-
Returns:
|
|
479
|
-
Formatted tool result
|
|
480
|
-
"""
|
|
481
|
-
if result.content:
|
|
482
|
-
return {
|
|
483
|
-
"type": "tool_result",
|
|
484
|
-
"content": result.content,
|
|
485
|
-
"tool_use_id": tool_use_id,
|
|
486
|
-
"is_error": bool(result.error),
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
tool_result_content = []
|
|
490
|
-
is_error = False
|
|
491
|
-
|
|
492
|
-
if result.error:
|
|
493
|
-
is_error = True
|
|
494
|
-
tool_result_content = [
|
|
495
|
-
{
|
|
496
|
-
"type": "text",
|
|
497
|
-
"text": self._maybe_prepend_system_tool_result(result, result.error),
|
|
498
|
-
}
|
|
499
|
-
]
|
|
500
|
-
else:
|
|
501
|
-
if result.output:
|
|
502
|
-
tool_result_content.append(
|
|
503
|
-
{
|
|
504
|
-
"type": "text",
|
|
505
|
-
"text": self._maybe_prepend_system_tool_result(result, result.output),
|
|
506
|
-
}
|
|
507
|
-
)
|
|
508
|
-
if result.base64_image:
|
|
509
|
-
tool_result_content.append(
|
|
510
|
-
{
|
|
511
|
-
"type": "image_url",
|
|
512
|
-
"image_url": {"url": f"data:image/png;base64,{result.base64_image}"},
|
|
513
|
-
}
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
return {
|
|
517
|
-
"type": "tool_result",
|
|
518
|
-
"content": tool_result_content,
|
|
519
|
-
"tool_use_id": tool_use_id,
|
|
520
|
-
"is_error": is_error,
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
def _maybe_prepend_system_tool_result(self, result: ToolResult, result_text: str) -> str:
|
|
524
|
-
"""Prepend system information to tool result if available.
|
|
525
|
-
|
|
526
|
-
Args:
|
|
527
|
-
result: Tool execution result
|
|
528
|
-
result_text: Text to prepend to
|
|
529
|
-
|
|
530
|
-
Returns:
|
|
531
|
-
Text with system information prepended if available
|
|
532
|
-
"""
|
|
533
|
-
if result.system:
|
|
534
|
-
result_text = f"<s>{result.system}</s>\n{result_text}"
|
|
535
|
-
return result_text
|
|
536
|
-
|
|
537
|
-
###########################################
|
|
538
|
-
# CALLBACK HANDLERS
|
|
539
|
-
###########################################
|
|
540
|
-
|
|
541
|
-
def _handle_content(self, content):
|
|
542
|
-
"""Handle content updates from the assistant."""
|
|
543
|
-
if content.get("type") == "text":
|
|
544
|
-
text = content.get("text", "")
|
|
545
|
-
if text == "<DONE>":
|
|
546
|
-
return
|
|
547
|
-
logger.info(f"Assistant: {text}")
|
|
548
|
-
|
|
549
|
-
def _handle_tool_result(self, result, tool_id):
|
|
550
|
-
"""Handle tool execution results."""
|
|
551
|
-
if result.error:
|
|
552
|
-
logger.error(f"Tool {tool_id} error: {result.error}")
|
|
553
|
-
else:
|
|
554
|
-
logger.info(f"Tool {tool_id} output: {result.output}")
|
|
555
|
-
|
|
556
|
-
def _handle_api_interaction(
|
|
557
|
-
self, request: Any, response: Any, error: Optional[Exception]
|
|
558
|
-
) -> None:
|
|
559
|
-
"""Handle API interactions."""
|
|
560
|
-
if error:
|
|
561
|
-
logger.error(f"API error: {error}")
|
|
562
|
-
self._log_api_call("error", request, error=error)
|
|
563
|
-
else:
|
|
564
|
-
logger.debug(f"API request: {request}")
|
|
565
|
-
if response:
|
|
566
|
-
self._log_api_call("response", request, response)
|
|
567
|
-
else:
|
|
568
|
-
self._log_api_call("request", request)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""System prompts for Anthropic provider."""
|
|
2
|
-
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
import platform
|
|
5
|
-
|
|
6
|
-
today = datetime.today()
|
|
7
|
-
today = f"{today.strftime('%A, %B')} {today.day}, {today.year}"
|
|
8
|
-
|
|
9
|
-
SYSTEM_PROMPT = f"""<SYSTEM_CAPABILITY>
|
|
10
|
-
* You are utilising a macOS virtual machine using ARM architecture with internet access and Safari as default browser.
|
|
11
|
-
* You can feel free to install macOS applications with your bash tool. Use curl instead of wget.
|
|
12
|
-
* Using bash tool you can start GUI applications. GUI apps run with bash tool will appear within your desktop environment, but they may take some time to appear. Take a screenshot to confirm it did.
|
|
13
|
-
* When using your bash tool with commands that are expected to output very large quantities of text, redirect into a tmp file and use str_replace_editor or `grep -n -B <lines before> -A <lines after> <query> <filename>` to confirm output.
|
|
14
|
-
* When viewing a page it can be helpful to zoom out so that you can see everything on the page. Either that, or make sure you scroll down to see everything before deciding something isn't available.
|
|
15
|
-
* When using your computer function calls, they take a while to run and send back to you. Where possible/feasible, try to chain multiple of these calls all into one function calls request.
|
|
16
|
-
* The current date is {today}.
|
|
17
|
-
</SYSTEM_CAPABILITY>
|
|
18
|
-
|
|
19
|
-
<IMPORTANT>
|
|
20
|
-
* Plan at maximum 1 step each time, and evaluate the result of each step before proceeding. Hold back if you're not sure about the result of the step.
|
|
21
|
-
* If you're not sure about the location of an application, use start the app using the bash tool.
|
|
22
|
-
* If the item you are looking at is a pdf, if after taking a single screenshot of the pdf it seems that you want to read the entire document instead of trying to continue to read the pdf from your screenshots + navigation, determine the URL, use curl to download the pdf, install and use pdftotext to convert it to a text file, and then read that text file directly with your StrReplaceEditTool.
|
|
23
|
-
</IMPORTANT>"""
|