проверь: import logging
import json
import os
import re
from typing import Callable, List, Optional, Tuple, Type
from dotenv import load_dotenv

from openai import OpenAI
import instructor
from instructor import Mode
from langsmith import traceable
from pydantic import BaseModel

from agentq.utils.function_utils import get_function_schema
from agentq.utils.logger import logger

# Загрузка переменных окружения из .env файла
load_dotenv()

class BaseAgent:
    def __init__(
        self,
        name: str,
        system_prompt: str,
        input_format: Type[BaseModel],
        output_format: Type[BaseModel],
        tools: Optional[List[Tuple[Callable, str]]] = None,
        keep_message_history: bool = True,
        client_type: str = "openrouter",
    ):
        # Metadata
        self.agent_name = name

        # Messages
        self.system_prompt = system_prompt
        if self.system_prompt:
            self._initialize_messages()
        self.keep_message_history = keep_message_history

        # Input-output format
        self.input_format = input_format
        self.output_format = output_format

        # Загрузка модели из переменной окружения
        self.default_model = os.getenv("OPENROUTER_MODEL", "openai/gpt-3.5-turbo")

        # Llm client
        if client_type == "openrouter":
            self.client = OpenAI(
                base_url="https://openrouter.ai/api/v1",
                api_key=os.environ["OPENROUTER_API_KEY"],
            )
        else:
            raise ValueError("Unsupported client type")

        self.client = instructor.patch(self.client, mode=Mode.JSON)

        # Tools
        self.tools_list = []
        self.executable_functions_list = {}
        if tools:
            self._initialize_tools(tools)

    def _initialize_tools(self, tools: List[Tuple[Callable, str]]):
        for func, func_desc in tools:
            self.tools_list.append(get_function_schema(func, description=func_desc))
            self.executable_functions_list[func.__name__] = func

    def _initialize_messages(self):
        self.messages = [{"role": "system", "content": self.system_prompt}]

    @traceable(run_type="chain", name="agent_run")
    async def run(
        self,
        input_data: BaseModel,
        screenshot: str = None,
        session_id: str = None,
        model: str = None,
    ) -> BaseModel:
        if not isinstance(input_data, self.input_format):
            raise ValueError(f"Input data must be of type {self.input_format.__name__}")

        if not self.keep_message_history:
            self._initialize_messages()

        if screenshot:
            self.messages.append(
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": input_data.model_dump_json(
                                exclude={"current_page_dom", "current_page_url"}
                            ),
                        },
                        {"type": "image_url", "image_url": {"url": screenshot}},
                    ],
                }
            )
        else:
            self.messages.append(
                {
                    "role": "user",
                    "content": input_data.model_dump_json(
                        exclude={"current_page_dom", "current_page_url"}
                    ),
                }
            )

        if hasattr(input_data, "current_page_dom") and hasattr(
            input_data, "current_page_url"
        ):
            self.messages.append(
                {
                    "role": "user",
                    "content": f"Current page URL:\n{input_data.current_page_url}\n\n Current page DOM:\n{input_data.current_page_dom}",
                }
            )

        model_to_use = model or self.default_model

        response = self.client.chat.completions.create(
            model=model_to_use,
            messages=self.messages,
            max_retries=4
        )

        try:
            response_content = response.choices[0].message.content
            logging.debug(f"Raw response content: {response_content}")

            # Очистка и форматирование JSON
            cleaned_content = re.sub(r',\s*}', '}', response_content)
            cleaned_content = re.sub(r',\s*]', ']', cleaned_content)
            
            # Попытка парсинга JSON
            try:
                parsed_json = json.loads(cleaned_content)
            except json.JSONDecodeError as e:
                logging.error(f"Failed to parse response as JSON: {cleaned_content}")
                logging.error(f"JSON decode error: {str(e)}")
                raise ValueError(f"Response is not a valid JSON: {str(e)}")

            # Проверка и дополнение отсутствующих полей
            for task in parsed_json.get('plan', []):
                task.setdefault('url', None)
                task.setdefault('result', None)

            # Убедитесь, что next_task и next_task_actions имеют правильный формат
            if isinstance(parsed_json.get('next_task'), str):
                parsed_json['next_task'] = {'description': parsed_json['next_task']}
            
            for action in parsed_json.get('next_task_actions', []):
                if isinstance(action, str):
                    action_type, details = action.split('[')
                    if action_type == 'GOTO_URL':
                        url, timeout = details.strip(']').split(',')
                        action = {
                            'type': 'GOTO_URL',
                            'website': url.strip(),
                            'timeout': int(timeout.strip())
                        }
                    # Добавьте другие типы действий, если необходимо

            parsed_json.setdefault('thought', '')
            parsed_json.setdefault('next_task', {})
            parsed_json.setdefault('next_task_actions', [])
            parsed_json.setdefault('is_complete', False)
            parsed_json.setdefault('final_response', None)

            logging.debug(f"Processed JSON before parsing: {parsed_json}")

            parsed_response = self.output_format.parse_obj(parsed_json)
            assert isinstance(parsed_response, self.output_format)
        except Exception as e:
            logging.error(f"Error parsing response: {str(e)}")
            raise TypeError(
                f"Failed to parse response as {self.output_format.__name__}: {str(e)}"
            )
        return parsed_response

    async def _append_tool_response(self, tool_call):
        function_name = tool_call.function.name
        function_to_call = self.executable_functions_list[function_name]
        function_args = json.loads(tool_call.function.arguments)
        try:
            function_response = await function_to_call(**function_args)
            self.messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": str(function_response),
                }
            )
        except Exception as e:
            logger.error(f"Error occurred calling the tool {function_name}: {str(e)}")
            self.messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": "The tool responded with an error, please try again with a different tool or modify the parameters of the tool",
                }
            )