DocUA's picture
Initial commit
a7174ff
raw
history blame
24.3 kB
import os
import json
import logging
import requests
from typing import Dict, List, Any, Optional
import openai
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-3.5-turbo" # Стандартна модель
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-pro" # Стандартна модель для 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
response = openai.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": """Ви аналітик Jira з досвідом у процесах розробки ПЗ.
Проаналізуйте надані дані про тікети та надайте корисні інсайти та рекомендації
для покращення процесу. Будьте конкретними та орієнтованими на дії.
Виділіть сильні та слабкі сторони, а також потенційні ризики та можливості.
Аналіз повинен бути структурованим і легким для сприйняття менеджерами проекту."""},
{"role": "user", "content": f"Проаналізуйте наступні дані Jira та надайте рекомендації:\n\n{data_summary}"}
],
temperature=temperature,
)
# Отримання результату
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"
# API endpoint
url = f"https://generativelanguage.googleapis.com/v1beta/models/{self.model}:generateContent?key={self.api_key}"
# Формування запиту
payload = {
"contents": [
{
"parts": [
{
"text": """Ви аналітик Jira з досвідом у процесах розробки ПЗ.
Проаналізуйте надані дані про тікети та надайте корисні інсайти та рекомендації
для покращення процесу. Будьте конкретними та орієнтованими на дії.
Виділіть сильні та слабкі сторони, а також потенційні ризики та можливості.
Аналіз повинен бути структурованим і легким для сприйняття менеджерами проекту."""
}
]
},
{
"parts": [
{
"text": f"Проаналізуйте наступні дані Jira та надайте рекомендації:\n\n{data_summary}"
}
]
}
],
"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("Успішно отримано аналіз від Gemini")
return result
logger.error(f"Помилка при взаємодії з Gemini: {response.text}")
return f"Помилка при взаємодії з Gemini: статус {response.status_code}"
except Exception as e:
logger.error(f"Помилка при взаємодії з Gemini: {e}")
return f"Помилка при взаємодії з Gemini: {str(e)}"
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 = openai.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 = openai.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 = openai.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":
# TODO: Реалізувати для Gemini
return f"Gemini API для агента '{agent_name}' ще не реалізовано"
except Exception as e:
logger.error(f"Помилка при виконанні задачі агентом '{agent_name}': {e}")
return f"Помилка при виконанні задачі: {str(e)}"