ort asyncio
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage
from langchain.prompts import ChatPromptTemplate
import faiss
import numpy as np

@dataclass
class AgentConfig:
model_name: str
temperature: float = 0
max_tokens: int = 1000
top_k: int = 5

@dataclass
class Message:
role: str
content: str

@dataclass
class Tool:
name: str
description: str
func: callable

class VectorStore:
def init(self, dimension: int):
self.index = faiss.IndexFlatL2(dimension)
self.texts: List[str] = []

def add(self, text: str, embedding: np.ndarray):
    self.index.add(embedding.reshape(1, -1))
    self.texts.append(text)

def search(self, query_embedding: np.ndarray, k: int) -> List[str]:
    _, indices = self.index.search(query_embedding.reshape(1, -1), k)
    return [self.texts[i] for i in indices[0] if i != -1]

class Agent:
def init(self, config: AgentConfig):
self.config = config
self.model = ChatOpenAI(model_name=config.model_name, temperature=config.temperature)
self.memory = VectorStore(dimension=1536) # Assuming OpenAI's embedding dimension
self.tools: Dict[str, Tool] = {}
self.messages: List[Message] = []

def add_tool(self, tool: Tool):
    self.tools[tool.name] = tool

def embed(self, text: str) -> np.ndarray:
    # Simple embedding function (replace with a more sophisticated one if needed)
    return np.array([hash(word) % 1536 for word in text.split()]).mean(axis=0)

async def process_message(self, user_message: str) -> str:
    self.messages.append(Message(role="user", content=user_message))
    
    # Search memory
    query_embedding = self.embed(user_message)
    relevant_memories = self.memory.search(query_embedding, self.config.top_k)
    
    # Prepare conversation history
    history = [SystemMessage(content="You are a helpful AI assistant.")]
    history.extend([HumanMessage(content=m.content) if m.role == "user" else AIMessage(content=m.content) for m in self.messages[-5:]])
    
    # Add relevant memories and available tools to the prompt
    memory_prompt = "\n".join(relevant_memories)
    tools_prompt = "\n".join([f"{name}: {tool.description}" for name, tool in self.tools.items()])
    prompt = ChatPromptTemplate.from_messages([
        SystemMessage(content=f"Relevant information:\n{memory_prompt}\n\nAvailable tools:\n{tools_prompt}"),
        *history
    ])

    # Generate response
    response = await self.model.agenerate(messages=[prompt])
    ai_message = response.generations[0][0].text

    # Check for tool usage
    if any(tool_name in ai_message for tool_name in self.tools):
        for tool_name, tool in self.tools.items():
            if tool_name in ai_message:
                ai_message += f"\n[Tool Output: {tool.func()}]"

    self.messages.append(Message(role="assistant", content=ai_message))
    
    # Add to memory
    self.memory.add(user_message, self.embed(user_message))
    self.memory.add(ai_message, self.embed(ai_message))

    return ai_message

async def main():
config = AgentConfig(model_name="gpt-3.5-turbo")
agent = Agent(config)

# Add some example tools
agent.add_tool(Tool("search", "Search the internet", lambda: "Search results..."))
agent.add_tool(Tool("calculator", "Perform calculations", lambda: "Calculation result..."))

while True:
    user_input = input("You: ")
    if user_input.lower() == "exit":
        break
    response = await agent.process_message(user_input)
    print(f"AI: {response}")

if name == "main":
asyncio.run(main()) оптимизируй код и улучши

Certainly! I'll help optimize and enhance your AI agent code by addressing several areas:

    Improved Embedding Function: Replace the simplistic hash-based embedding with a proper embedding model using LangChain's OpenAIEmbeddings for better semantic understanding.

    Asynchronous Tool Functions: Modify tool functions to be asynchronous to fully leverage asyncio.

    Enhanced Prompt Construction: Streamline prompt creation by integrating relevant memories and tools more effectively.

    Robust Tool Invocation: Implement a more reliable method to detect and execute tool usage requests from the AI's response.

    Memory Management: Limit the memory size to prevent unbounded growth and ensure efficient retrieval.

    Error Handling and Type Hints: Add comprehensive type hints and error handling for better code reliability and readability.

Here's the optimized and improved version of your AI agent:

import asyncio
from dataclasses import dataclass, field
from typing import List, Dict, Callable, Any, Optional
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage
from langchain.prompts import ChatPromptTemplate
from langchain.embeddings import OpenAIEmbeddings
import faiss
import numpy as np

@dataclass
class AgentConfig:
    model_name: str = "gpt-3.5-turbo"
    temperature: float = 0.0
    max_tokens: int = 1000
    top_k: int = 5
    embedding_model: str = "text-embedding-ada-002"  # Example embedding model
    memory_limit: int = 1000  # Maximum number of memories to store

@dataclass
class Message:
    role: str
    content: str

@dataclass
class Tool:
    name: str
    description: str
    func: Callable[..., Any]  # Can be async

class VectorStore:
    def __init__(self, dimension: int):
        self.dimension = dimension
        self.index = faiss.IndexFlatL2(dimension)
        self.texts: List[str] = []

    def add(self, text: str, embedding: np.ndarray):
        if embedding.shape[0] != self.dimension:
            raise ValueError(f"Embedding dimension mismatch: expected {self.dimension}, got {embedding.shape[0]}")
        self.index.add(embedding.reshape(1, -1))
        self.texts.append(text)

    def search(self, query_embedding: np.ndarray, k: int) -> List[str]:
        if query_embedding.shape[0] != self.dimension:
            raise ValueError(f"Query embedding dimension mismatch: expected {self.dimension}, got {query_embedding.shape[0]}")
        distances, indices = self.index.search(query_embedding.reshape(1, -1), k)
        results = []
        for idx in indices[0]:
            if idx != -1 and idx < len(self.texts):
                results.append(self.texts[idx])
        return results

class Agent:
    def __init__(self, config: AgentConfig):
        self.config = config
        self.model = ChatOpenAI(
            model_name=config.model_name,
            temperature=config.temperature,
            max_tokens=config.max_tokens
        )
        self.embeddings = OpenAIEmbeddings(model=config.embedding_model)
        self.memory = VectorStore(dimension=self.embeddings.get_dimension())
        self.tools: Dict[str, Tool] = {}
        self.messages: List[Message] = []

    def add_tool(self, tool: Tool):
        self.tools[tool.name.lower()] = tool

    async def embed(self, text: str) -> np.ndarray:
        embedding = await self.embeddings.embed_query(text)
        return np.array(embedding, dtype=np.float32)

    async def process_message(self, user_message: str) -> str:
        self.messages.append(Message(role="user", content=user_message))
        
        # Embed the user message
        user_embedding = await self.embed(user_message)
        self.memory.add(user_message, user_embedding)
        
        # Search memory for relevant contexts
        relevant_memories = self.memory.search(user_embedding, self.config.top_k)
        
        # Construct conversation history (limit to last 10 messages for context)
        history = [SystemMessage(content="You are a helpful AI assistant.")]
        recent_messages = self.messages[-10:]
        for m in recent_messages:
            if m.role == "user":
                history.append(HumanMessage(content=m.content))
            elif m.role == "assistant":
                history.append(AIMessage(content=m.content))
        
        # Prepare tool descriptions
        if self.tools:
            tools_prompt = "Available tools:\n" + "\n".join(
                [f"{name}: {tool.description}" for name, tool in self.tools.items()]
            )
            history.append(SystemMessage(content=tools_prompt))
        
        # Include relevant memories
        if relevant_memories:
            memory_prompt = "Relevant information:\n" + "\n".join(relevant_memories)
            history.insert(1, SystemMessage(content=memory_prompt))  # After the assistant intro
        
        # Create the prompt
        prompt = ChatPromptTemplate.from_messages(history)
        
        # Generate AI response
        response = await self.model.agenerate(messages=prompt.messages)
        ai_message = response.generations[0][0].text.strip()
        
        # Check for tool invocation in the AI response
        # Example pattern: [tool_name] parameters...
        import re
        tool_pattern = r"\[(\w+)]\s*(.*)"
        match = re.search(tool_pattern, ai_message)
        if match:
            tool_name, tool_args = match.groups()
            tool_name = tool_name.lower()
            tool = self.tools.get(tool_name)
            if tool:
                try:
                    # If the tool function is async, await it
                    if asyncio.iscoroutinefunction(tool.func):
                        tool_output = await tool.func(tool_args)
                    else:
                        tool_output = tool.func(tool_args)
                    # Append tool output to AI message
                    ai_message += f"\n[Tool Output: {tool_output}]"
                except Exception as e:
                    ai_message += f"\n[Tool Error: {str(e)}]"
        
        self.messages.append(Message(role="assistant", content=ai_message))
        
        # Embed and store the AI response
        ai_embedding = await self.embed(ai_message)
        self.memory.add(ai_message, ai_embedding)
        
        # Enforce memory limit
        if len(self.memory.texts) > self.config.memory_limit:
            # Simple FIFO removal
            excess = len(self.memory.texts) - self.config.memory_limit
            self.memory.texts = self.memory.texts[excess:]
            # Note: FAISS doesn't support deletion in IndexFlatL2; consider using a different index for writable memory if needed
        
        return ai_message

async def main():
    config = AgentConfig()
    agent = Agent(config)
    
    # Example asynchronous tool functions
    async def async_search(query: str) -> str:
        # Placeholder for an async search, e.g., using an API
        await asyncio.sleep(1)  # Simulate async operation
        return f"Search results for '{query}'..."
    
    async def async_calculator(expression: str) -> str:
        try:
            # WARNING: Using eval is dangerous. In production, use a safe parser.
            result = eval(expression, {"__builtins__": {}})
            return f"The result is {result}"
        except Exception as e:
            return f"Error in calculation: {str(e)}"
    
    # Add tools
    agent.add_tool(Tool(name="search", description="Search the internet for information.", func=async_search))
    agent.add_tool(Tool(name="calculator", description="Perform mathematical calculations.", func=async_calculator))
    
    print("AI Agent is running. Type your messages below (type 'exit' to quit).")
    
    loop = asyncio.get_event_loop()
    while True:
        user_input = await loop.run_in_executor(None, input, "You: ")
        if user_input.lower() == "exit":
            print("Exiting AI Agent. Goodbye!")
            break
        try:
            response = await agent.process_message(user_input)
            print(f"AI: {response}\n")
        except Exception as e:
            print(f"Error: {str(e)}\n")

if __name__ == "__main__":
    asyncio.run(main())

Key Enhancements Explained

    Proper Embedding with OpenAIEmbeddings:
        Before: A simplistic hash-based embedding which doesn't capture semantic meaning.
        After: Utilizes OpenAIEmbeddings from LangChain to generate meaningful vector representations of text, enhancing the relevance of memory retrieval.

from langchain.embeddings import OpenAIEmbeddings

class Agent:
    # ...
    def __init__(self, config: AgentConfig):
        # ...
        self.embeddings = OpenAIEmbeddings(model=config.embedding_model)
        self.memory = VectorStore(dimension=self.embeddings.get_dimension())
    async def embed(self, text: str) -> np.ndarray:
        embedding = await self.embeddings.embed_query(text)
        return np.array(embedding, dtype=np.float32)

Asynchronous Tool Functions:

    Before: Tool functions were synchronous, potentially blocking the event loop.
    After: Tool functions are designed to be asynchronous, allowing for non-blocking execution.

async def async_search(query: str) -> str:
    # Implement actual async search logic here
    await asyncio.sleep(1)  # Simulate async operation
    return f"Search results for '{query}'..."

async def async_calculator(expression: str) -> str:
    try:
        result = eval(expression, {"__builtins__": {}})
        return f"The result is {result}"
    except Exception as e:
        return f"Error in calculation: {str(e)}"

# Adding tools
agent.add_tool(Tool(name="search", description="Search the internet for information.", func=async_search))
agent.add_tool(Tool(name="calculator", description="Perform mathematical calculations.", func=async_calculator))

Robust Tool Invocation Handling:

    Before: Simple substring check which could lead to false positives.
    After: Utilizes regex to detect tool invocation patterns like [tool_name] arguments, ensuring precise identification and execution.

import re

# Inside process_message
tool_pattern = r"\[(\w+)]\s*(.*)"
match = re.search(tool_pattern, ai_message)
if match:
    tool_name, tool_args = match.groups()
    tool_name = tool_name.lower()
    tool = self.tools.get(tool_name)
    if tool:
        try:
            if asyncio.iscoroutinefunction(tool.func):
                tool_output = await tool.func(tool_args)
            else:
                tool_output = tool.func(tool_args)
            ai_message += f"\n[Tool Output: {tool_output}]"
        except Exception as e:
            ai_message += f"\n[Tool Error: {str(e)}]"

Memory Management and Limits:

    Prevents unbounded growth of the memory by enforcing a limit (memory_limit) and removing the oldest entries when exceeded. Note that FAISS's IndexFlatL2 doesn’t support deletion, so this example removes texts from the list but doesn't remove them from the FAISS index. For more advanced memory management, consider using a different FAISS index type or implementing separate memory layers.

if len(self.memory.texts) > self.config.memory_limit:
    excess = len(self.memory.texts) - self.config.memory_limit
    self.memory.texts = self.memory.texts[excess:]
    # Note: FAISS doesn't support deletion in IndexFlatL2

Enhanced Prompt Construction:

    Integrates relevant memories and tool descriptions into the prompt more effectively, providing the AI with better context and capabilities.

if self.tools:
    tools_prompt = "Available tools:\n" + "\n".join(
        [f"{name}: {tool.description}" for name, tool in self.tools.items()]
    )
    history.append(SystemMessage(content=tools_prompt))

if relevant_memories:
    memory_prompt = "Relevant information:\n" + "\n".join(relevant_memories)
    history.insert(1, SystemMessage(content=memory_prompt))  # After the assistant intro

Type Hints and Error Handling:

    Comprehensive type hints improve code readability and maintainability.
    Added error handling around embedding and tool execution to prevent the agent from crashing due to unexpected errors.

User Experience Enhancements:

    Clear instructions and graceful exit message for the user interface.
    Asynchronous input handling ensures responsiveness.

    print("AI Agent is running. Type your messages below (type 'exit' to quit).")
    # ...
    if user_input.lower() == "exit":
        print("Exiting AI Agent. Goodbye!")
        break

Additional Recommendations

    Secure Evaluation in Calculator Tool:
        The provided eval usage is dangerous as it can execute arbitrary code. For production, consider using a safe math parser like ast.literal_eval for simple expressions or a dedicated math expression library.

    Persistent Memory Storage:
        Currently, memories are stored in-memory and lost when the program exits. For persistence, consider integrating a database or exporting/importing FAISS indices.

    Advanced FAISS Indexing:
        Explore more sophisticated FAISS indices that support addition and deletion of vectors to manage memory more effectively.

    Enhanced Tool Functionality:
        Implement actual search functionality (e.g., using an API like Bing or Google Custom Search).
        Expand tool capabilities based on user needs.

    Rate Limiting and API Key Management:
        If deploying this agent using APIs that require keys (e.g., OpenAI), ensure proper management and rate limiting to avoid exceeding quotas.

    Logging and Monitoring:
        Implement logging to track interactions, errors, and tool usage for better monitoring and debugging.

By implementing these optimizations and enhancements, your AI agent will be more robust, efficient, and capable of handling complex interactions with improved context understanding and tool usage.

переделай на использование апи опенроутер и сделай подробные комменты к коду на рус яз

Конечно! Я переделаю ваш код для использования API OpenRouter и добавлю подробные комментарии на русском языке для лучшего понимания. Предполагается, что OpenRouter совместим с интерфейсами OpenAI, и мы можем настроить ChatOpenAI и другие компоненты LangChain для работы с ним путем указания базового URL API и ключа доступа.

Вот улучшенная версия вашего AI-агента с использованием API OpenRouter и подробными комментариями на русском языке:

import asyncio
from dataclasses import dataclass, field
from typing import List, Dict, Callable, Any, Optional
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage
from langchain.prompts import ChatPromptTemplate
from langchain.embeddings import OpenAIEmbeddings
import faiss
import numpy as np
import re

# Конфигурация агента, включает настройки модели и API OpenRouter
@dataclass
class AgentConfig:
    model_name: str = "gpt-3.5-turbo"          # Имя модели
    temperature: float = 0.0                   # Креативность ответа
    max_tokens: int = 1000                     # Максимальное количество токенов в ответе
    top_k: int = 5                             # Количество релевантных воспоминаний для поиска
    embedding_model: str = "text-embedding-ada-002"  # Модель для получения эмбеддингов
    memory_limit: int = 1000                   # Максимальное количество записей в памяти
    openrouter_api_base: str = "https://api.openrouter.ai/v1"  # Базовый URL API OpenRouter
    openrouter_api_key: str = "your_openrouter_api_key"         # Ключ доступа к API OpenRouter

# Структура сообщения, содержащая роль и содержание
@dataclass
class Message:
    role: str
    content: str

# Структура инструмента с именем, описанием и функцией
@dataclass
class Tool:
    name: str
    description: str
    func: Callable[..., Any]  # Функция инструмента, может быть асинхронной

# Класс для работы с векторным хранилищем с использованием FAISS
class VectorStore:
    def __init__(self, dimension: int):
        self.dimension = dimension
        self.index = faiss.IndexFlatL2(dimension)  # Индекс для быстрого поиска по L2 расстоянию
        self.texts: List[str] = []               # Список текстов, соответствующих эмбеддингам

    def add(self, text: str, embedding: np.ndarray):
        """
        Добавляет текст и его эмбеддинг в хранилище.
        """
        if embedding.shape[0] != self.dimension:
            raise ValueError(f"Несоответствие размерности эмбеддинга: ожидалась {self.dimension}, получено {embedding.shape[0]}")
        self.index.add(embedding.reshape(1, -1))  # Добавляем эмбеддинг в индекс
        self.texts.append(text)                    # Сохраняем текст

    def search(self, query_embedding: np.ndarray, k: int) -> List[str]:
        """
        Выполняет поиск наиболее релевантных текстов по заданному эмбеддингу запроса.
        """
        if query_embedding.shape[0] != self.dimension:
            raise ValueError(f"Несоответствие размерности эмбеддинга запроса: ожидалась {self.dimension}, получено {query_embedding.shape[0]}")
        distances, indices = self.index.search(query_embedding.reshape(1, -1), k)  # Выполняем поиск
        results = []
        for idx in indices[0]:
            if idx != -1 and idx < len(self.texts):
                results.append(self.texts[idx])
        return results

# Основной класс агента
class Agent:
    def __init__(self, config: AgentConfig):
        """
        Инициализирует агента с заданной конфигурацией.
        """
        self.config = config
        # Инициализируем модель ChatOpenAI, указывая базовый URL и ключ для OpenRouter
        self.model = ChatOpenAI(
            model_name=config.model_name,
            temperature=config.temperature,
            max_tokens=config.max_tokens,
            openai_api_base=config.openrouter_api_base,  # Указываем базовый URL OpenRouter
            openai_api_key=config.openrouter_api_key      # Указываем ключ доступа к OpenRouter
        )
        # Инициализируем эмбеддинги с помощью OpenAIEmbeddings
        self.embeddings = OpenAIEmbeddings(
            model=config.embedding_model,
            openai_api_base=config.openrouter_api_base,
            openai_api_key=config.openrouter_api_key
        )
        # Инициализируем векторное хранилище с размерностью эмбеддингов
        self.memory = VectorStore(dimension=self.embeddings.get_dimension())
        self.tools: Dict[str, Tool] = {}    # Словарь доступных инструментов
        self.messages: List[Message] = []   # История сообщений

    def add_tool(self, tool: Tool):
        """
        Добавляет инструмент в словарь доступных инструментов.
        """
        self.tools[tool.name.lower()] = tool

    async def embed(self, text: str) -> np.ndarray:
        """
        Получает эмбеддинг для заданного текста.
        """
        embedding = await self.embeddings.embed_query(text)
        return np.array(embedding, dtype=np.float32)

    async def process_message(self, user_message: str) -> str:
        """
        Обрабатывает сообщение пользователя и генерирует ответ.
        """
        self.messages.append(Message(role="user", content=user_message))
        
        # Получаем эмбеддинг пользовательского сообщения
        user_embedding = await self.embed(user_message)
        self.memory.add(user_message, user_embedding)  # Добавляем сообщение в память
        
        # Ищем релевантные воспоминания в памяти
        relevant_memories = self.memory.search(user_embedding, self.config.top_k)
        
        # Конструируем историю беседы (ограничиваемся последними 10 сообщениями)
        history = [SystemMessage(content="You are a helpful AI assistant.")]
        recent_messages = self.messages[-10:]
        for m in recent_messages:
            if m.role == "user":
                history.append(HumanMessage(content=m.content))
            elif m.role == "assistant":
                history.append(AIMessage(content=m.content))
        
        # Добавляем описания доступных инструментов в подсказку
        if self.tools:
            tools_prompt = "Available tools:\n" + "\n".join(
                [f"{name}: {tool.description}" for name, tool in self.tools.items()]
            )
            history.append(SystemMessage(content=tools_prompt))
        
        # Включаем релевантные воспоминания в подсказку
        if relevant_memories:
            memory_prompt = "Relevant information:\n" + "\n".join(relevant_memories)
            history.insert(1, SystemMessage(content=memory_prompt))  # Вставляем после вступления ассистента
        
        # Создаем подсказку для модели
        prompt = ChatPromptTemplate.from_messages(history)
        
        # Генерируем ответ от модели
        response = await self.model.agenerate(messages=prompt.messages)
        ai_message = response.generations[0][0].text.strip()
        
        # Проверяем, требует ли ответ вызова инструмента
        tool_pattern = r"\[(\w+)]\s*(.*)"  # Шаблон для поиска инструментов в виде [имя_инструмента] аргументы
        match = re.search(tool_pattern, ai_message)
        if match:
            tool_name, tool_args = match.groups()
            tool_name = tool_name.lower()
            tool = self.tools.get(tool_name)
            if tool:
                try:
                    # Проверяем, является ли функция инструмента асинхронной
                    if asyncio.iscoroutinefunction(tool.func):
                        tool_output = await tool.func(tool_args)
                    else:
                        tool_output = tool.func(tool_args)
                    # Добавляем вывод инструмента к сообщению AI
                    ai_message += f"\n[Tool Output: {tool_output}]"
                except Exception as e:
                    # Обрабатываем ошибки при выполнении инструмента
                    ai_message += f"\n[Tool Error: {str(e)}]"
        
        self.messages.append(Message(role="assistant", content=ai_message))  # Сохраняем ответ AI
        
        # Получаем эмбеддинг ответа AI и сохраняем в память
        ai_embedding = await self.embed(ai_message)
        self.memory.add(ai_message, ai_embedding)
        
        # Ограничиваем размер памяти
        if len(self.memory.texts) > self.config.memory_limit:
            excess = len(self.memory.texts) - self.config.memory_limit
            self.memory.texts = self.memory.texts[excess:]
            # Обратите внимание: FAISS IndexFlatL2 не поддерживает удаление, поэтому мы просто очищаем текстовые записи
        
        return ai_message

# Основная асинхронная функция для запуска агента
async def main():
    # Создаем конфигурацию агента, указывая параметры OpenRouter
    config = AgentConfig(
        openrouter_api_base="https://api.openrouter.ai/v1",          # Базовый URL OpenRouter
        openrouter_api_key="your_openrouter_api_key_here"            # Ваш API ключ OpenRouter
    )
    agent = Agent(config)  # Инициализируем агента с конфигурацией
    
    # Пример асинхронных функций инструментов
    async def async_search(query: str) -> str:
        """
        Асинхронный инструмент для поиска информации в интернете.
        Здесь необходимо реализовать реальную логику поиска, например, через API.
        """
        await asyncio.sleep(1)  # Симуляция асинхронной операции
        return f"Результаты поиска по запросу '{query}'..."
    
    async def async_calculator(expression: str) -> str:
        """
        Асинхронный калькулятор для выполнения математических вычислений.
        ВНИМАНИЕ: Использование eval может быть опасно. Для безопасного парсинга используйте специализированные библиотеки.
        """
        try:
            # Выполняем вычисления
            result = eval(expression, {"__builtins__": {}})
            return f"Результат: {result}"
        except Exception as e:
            return f"Ошибка в вычислении: {str(e)}"
    
    # Добавляем инструменты в агента
    agent.add_tool(Tool(name="search", description="Поиск информации в интернете.", func=async_search))
    agent.add_tool(Tool(name="calculator", description="Выполнение математических вычислений.", func=async_calculator))
    
    print("AI-агент запущен. Введите ваши сообщения ниже (введите 'exit' для выхода).")
    
    # Получаем цикл событий
    loop = asyncio.get_event_loop()
    while True:
        # Получаем ввод пользователя асинхронно
        user_input = await loop.run_in_executor(None, input, "Вы: ")
        if user_input.lower() == "exit":
            print("Завершение работы AI-агента. До свидания!")
            break
        try:
            # Обрабатываем сообщение пользователя и получаем ответ от AI
            response = await agent.process_message(user_input)
            print(f"AI: {response}\n")
        except Exception as e:
            # Обрабатываем возможные ошибки
            print(f"Ошибка: {str(e)}\n")

# Точка входа в программу
if __name__ == "__main__":
    asyncio.run(main())

Пояснения к улучшенному коду
1. Использование API OpenRouter

    Конфигурация API:
        Добавлены параметры openrouter_api_base и openrouter_api_key в класс AgentConfig для указания базового URL и ключа доступа к API OpenRouter.
        При инициализации ChatOpenAI и OpenAIEmbeddings передаются эти параметры, чтобы настроить подключение к OpenRouter вместо стандартного OpenAI API.

    @dataclass
    class AgentConfig:
        # ... другие параметры ...
        openrouter_api_base: str = "https://api.openrouter.ai/v1"  # Базовый URL API OpenRouter
        openrouter_api_key: str = "your_openrouter_api_key"         # Ключ доступа к API OpenRouter

    class Agent:
        def __init__(self, config: AgentConfig):
            # Инициализация модели ChatOpenAI с использованием OpenRouter
            self.model = ChatOpenAI(
                model_name=config.model_name,
                temperature=config.temperature,
                max_tokens=config.max_tokens,
                openai_api_base=config.openrouter_api_base,  # Указываем базовый URL OpenRouter
                openai_api_key=config.openrouter_api_key      # Указываем ключ доступа к OpenRouter
            )
            # Инициализация эмбеддингов с использованием OpenRouter
            self.embeddings = OpenAIEmbeddings(
                model=config.embedding_model,
                openai_api_base=config.openrouter_api_base,
                openai_api_key=config.openrouter_api_key
            )

2. Асинхронные Инструменты

    Асинхронные функции инструментов:
        Инструменты search и calculator реализованы как асинхронные функции для возможности выполнения неблокирующих операций.

    async def async_search(query: str) -> str:
        # Здесь следует реализовать реальную логику поиска
        await asyncio.sleep(1)  # Симуляция асинхронной операции
        return f"Результаты поиска по запросу '{query}'..."

    async def async_calculator(expression: str) -> str:
        try:
            # Выполнение вычислений
            result = eval(expression, {"__builtins__": {}})
            return f"Результат: {result}"
        except Exception as e:
            return f"Ошибка в вычислении: {str(e)}"

3. Память и Векторное Хранилище

    VectorStore:
        Используется FAISS для быстрого поиска по эмбеддингам.
        Добавлены проверки на соответствие размерности эмбеддингов.

    class VectorStore:
        def __init__(self, dimension: int):
            self.dimension = dimension
            self.index = faiss.IndexFlatL2(dimension)
            self.texts: List[str] = []

        def add(self, text: str, embedding: np.ndarray):
            if embedding.shape[0] != self.dimension:
                raise ValueError(f"Несоответствие размерности эмбеддинга: ожидалась {self.dimension}, получено {embedding.shape[0]}")
            self.index.add(embedding.reshape(1, -1))
            self.texts.append(text)

4. Обработка Сообщений и Вызов Инструментов

    Обработка сообщений:
        Сообщения пользователя и AI сохраняются в истории беседы.
        Выполняется поиск релевантных воспоминаний по эмбеддингу сообщений.
        Формируется подсказка для модели, включающая историю, релевантные воспоминания и описания доступных инструментов.

    Вызов инструментов:
        Используется регулярное выражение для поиска шаблона вызова инструмента в ответе AI (например, [search] запрос).
        При обнаружении вызывается соответствующая функция инструмента, и результат добавляется к ответу AI.

    # Проверяем, требует ли ответ вызова инструмента
    tool_pattern = r"\[(\w+)]\s*(.*)"  # Шаблон для поиска инструментов в виде [имя_инструмента] аргументы
    match = re.search(tool_pattern, ai_message)
    if match:
        tool_name, tool_args = match.groups()
        tool_name = tool_name.lower()
        tool = self.tools.get(tool_name)
        if tool:
            try:
                # Проверяем, является ли функция инструмента асинхронной
                if asyncio.iscoroutinefunction(tool.func):
                    tool_output = await tool.func(tool_args)
                else:
                    tool_output = tool.func(tool_args)
                # Добавляем вывод инструмента к сообщению AI
                ai_message += f"\n[Tool Output: {tool_output}]"
            except Exception as e:
                # Обрабатываем ошибки при выполнении инструмента
                ai_message += f"\n[Tool Error: {str(e)}]"

5. Управление Памятью

    Ограничение размера памяти:
        Память ограничивается максимальным количеством записей (memory_limit).
        При превышении лимита старые записи удаляются из списка текстов. Обратите внимание, что FAISS IndexFlatL2 не поддерживает удаление векторов, поэтому только текстовая часть очищается.

    # Ограничиваем размер памяти
    if len(self.memory.texts) > self.config.memory_limit:
        excess = len(self.memory.texts) - self.config.memory_limit
        self.memory.texts = self.memory.texts[excess:]
        # Обратите внимание: FAISS IndexFlatL2 не поддерживает удаление, поэтому мы просто очищаем текстовые записи

6. Безопасность и Рекомендуемые Практики

    Использование eval:
        В инструментах, использующих eval, есть потенциальная опасность выполнения произвольного кода. В продакшене рекомендуется использовать безопасные парсеры выражений или специализированные библиотеки для математических вычислений.

    async def async_calculator(expression: str) -> str:
        try:
            # Выполнение вычислений
            result = eval(expression, {"__builtins__": {}})
            return f"Результат: {result}"
        except Exception as e:
            return f"Ошибка в вычислении: {str(e)}"

    Хранение openrouter_api_key:
        Ключ API не рекомендуется хранить прямо в коде. Лучше использовать переменные окружения или файлы конфигурации с ограниченным доступом.

7. Запуск и Взаимодействие с Агентом

    Асинхронный ввод пользователя:
        Используется run_in_executor для получения ввода пользователя без блокировки основного цикла событий.

    loop = asyncio.get_event_loop()
    while True:
        # Получаем ввод пользователя асинхронно
        user_input = await loop.run_in_executor(None, input, "Вы: ")
        if user_input.lower() == "exit":
            print("Завершение работы AI-агента. До свидания!")
            break
        try:
            # Обрабатываем сообщение пользователя и получаем ответ от AI
            response = await agent.process_message(user_input)
            print(f"AI: {response}\n")
        except Exception as e:
            # Обрабатываем возможные ошибки
            print(f"Ошибка: {str(e)}\n")

Дополнительные Рекомендации

    Безопасность Инструментов:
        Избегайте использования eval для вычислений. Рассмотрите возможность использования безопасных библиотек, таких как ast.literal_eval или специализированных математических парсеров.

    Постоянное Хранение Памяти:
        В текущей реализации память хранится в оперативной памяти и будет теряться при перезапуске программы. Для постоянного хранения можно использовать базы данных или сохранять и загружать эмбеддинги и тексты из файлов.

    Расширение Функционала Инструментов:
        Реализуйте реальные функции поиска, используя доступные API (например, Bing Search API, Google Custom Search и т.д.).
        Добавьте дополнительные инструменты по мере необходимости (например, переводчик, анализатор тональности и т.д.).

    Логирование и Мониторинг:
        Реализуйте систему логирования для отслеживания взаимодействий, ошибок и использования инструментов. Это облегчит отладку и мониторинг работы агента.

    Обработка Ошибок:
        Добавьте более детальную обработку ошибок для различных сценариев, чтобы агент мог корректно реагировать на неожиданные ситуации.

    Оптимизация Памяти:
        Рассмотрите использование более продвинутых методов управления памятью, например, разные типы индексов FAISS, которые поддерживают удаление, или применение кэширования.

    Конфиденциальность и Безопасность Данных:
        Убедитесь, что все передаваемые данные защищены, особенно если агент обрабатывает конфиденциальную информацию.

Заключение

Переделанный код теперь использует API OpenRouter вместо прямого обращения к OpenAI, что может предоставить дополнительные возможности и гибкость. Подробные комментарии на русском языке помогут лучше понять каждую часть кода и облегчить его дальнейшую настройку и расширение. Не забудьте заменить your_openrouter_api_key_here на ваш реальный ключ доступа к API OpenRouter и обеспечить его безопасное хранение. ииновационо внеси правки в соответствии с подходом триз улучши и оптимизируй код