alexorlov commited on
Commit
ef2fdd9
·
verified ·
1 Parent(s): de5fcc2

Upload app/services/llm.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app/services/llm.py +46 -179
app/services/llm.py CHANGED
@@ -1,185 +1,52 @@
1
- from anthropic import Anthropic
2
- from typing import List, Dict, Any
3
- from app.config import get_settings
4
- from app.models.question import Answer
5
- from app.models.checklist import ChecklistItem
6
  import json
7
- import re
 
 
 
8
 
 
9
 
10
- def extract_json(text: str) -> dict:
11
- """Извлекает JSON из текста, даже если он обернут в markdown code blocks"""
12
- # Пробуем найти JSON в code blocks
13
- json_match = re.search(r'```(?:json)?\s*([\s\S]*?)```', text)
14
- if json_match:
15
- text = json_match.group(1).strip()
16
-
17
- # Пробуем найти JSON объект напрямую
18
- json_match = re.search(r'\{[\s\S]*\}', text)
19
- if json_match:
20
- text = json_match.group(0)
21
-
22
- return json.loads(text)
23
 
24
-
25
- class LLMService:
26
  def __init__(self):
27
- settings = get_settings()
28
- self.client = Anthropic(api_key=settings.anthropic_api_key)
29
- self.model = "claude-haiku-4-5-20251001"
30
-
31
- def generate_initial_questions(self) -> List[Dict[str, str]]:
32
- """Генерирует первые 3 вопроса для начала интервью"""
33
- response = self.client.messages.create(
34
- model=self.model,
35
- max_tokens=1024,
36
- messages=[
37
- {
38
- "role": "user",
39
- "content": """Ты - AI ассистент, который помогает заполнить чеклист созвона с клиентом.
40
-
41
- Сгенерируй 3 начальных вопроса для клиента, чтобы понять суть его проекта.
42
- Вопросы должны быть открытыми и направлены на выяснение:
43
- 1. Общей информации о проекте
44
- 2. Целей и задач
45
- 3. Текущей ситуации
46
-
47
- Ответ верни в формате JSON:
48
- {
49
- "questions": [
50
- {"id": "q1", "text": "текст вопроса 1"},
51
- {"id": "q2", "text": "текст вопроса 2"},
52
- {"id": "q3", "text": "текст вопроса 3"}
53
- ]
54
- }
55
-
56
- Только JSON, без дополнительного текста."""
57
- }
58
- ]
59
- )
60
-
61
- result = extract_json(response.content[0].text)
62
- return result["questions"]
63
-
64
- def analyze_round_and_generate_questions(
65
- self,
66
- round_number: int,
67
- all_answers: List[Answer],
68
- round_summaries: List[str]
69
- ) -> Dict[str, Any]:
70
- """Анализирует ответы раунда и генерирует следующие вопросы"""
71
-
72
- answers_text = "\n".join([
73
- f"Вопрос: {a.question_text}\nОтвет: {a.audio_transcript}"
74
- for a in all_answers
75
- ])
76
-
77
- summaries_text = "\n".join([
78
- f"Раунд {i+1}: {s}" for i, s in enumerate(round_summaries)
79
- ]) if round_summaries else "Нет предыдущих саммари"
80
-
81
- response = self.client.messages.create(
82
- model=self.model,
83
- max_tokens=2048,
84
- messages=[
85
- {
86
- "role": "user",
87
- "content": f"""Ты - AI ассистент для заполнения чеклиста созвона с клиентом.
88
-
89
- Текущий раунд: {round_number}
90
- Всего раундов: 3
91
-
92
- Предыдущие саммари:
93
- {summaries_text}
94
-
95
- Все ответы клиента:
96
- {answers_text}
97
-
98
- Задача:
99
- 1. Создай краткое саммари текущего раунда (2-3 предложения)
100
- 2. Если это не последний раунд (раунд < 3), сгенерируй 3 уточняющих вопроса на основе полученных ответов
101
-
102
- Ответ в формате JSON:
103
- {{
104
- "round_summary": "краткое саммари раунда",
105
- "questions": [
106
- {{"id": "q{round_number*3+1}", "text": "вопрос 1"}},
107
- {{"id": "q{round_number*3+2}", "text": "вопрос 2"}},
108
- {{"id": "q{round_number*3+3}", "text": "вопрос 3"}}
109
- ]
110
- }}
111
-
112
- Если это раунд 3, поле "questions" может быть пустым массивом.
113
- Только JSON, без дополнительного текста."""
114
- }
115
- ]
116
  )
117
-
118
- return extract_json(response.content[0].text)
119
-
120
- def generate_checklist(
121
- self,
122
- all_answers: List[Answer],
123
- round_summaries: List[str]
124
- ) -> Dict[str, Any]:
125
- """Генерирует финальный чеклист на основе всех ответов"""
126
-
127
- answers_text = "\n".join([
128
- f"Вопрос: {a.question_text}\nОтвет: {a.audio_transcript}"
129
- for a in all_answers
130
- ])
131
-
132
- summaries_text = "\n".join([
133
- f"Раунд {i+1}: {s}" for i, s in enumerate(round_summaries)
134
- ])
135
-
136
- response = self.client.messages.create(
137
- model=self.model,
138
- max_tokens=4096,
139
- messages=[
140
- {
141
- "role": "user",
142
- "content": f"""Ты - AI ассистент для заполнения чеклиста созвона с клиентом.
143
-
144
- Саммари раундов:
145
- {summaries_text}
146
-
147
- Все ответы клиента:
148
- {answers_text}
149
-
150
- Создай структурированный чеклист созвона с клиентом.
151
-
152
- Ответ в формате JSON:
153
- {{
154
- "checklist": [
155
- {{
156
- "category": "Общая информация",
157
- "item": "описание пункта",
158
- "status": "confirmed",
159
- "notes": "дополнительные заметки или null"
160
- }}
161
- ]
162
- }}
163
-
164
- Статусы:
165
- - "confirmed" - информация получена и подтверждена
166
- - "needs_clarification" - требует уточнения
167
- - "not_discussed" - не обсуждалось
168
-
169
- Категории могут быть:
170
- - Общая информация
171
- - Цели и задачи
172
- - Сроки и бюджет
173
- - Технические требования
174
- - Дополнительные заметки
175
-
176
- Только JSON, без дополнительного текста."""
177
- }
178
- ]
179
- )
180
-
181
- return extract_json(response.content[0].text)
182
-
183
-
184
- def get_llm_service() -> LLMService:
185
- return LLMService()
 
 
 
 
 
 
1
  import json
2
+ import logging
3
+ from google import genai
4
+ from google.genai import types
5
+ from app.config import settings
6
 
7
+ logger = logging.getLogger(__name__)
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ class GeminiService:
 
11
  def __init__(self):
12
+ self._client = None
13
+
14
+ def initialize(self):
15
+ if not settings.gemini_api_key:
16
+ logger.warning("GEMINI_API_KEY not set — LLM calls will fail")
17
+ return
18
+ self._client = genai.Client(api_key=settings.gemini_api_key)
19
+ logger.info(f"Gemini client initialized with model: {settings.gemini_model}")
20
+
21
+ async def generate(self, system_prompt: str, user_prompt: str) -> str:
22
+ if not self._client:
23
+ raise RuntimeError("Gemini client not initialized")
24
+
25
+ response = self._client.models.generate_content(
26
+ model=settings.gemini_model,
27
+ contents=[types.Content(role="user", parts=[types.Part(text=user_prompt)])],
28
+ config=types.GenerateContentConfig(
29
+ system_instruction=system_prompt,
30
+ thinking_config=types.ThinkingConfig(thinking_level="HIGH"),
31
+ ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  )
33
+ # Extract text, skipping thinking parts
34
+ text_parts = []
35
+ for part in response.candidates[0].content.parts:
36
+ if part.text and not getattr(part, "thought", False):
37
+ text_parts.append(part.text)
38
+ return "\n".join(text_parts).strip()
39
+
40
+ async def generate_json(self, system_prompt: str, user_prompt: str) -> dict | list:
41
+ raw = await self.generate(system_prompt, user_prompt)
42
+ # Strip markdown code fences if present
43
+ cleaned = raw.strip()
44
+ if cleaned.startswith("```"):
45
+ lines = cleaned.split("\n")
46
+ # Remove first line (```json) and last line (```)
47
+ lines = [l for l in lines[1:] if l.strip() != "```"]
48
+ cleaned = "\n".join(lines)
49
+ return json.loads(cleaned)
50
+
51
+
52
+ gemini_service = GeminiService()