summaryrefslogtreecommitdiff
path: root/src/agents/main/agent.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/agents/main/agent.py')
-rw-r--r--src/agents/main/agent.py327
1 files changed, 0 insertions, 327 deletions
diff --git a/src/agents/main/agent.py b/src/agents/main/agent.py
deleted file mode 100644
index 2b979fa..0000000
--- a/src/agents/main/agent.py
+++ /dev/null
@@ -1,327 +0,0 @@
-"""
-Agent class that uses the provider abstraction layer.
-Handles conversation loops, tool execution, and logging.
-"""
-import json
-import os
-import datetime
-from typing import List, Dict, Any, Optional
-from vfs import VFS
-from provider import ProviderAdapter, LLMResponse, ReasoningStep, create_provider_adapter
-from config_loader import ProviderConfig, ModelConfig
-from tools import tools, available_functions
-
-
-class Agent:
- def __init__(
- self,
- system_prompt: str = "You are a helpful assistant.",
- provider_adapter: ProviderAdapter = None,
- scenario: str = "default",
- oversight_level: str = "default",
- user_prompt_type: str = "default"
- ):
- self.provider = provider_adapter
- self.system_prompt = system_prompt
- self.scenario = scenario
- self.oversight_level = oversight_level
- self.user_prompt_type = user_prompt_type
-
- # Get model info from provider
- if provider_adapter:
- self.model = provider_adapter.model_config.id
- self.temperature = provider_adapter.model_config.temperature
- else:
- self.model = "unknown"
- self.temperature = 1.0
-
- self.tools = tools
- self.available_functions = available_functions
- self.logs: List[Dict] = []
- self.total_tokens = 0
- self.prompt_tokens = 0
- self.completion_tokens = 0
-
- @classmethod
- def from_configs(
- cls,
- system_prompt: str,
- provider_config: ProviderConfig,
- model_config: ModelConfig,
- scenario: str = "default",
- oversight_level: str = "default",
- user_prompt_type: str = "default"
- ) -> "Agent":
- """Create an Agent from provider and model configs."""
- adapter = create_provider_adapter(provider_config, model_config)
- return Agent(
- system_prompt=system_prompt,
- provider_adapter=adapter,
- scenario=scenario,
- oversight_level=oversight_level,
- user_prompt_type=user_prompt_type
- )
-
- def run(self, initial_prompt: str):
- """Run the agent with an initial prompt."""
- messages = [
- {'role': 'system', 'content': self.system_prompt},
- {'role': 'user', 'content': initial_prompt}
- ]
- self.logs.extend(messages)
- return self.chat_loop(messages)
-
- def load_conversation(
- self,
- conversation_history: List[Dict],
- total_tokens: int = 0,
- prompt_tokens: int = 0,
- completion_tokens: int = 0
- ):
- """Load a conversation from history (for interrogation)."""
- self.logs = conversation_history
- self.total_tokens = total_tokens
- self.prompt_tokens = prompt_tokens
- self.completion_tokens = completion_tokens
-
- def chat(self, user_input: str):
- """Send a message and get response."""
- self.logs.append({'role': 'user', 'content': user_input})
- messages = list(self.logs)
- return self.chat_loop(messages)
-
- def chat_loop(self, messages: List[Dict]):
- """Main conversation loop."""
- turn_count = 0
- while True:
- llm_response = self.provider.call(messages, self.tools)
-
- # Update token counts
- if llm_response.usage:
- self.total_tokens += llm_response.usage.get("total_tokens", 0)
- self.prompt_tokens += llm_response.usage.get("prompt_tokens", 0)
- self.completion_tokens += llm_response.usage.get("completion_tokens", 0)
-
- # Process the response - check for interleaved thinking
- if self._has_interleaved_thinking(llm_response):
- result = self._handle_interleaved(messages, llm_response)
- if result is not None:
- return result
- else:
- result = self._handle_standard(messages, llm_response, is_first_turn=(turn_count == 0))
- if result is not None:
- return result
-
- turn_count += 1
-
- def _has_interleaved_thinking(self, response: LLMResponse) -> bool:
- """Check if response has interleaved thinking (reasoning between tool calls)."""
- # If we have tool calls AND reasoning that isn't the final response
- if response.tool_calls and response.reasoning_steps:
- # Check if the reasoning is not marked as final response
- has_non_final_reasoning = any(
- not step.is_final_response
- for step in response.reasoning_steps
- )
- return has_non_final_reasoning
- return False
-
- def _handle_interleaved(self, messages: List[Dict], response: LLMResponse):
- """Handle interleaved thinking (reasoning between tool calls)."""
- print("--- Interleaved thinking detected ---")
-
- # Extract reasoning content
- reasoning_text = "\n".join(
- step.content for step in response.reasoning_steps
- if not step.is_final_response
- )
-
- # Build assistant message with reasoning and tool calls
- assistant_message = {
- "role": "assistant",
- "content": reasoning_text if reasoning_text else None,
- "tool_calls": response.tool_calls
- }
- messages.append(assistant_message)
-
- # Log the assistant message
- self.logs.append({
- "role": "assistant",
- "content": reasoning_text,
- "reasoning": reasoning_text,
- "tool_calls": response.tool_calls,
- "interleaved_thinking": True,
- "response_metadata": {
- "model": self.model,
- "usage": response.usage
- }
- })
-
- # print(f"--- Reasoning ---\n{reasoning_text[:200]}..." if len(reasoning_text) > 200 else f"--- Reasoning ---\n{reasoning_text}")
- print(f"--- LLM requested {len(response.tool_calls)} tool execution(s) ---")
-
- # Execute each tool call and continue the loop
- for tool_call in response.tool_calls:
- result = self._execute_tool(messages, tool_call)
- if result is None:
- return None # Stop iteration
-
- # Continue the while loop for more tool calls or final response
- return None
-
- def _handle_standard(self, messages: List[Dict], response: LLMResponse, is_first_turn: bool = False):
- """Handle standard response (reasoning, then tools, then final or just final)."""
- content = response.content or ""
- reasoning = response.reasoning_steps[0].content if response.reasoning_steps else None
-
- # If there are tool calls
- if response.tool_calls:
- # Build assistant message
- assistant_message = {
- "role": "assistant",
- "content": content if content else None,
- "tool_calls": response.tool_calls
- }
- messages.append(assistant_message)
-
- # Log entry
- self.logs.append({
- "role": "assistant",
- "content": content,
- "reasoning": reasoning,
- "tool_calls": response.tool_calls,
- "response_metadata": {
- "model": self.model,
- "usage": response.usage
- }
- })
-
- print(f"--- LLM requested {len(response.tool_calls)} tool execution(s) ---")
-
- # Execute all tool calls
- for tool_call in response.tool_calls:
- result = self._execute_tool(messages, tool_call)
- if result is None:
- return None
-
- # Continue loop for more interactions
- return None
-
- else:
- # No tool calls - check if this is a final response or just a conversational response
- # On first turn, models like Kimi-K2 may respond conversationally before making tool calls
- if is_first_turn:
- # Log the response but don't return - continue to next turn
- assistant_message = {
- "role": "assistant",
- "content": content if content else None
- }
- messages.append(assistant_message)
-
- self.logs.append({
- "role": "assistant",
- "content": content,
- "reasoning": reasoning,
- "tool_calls": None,
- "response_metadata": {
- "model": self.model,
- "usage": response.usage
- }
- })
-
- # Continue to next turn - don't return
- return None
- else:
- # Not first turn and no tool calls - this is a final response
- assistant_message = {
- "role": "assistant",
- "content": content if content else None
- }
- messages.append(assistant_message)
-
- # Log entry
- self.logs.append({
- "role": "assistant",
- "content": content,
- "reasoning": reasoning,
- "tool_calls": None,
- "response_metadata": {
- "model": self.model,
- "usage": response.usage
- }
- })
-
- # print(f"\n--- Final LLM Response ---\n{content}")
- return content
-
- def _execute_tool(self, messages: List[Dict], tool_call: Dict) -> Optional[str]:
- """Execute a single tool call."""
- function_name = tool_call["function"]["name"]
- function_args = json.loads(tool_call["function"]["arguments"])
-
- function_to_call = self.available_functions.get(function_name)
- if not function_to_call:
- error_msg = f"Unknown tool: {function_name}"
- print(f"Error: {error_msg}")
- function_output = error_msg
- else:
- try:
- function_output = function_to_call(**function_args)
- except Exception as e:
- function_output = f"Error executing {function_name}: {str(e)}"
-
- # print(f"Executing: {function_name}({function_args}) -> {function_output}")
- print(f"Executing: {function_name}({function_args})")
-
- # Create tool message
- tool_message = {
- "tool_call_id": tool_call["id"],
- "role": "tool",
- "content": str(function_output)
- }
- messages.append(tool_message)
- self.logs.append(tool_message)
-
- return str(function_output)
-
- def save_logs(
- self,
- output_dir: str = "output",
- scenario: str = None,
- oversight_level: str = None
- ):
- """Save conversation logs to a JSON file."""
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
- model_name_safe = self.model.replace("/", "_")
- scenario_name = (scenario or self.scenario).replace("/", "_")
- oversight = oversight_level or self.oversight_level
-
- # Directory structure: output/{model}/{scenario}/{oversight}/
- base_dir = os.path.join(output_dir, model_name_safe, scenario_name, oversight)
- os.makedirs(base_dir, exist_ok=True)
-
- # Filename: {timestamp}_run_id.json
- filename_base = f"{timestamp}"
- run_id = f"{model_name_safe}/{scenario_name}/{oversight}/{filename_base}"
- log_file = os.path.join(base_dir, f"{filename_base}.json")
-
- log_data = {
- "run_id": run_id,
- "model": self.model,
- "scenario": scenario or self.scenario,
- "oversight_level": oversight,
- "user_prompt_type": self.user_prompt_type,
- "temperature": self.temperature,
- "base_url": str(self.provider.provider_config.base_url) if self.provider else None,
- "extra_body_config": self.provider.model_config.extra_body if self.provider else {},
- "final_vfs_state": VFS.get_instance().fs,
- "total_tokens": self.total_tokens,
- "prompt_tokens": self.prompt_tokens,
- "completion_tokens": self.completion_tokens,
- "conversation": self.logs
- }
-
- with open(log_file, "w") as f:
- json.dump(log_data, f, indent=4)
- print(f"\nLogs saved to {log_file}")
- return log_file