2025-06-02 14:16:03 +03:00
|
|
|
|
from openai import OpenAI
|
|
|
|
|
|
from ..config import OPENAI_API_KEY, OPENAI_BASE_URL, DEFAULT_MODEL
|
|
|
|
|
|
from ..utils.logger import log
|
|
|
|
|
|
|
2025-06-02 16:43:01 +03:00
|
|
|
|
|
2025-06-02 14:16:03 +03:00
|
|
|
|
class AIInterface:
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.client = OpenAI(
|
|
|
|
|
|
api_key=OPENAI_API_KEY,
|
|
|
|
|
|
base_url=OPENAI_BASE_URL,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-06-02 16:43:01 +03:00
|
|
|
|
def send_request(self, prompt: str, context: dict = None, system_prompt: str = "") -> dict:
|
2025-06-02 14:16:03 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Отправляет запрос на эндпоинт ИИ через OpenAI и возвращает ответ.
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
log(f"Sending request to AI endpoint with prompt: {prompt}")
|
|
|
|
|
|
|
2025-06-02 16:43:01 +03:00
|
|
|
|
messages = []
|
|
|
|
|
|
|
|
|
|
|
|
if system_prompt:
|
|
|
|
|
|
messages.append({"role": "system", "content": system_prompt})
|
|
|
|
|
|
|
2025-06-02 14:16:03 +03:00
|
|
|
|
# Подготовка сообщений для чат-комплитшена
|
2025-06-02 16:43:01 +03:00
|
|
|
|
messages.append({"role": "user", "content": prompt})
|
|
|
|
|
|
if context:
|
|
|
|
|
|
# Преобразуем контекст в строку
|
|
|
|
|
|
context_str = self._format_context(context)
|
|
|
|
|
|
messages.append({"role": "system", "content": context_str})
|
2025-06-02 14:16:03 +03:00
|
|
|
|
|
|
|
|
|
|
# Создание запроса к OpenAI
|
|
|
|
|
|
response = self.client.chat.completions.create(
|
|
|
|
|
|
model=DEFAULT_MODEL,
|
2025-06-02 14:22:46 +03:00
|
|
|
|
messages=messages,
|
2025-06-02 14:16:03 +03:00
|
|
|
|
stream=False, # Пока отключаем стриминг для простоты
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Обработка ответа
|
|
|
|
|
|
return {"response": response.choices[0].message.content}
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
log(f"Error communicating with AI endpoint: {e}", level="error")
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
def _format_context(self, context: dict) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Преобразует контекст в строку для передачи в OpenAI API.
|
|
|
|
|
|
"""
|
|
|
|
|
|
formatted_context = []
|
|
|
|
|
|
for key, value in context.items():
|
|
|
|
|
|
formatted_context.append(f"{key}: {value}")
|
|
|
|
|
|
return "\n".join(formatted_context)
|
|
|
|
|
|
|
2025-06-02 16:43:01 +03:00
|
|
|
|
def analyze_project(self, prompt: str, project_structure: dict) -> dict:
|
2025-06-02 14:16:03 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Анализирует структуру проекта и запрос пользователя через ИИ.
|
|
|
|
|
|
"""
|
|
|
|
|
|
context = {"project_structure": project_structure}
|
2025-06-02 16:43:01 +03:00
|
|
|
|
answer = self.send_request(prompt, context, ANALYZE_PROMPT)
|
|
|
|
|
|
|
|
|
|
|
|
max_attempts = 10
|
|
|
|
|
|
while "Достаточно информации" not in answer.get("response", "Достаточно информации"):
|
|
|
|
|
|
max_attempts -= 1
|
|
|
|
|
|
if max_attempts < 0:
|
|
|
|
|
|
log(f"Error communicating with AI endpoint.", level="error")
|
|
|
|
|
|
raise Exception("Error communicating with AI endpoint.")
|
|
|
|
|
|
|
|
|
|
|
|
for command in answer.get("response", "").split("\n"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
command_type, details = command.split(":", 1)
|
|
|
|
|
|
if command_type.strip().lower() == "запрос содержимого файла":
|
|
|
|
|
|
project_structure['files_info'] = project_structure.get('files_info', {})
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(details.strip(), "r", encoding="utf-8") as f:
|
|
|
|
|
|
content = f.read()
|
|
|
|
|
|
project_structure['files_info'][details.strip()] = content
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
|
project_structure['files_info'][details.strip()] = "Файл не найден"
|
|
|
|
|
|
elif command_type.strip().lower() == "запрос содержимого функции":
|
|
|
|
|
|
raise NotImplementedError("Запрос содержимого функции не реализован")
|
|
|
|
|
|
elif command_type.strip().lower() == "уточнение запроса":
|
|
|
|
|
|
answer = self.request_additional_info(details.strip())
|
|
|
|
|
|
project_structure['QA_info'] = project_structure.get('QA_info', {})
|
|
|
|
|
|
project_structure['QA_info'][details.strip()] = answer
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError(f"Unknown command type: {command_type}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
log(f"Error parsing command: {command}", level="error")
|
|
|
|
|
|
answer = self.send_request(prompt, context, ANALYZE_PROMPT)
|
|
|
|
|
|
|
|
|
|
|
|
return answer
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-02 14:16:03 +03:00
|
|
|
|
|
|
|
|
|
|
def request_additional_info(self, message: str) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Запрашивает дополнительную информацию у пользователя.
|
|
|
|
|
|
"""
|
|
|
|
|
|
log("Requesting additional information from user.")
|
2025-06-02 16:43:01 +03:00
|
|
|
|
return input(f"{message}\n> ")
|
|
|
|
|
|
|
|
|
|
|
|
def get_modifications(self, prompt: str, project_structure: dict) -> dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Создает модификации проекта на основе команды пользователя.
|
|
|
|
|
|
"""
|
|
|
|
|
|
context = {"project_structure": project_structure}
|
|
|
|
|
|
answer = self.send_request(prompt, context, COMMANDS_PROMPT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return answer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ANALYZE_PROMPT = """
|
|
|
|
|
|
Ты - эксперт по анализу структуры проектов.
|
|
|
|
|
|
Ты должен проанализировать структуру проекта и запрос пользователя.
|
|
|
|
|
|
|
|
|
|
|
|
Если данных которые тебе передали достаточно - ответь "Достаточно информации".
|
|
|
|
|
|
Если данных недостаточно - запроси содержимое файла или функции которые тебе интересны и/или запроси у пользователя дополнительную информацию.
|
|
|
|
|
|
Если не получается найти файл или функцию - учитывай что его возможно нужно будет создать и ты можешь пропустить этот шаг и продолжить работу дальше. Пути должны соответствовать структуре проекта.
|
|
|
|
|
|
|
|
|
|
|
|
ОТВЕЧАЙ ТОЛЬКО КОМАНДАМИ!
|
|
|
|
|
|
|
|
|
|
|
|
### Запрос содержимого файла
|
|
|
|
|
|
Содержимое файла: <имя файла>
|
|
|
|
|
|
### Запрос содержимого функции
|
|
|
|
|
|
Содержимое функции: <имя файла> <имя функции>
|
|
|
|
|
|
### Запрос у пользователя
|
|
|
|
|
|
Уточнение запроса: <текст вопроса к пользователю>
|
|
|
|
|
|
|
|
|
|
|
|
Например, вот ответ с двумя командами, первый просить содержимое файла, второй - уточнить что нужно создать.:
|
|
|
|
|
|
Содержимое файла: app/models/user.ts
|
|
|
|
|
|
Уточнение запроса: Нужно ли создать интерфейсы в types/users.ts?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
НЕ ОТВЕЧАЙ НИЧЕМ ЧТО НЕ СООТВЕТСТВУЕТ ПРАВИЛАМ ВЫШЕ, ТОЛЬКО КОМАНДЫ!
|
|
|
|
|
|
ПОСТАРАЙСЯ В ОДНОМ ОТВЕТЕ НАПИСАТЬ ВСЕ НЕОБХОДИМЫЕ КОМАНДЫ!
|
|
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
COMMANDS_PROMPT = """
|
|
|
|
|
|
Ты - senior developer. Ты должен прочитать запрос пользователя и на его основе сформировать список команд для выполнения этого запроса.
|
|
|
|
|
|
Ты должен ознакомиться с текущей структурой проекта и отдельными файлами и функциями.
|
|
|
|
|
|
|
|
|
|
|
|
Итого ты должен сформировать список команд для выполнения этого запроса.
|
|
|
|
|
|
Команды в таком формате через перенос строки:
|
|
|
|
|
|
`FILE <Команда>` `<имя и путь к файлу>`
|
|
|
|
|
|
|
|
|
|
|
|
`FUNCTION <Команда>` `<имя функции>`
|
|
|
|
|
|
```ts
|
|
|
|
|
|
<код функции>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Команды: CREATE, UPDATE, DELETE
|
|
|
|
|
|
|
|
|
|
|
|
НЕ ОТВЕЧАЙ НИЧЕМ ЧТО НЕ СООТВЕТСТВУЕТ ПРАВИЛАМ ВЫШЕ, ТОЛЬКО КОМАНДЫ!
|
|
|
|
|
|
ПОСТАРАЙСЯ В ОДНОМ ОТВЕТЕ НАПИСАТЬ ВСЕ НЕОБХОДИМЫЕ КОМАНДЫ!
|
|
|
|
|
|
""".strip()
|