DocUA's picture
налаштування AI Аналіз
b4da720
import os
import json
import logging
import requests
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
class LLMConnector:
"""
Клас для взаємодії з LLM (OpenAI, Google Gemini, тощо)
"""
def __init__(self, api_key=None, model_type="openai"):
"""
Ініціалізація з'єднання з LLM.
Args:
api_key (str): API ключ для доступу до LLM.
Якщо None, спробує використати змінну середовища.
model_type (str): Тип моделі ("openai" або "gemini")
"""
self.model_type = model_type.lower()
if self.model_type == "openai":
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
if not self.api_key:
logger.warning("API ключ OpenAI не вказано")
self.model = "gpt-4o-mini" # Оновлено стандартну модель
# Використовуємо новий спосіб ініціалізації клієнта OpenAI
# без проксі та інших застарілих параметрів
from openai import OpenAI
self.client = OpenAI(api_key=self.api_key)
elif self.model_type == "gemini":
self.api_key = api_key or os.getenv("GEMINI_API_KEY")
if not self.api_key:
logger.warning("API ключ Gemini не вказано")
self.model = "gemini-2.0-flash" # Оновлена модель для Gemini
else:
raise ValueError(f"Непідтримуваний тип моделі: {model_type}")
def analyze_jira_data(self, stats, inactive_issues, temperature=0.2):
"""
Аналіз даних Jira за допомогою LLM.
Args:
stats (dict): Базова статистика даних Jira
inactive_issues (dict): Дані про неактивні тікети
temperature (float): Параметр температури для генерації
Returns:
str: Результат аналізу
"""
try:
# Підготовка даних для аналізу
data_summary = self._prepare_data_for_llm(stats, inactive_issues)
# Відправлення запиту до LLM
if self.model_type == "openai":
return self._analyze_with_openai(data_summary, temperature)
elif self.model_type == "gemini":
return self._analyze_with_gemini(data_summary, temperature)
except Exception as e:
logger.error(f"Помилка при аналізі даних за допомогою LLM: {e}")
return f"Помилка при аналізі даних: {str(e)}"
def _prepare_data_for_llm(self, stats, inactive_issues):
"""
Підготовка даних для аналізу за допомогою LLM.
Args:
stats (dict): Базова статистика
inactive_issues (dict): Дані про неактивні тікети
Returns:
str: Дані для аналізу у текстовому форматі
"""
summary = []
# Додавання загальної статистики
summary.append(f"Загальна кількість тікетів: {stats.get('total_tickets', 'Невідомо')}")
# Додавання статистики за статусами
if 'status_counts' in stats and stats['status_counts']:
summary.append("\nТікети за статусами:")
for status, count in stats['status_counts'].items():
summary.append(f"- {status}: {count}")
# Додавання статистики за типами
if 'type_counts' in stats and stats['type_counts']:
summary.append("\nТікети за типами:")
for issue_type, count in stats['type_counts'].items():
summary.append(f"- {issue_type}: {count}")
# Додавання статистики за пріоритетами
if 'priority_counts' in stats and stats['priority_counts']:
summary.append("\nТікети за пріоритетами:")
for priority, count in stats['priority_counts'].items():
summary.append(f"- {priority}: {count}")
# Аналіз створених тікетів
if 'created_stats' in stats and stats['created_stats']:
summary.append("\nСтатистика створених тікетів:")
for key, value in stats['created_stats'].items():
if key == 'last_7_days':
summary.append(f"- Створено за останні 7 днів: {value}")
elif key == 'min':
summary.append(f"- Найраніший тікет створено: {value}")
elif key == 'max':
summary.append(f"- Найпізніший тікет створено: {value}")
# Аналіз неактивних тікетів
if inactive_issues:
total_inactive = inactive_issues.get('total_count', 0)
percentage = inactive_issues.get('percentage', 0)
summary.append(f"\nНеактивні тікети: {total_inactive} ({percentage}% від загальної кількості)")
if 'by_status' in inactive_issues and inactive_issues['by_status']:
summary.append("Неактивні тікети за статусами:")
for status, count in inactive_issues['by_status'].items():
summary.append(f"- {status}: {count}")
if 'top_inactive' in inactive_issues and inactive_issues['top_inactive']:
summary.append("\nНайбільш неактивні тікети:")
for i, ticket in enumerate(inactive_issues['top_inactive']):
key = ticket.get('key', 'Невідомо')
status = ticket.get('status', 'Невідомо')
days = ticket.get('days_inactive', 'Невідомо')
summary.append(f"- {key} (Статус: {status}, Днів неактивності: {days})")
return "\n".join(summary)
def _analyze_with_openai(self, data_summary, temperature=0.2):
"""
Аналіз даних за допомогою OpenAI.
Args:
data_summary (str): Дані для аналізу
temperature (float): Параметр температури
Returns:
str: Результат аналізу
"""
try:
if not self.api_key:
return "Не вказано API ключ OpenAI"
# Створення запиту до LLM з новим API
system_prompt = """Ви аналітик Jira з досвідом у процесах розробки ПЗ.
Проаналізуйте надані дані про тікети та надайте корисні інсайти та рекомендації
для покращення процесу. Будьте конкретними та орієнтованими на дії.
Виділіть сильні та слабкі сторони, а також потенційні ризики та можливості.
Аналіз повинен бути структурованим і легким для сприйняття менеджерами проекту."""
user_prompt = f"Проаналізуйте наступні дані Jira та надайте рекомендації:\n\n{data_summary}"
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# Надсилання запиту через новий клієнт OpenAI
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
max_tokens=2048
)
# Отримання результату
analysis_result = response.choices[0].message.content
logger.info("Успішно отримано аналіз від OpenAI")
return analysis_result
except Exception as e:
logger.error(f"Помилка при взаємодії з OpenAI: {e}")
return f"Помилка при взаємодії з OpenAI: {str(e)}"
def _analyze_with_gemini(self, data_summary, temperature=0.2):
"""
Аналіз даних за допомогою Google Gemini.
Args:
data_summary (str): Дані для аналізу
temperature (float): Параметр температури
Returns:
str: Результат аналізу
"""
try:
if not self.api_key:
return "Не вказано API ключ Gemini"
# Імпортуємо необхідні бібліотеки
try:
from google import genai
from google.genai import types
except ImportError:
logger.error("Бібліотека google-generativeai не встановлена")
return "Помилка: бібліотека google-generativeai не встановлена. Встановіть її командою: pip install google-generativeai"
# Налаштування клієнта
client = genai.Client(api_key=self.api_key)
# Додаємо логування для діагностики
logger.info(f"Відправляємо запит до Gemini API. Довжина запиту: {len(data_summary)} символів")
# Системна інструкція
system_instruction = """Ви аналітик Jira з досвідом у процесах розробки ПЗ.
Проаналізуйте надані дані про тікети та надайте корисні інсайти та рекомендації
для покращення процесу. Будьте конкретними та орієнтованими на дії.
Виділіть сильні та слабкі сторони, а також потенційні ризики та можливості.
Аналіз повинен бути структурованим і легким для сприйняття менеджерами проекту."""
# Запит користувача
user_prompt = f"Проаналізуйте наступні дані Jira та надайте рекомендації:\n\n{data_summary}"
# Створення запиту
contents = [
types.Content(
role="user",
parts=[types.Part.from_text(text=user_prompt)]
)
]
# Налаштування генерації з системною інструкцією
generate_content_config = types.GenerateContentConfig(
temperature=temperature,
top_p=0.95,
top_k=40,
max_output_tokens=2048,
response_mime_type="text/plain",
system_instruction=[
types.Part.from_text(text=system_instruction)
],
)
# Відправка запиту
try:
response = client.models.generate_content(
model=self.model,
contents=contents,
config=generate_content_config,
)
# Перевірка відповіді
if hasattr(response, 'text') and response.text:
logger.info("Успішно отримано аналіз від Gemini")
return response.text
else:
logger.error("Порожня відповідь від Gemini API")
return "Помилка: порожня відповідь від Gemini API"
except Exception as api_error:
logger.error(f"Помилка API Gemini: {str(api_error)}")
# Спробуємо з іншою моделлю, якщо поточна не працює
try:
logger.info("Спроба з альтернативною моделлю gemini-1.5-flash")
alternative_model = "gemini-1.5-flash"
response = client.models.generate_content(
model=alternative_model,
contents=contents,
config=generate_content_config,
)
if hasattr(response, 'text') and response.text:
logger.info("Успішно отримано аналіз від альтернативної моделі Gemini")
return response.text
else:
return "Помилка: порожня відповідь від альтернативної моделі Gemini API"
except Exception as retry_error:
logger.error(f"Повторна помилка API Gemini: {str(retry_error)}")
return f"Помилка при взаємодії з Gemini: {str(api_error)}"
except Exception as e:
import traceback
error_msg = f"Помилка при взаємодії з Gemini: {str(e)}\n\n{traceback.format_exc()}"
logger.error(error_msg)
return error_msg
def ask_question(self, question, context=None, temperature=0.3):
"""
Задати питання до LLM на основі даних Jira.
Args:
question (str): Питання користувача
context (str): Додатковий контекст (може містити JSON дані тікетів)
temperature (float): Параметр температури
Returns:
str: Відповідь на питання
"""
try:
# Формування запиту для LLM
if self.model_type == "openai":
messages = [
{"role": "system", "content": """Ви асистент, який допомагає аналізувати дані Jira.
Відповідайте на питання користувача на основі наданого контексту.
Якщо контекст недостатній для відповіді, чесно визнайте це."""}
]
if context:
messages.append({"role": "user", "content": f"Контекст: {context}"})
messages.append({"role": "user", "content": question})
# Відправлення запиту
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
)
# Отримання відповіді
answer = response.choices[0].message.content
logger.info("Успішно отримано відповідь від OpenAI")
return answer
elif self.model_type == "gemini":
# TODO: Реалізувати для Gemini
return "Gemini API для Q&A ще не реалізовано"
except Exception as e:
logger.error(f"Помилка при отриманні відповіді від LLM: {e}")
return f"Помилка при обробці запитання: {str(e)}"
def generate_summary(self, jira_data, output_format="markdown", temperature=0.3):
"""
Генерація підсумкового звіту по даним Jira.
Args:
jira_data (str): Дані Jira для аналізу
output_format (str): Формат виводу ("markdown", "html", "text")
temperature (float): Параметр температури
Returns:
str: Згенерований звіт
"""
try:
# Формування запиту для LLM
if self.model_type == "openai":
format_instruction = ""
if output_format == "markdown":
format_instruction = "Використовуйте Markdown для форматування звіту."
elif output_format == "html":
format_instruction = "Створіть звіт у форматі HTML з використанням відповідних тегів."
else:
format_instruction = "Створіть звіт у простому текстовому форматі."
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": f"""Ви аналітик, який створює професійні звіти на основі даних Jira.
Проаналізуйте надані дані та створіть структурований звіт з наступними розділами:
1. Короткий огляд проекту
2. Аналіз поточного стану
3. Ризики та проблеми
4. Рекомендації
{format_instruction}"""},
{"role": "user", "content": f"Дані для аналізу:\n\n{jira_data}"}
],
temperature=temperature,
)
# Отримання звіту
report = response.choices[0].message.content
logger.info(f"Успішно згенеровано звіт у форматі {output_format}")
return report
elif self.model_type == "gemini":
# TODO: Реалізувати для Gemini
return "Gemini API для генерації звітів ще не реалізовано"
except Exception as e:
logger.error(f"Помилка при генерації звіту: {e}")
return f"Помилка при генерації звіту: {str(e)}"
class AIAgentManager:
"""
Менеджер AI агентів для аналізу даних Jira
"""
def __init__(self, api_key=None, model_type="openai"):
"""
Ініціалізація менеджера AI агентів.
Args:
api_key (str): API ключ для LLM
model_type (str): Тип моделі ("openai" або "gemini")
"""
self.llm_connector = LLMConnector(api_key, model_type)
self.agents = {
"analytics_engineer": self._create_analytics_engineer(),
"project_manager": self._create_project_manager(),
"communication_specialist": self._create_communication_specialist()
}
def _create_analytics_engineer(self):
"""
Створення агента "Аналітичний інженер" для обробки даних.
Returns:
dict: Конфігурація агента
"""
return {
"name": "Аналітичний інженер",
"role": "Обробка та аналіз даних Jira",
"system_prompt": """Ви досвідчений аналітичний інженер, експерт з обробки та аналізу даних Jira.
Ваша задача - отримувати, обробляти та аналізувати дані з Jira, виявляти паттерни та готувати
інформацію для подальшого аналізу іншими спеціалістами. Фокусуйтеся на виявленні аномалій,
трендів та інсайтів у даних."""
}
def _create_project_manager(self):
"""
Створення агента "Проектний менеджер" для аналізу проекту.
Returns:
dict: Конфігурація агента
"""
return {
"name": "Проектний менеджер",
"role": "Аналіз стану проекту та вироблення рекомендацій",
"system_prompt": """Ви досвідчений проектний менеджер з глибоким розумінням процесів розробки ПЗ.
Ваша задача - аналізувати дані Jira, розуміти поточний стан проекту, виявляти ризики та проблеми,
та надавати конкретні дієві рекомендації для покращення процесу. Вас цікавлять дедлайни,
блокуючі фактори, неактивні тікети та ефективність процесу."""
}
def _create_communication_specialist(self):
"""
Створення агента "Комунікаційний спеціаліст" для формування повідомлень.
Returns:
dict: Конфігурація агента
"""
return {
"name": "Комунікаційний спеціаліст",
"role": "Формування повідомлень для стейкхолдерів",
"system_prompt": """Ви досвідчений комунікаційний спеціаліст, експерт з формування чітких,
інформативних та професійних повідомлень для стейкхолдерів проекту. Ваша задача -
перетворювати технічну інформацію та аналітику в зрозумілі, структуровані повідомлення,
які допоможуть стейкхолдерам приймати рішення. Фокусуйтеся на ключових інсайтах,
використовуйте професійний, але дружній тон."""
}
def run_agent(self, agent_name, task, context=None, temperature=0.3):
"""
Запуск конкретного агента для виконання задачі.
Args:
agent_name (str): Назва агента ("analytics_engineer", "project_manager" або "communication_specialist")
task (str): Задача для агента
context (str): Контекст для виконання задачі
temperature (float): Параметр температури
Returns:
str: Результат виконання задачі
"""
try:
if agent_name not in self.agents:
return f"Помилка: агент '{agent_name}' не знайдений"
agent = self.agents[agent_name]
# Формування запиту для LLM
messages = [
{"role": "system", "content": agent["system_prompt"]}
]
if context:
messages.append({"role": "user", "content": f"Контекст: {context}"})
messages.append({"role": "user", "content": task})
# Відправлення запиту до LLM
if self.llm_connector.model_type == "openai":
response = self.llm_connector.client.chat.completions.create(
model=self.llm_connector.model,
messages=messages,
temperature=temperature,
)
# Отримання результату
result = response.choices[0].message.content
logger.info(f"Успішно отримано результат від агента '{agent_name}'")
return result
elif self.llm_connector.model_type == "gemini":
# Реалізуємо запит до Gemini API через прямий HTTP запит
url = f"https://generativelanguage.googleapis.com/v1beta/models/{self.llm_connector.model}:generateContent?key={self.llm_connector.api_key}"
# Формування системного промпта та запиту користувача
system_prompt = agent["system_prompt"]
user_content = ""
if context:
user_content += f"Контекст: {context}\n\n"
user_content += task
# Формування повного запиту
payload = {
"contents": [
{
"parts": [
{
"text": f"{system_prompt}\n\n{user_content}"
}
]
}
],
"generationConfig": {
"temperature": temperature,
"maxOutputTokens": 2048
}
}
# Відправлення запиту
headers = {"Content-Type": "application/json"}
response = requests.post(url, headers=headers, json=payload)
# Обробка відповіді
if response.status_code == 200:
response_data = response.json()
# Отримання тексту відповіді
if 'candidates' in response_data and len(response_data['candidates']) > 0:
if 'content' in response_data['candidates'][0]:
content = response_data['candidates'][0]['content']
if 'parts' in content and len(content['parts']) > 0:
result = content['parts'][0].get('text', '')
logger.info(f"Успішно отримано результат від агента '{agent_name}' (Gemini)")
return result
logger.error(f"Помилка при запиті до Gemini API: {response.text}")
return f"Помилка при запиті до Gemini API: статус {response.status_code}"
except Exception as e:
logger.error(f"Помилка при виконанні задачі агентом '{agent_name}': {e}")
return f"Помилка при виконанні задачі: {str(e)}"