File size: 13,716 Bytes
3e770f4
 
 
cd6d2f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2891839
cd6d2f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2891839
cd6d2f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b4b892
cd6d2f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import time
import json
import logging
from typing import List, Tuple, Generator, Optional, Any
import groq  # Предполагается, что модуль groq доступен

# Константы для параметризации
DEFAULT_MAX_TOKENS = 8192
FINAL_MAX_TOKENS = 4096
RETRY_DELAY = 2  # базовая задержка в секундах

# Выбор моделей на основе анализа:
MODEL_ITERATIONS = "llama-3.3-70b-versatile"  # Для итеративных рассуждений
MODEL_FINAL = "llama-3.3-70b-versatile"         # Для финального ответа
MODEL_CODE = "llama-3.1-8b-instant"             # Для генерации кода

logging.basicConfig(level=logging.DEBUG)

# Замените 'your_api_key' на ваш реальный API ключ
client = groq.Groq(api_key='your_api_key')

def parse_json_response(raw_response: str) -> dict:
    """
    Разбирает строку JSON и проверяет наличие обязательных ключей: title, content, next_action.
    
    Аргументы:
        raw_response (str): Строка, содержащая JSON.
    
    Возвращает:
        dict: Распарсенный JSON-объект с ключами title, content и next_action.
        
    Выбрасывает:
        ValueError: Если JSON некорректный или отсутствуют обязательные ключи.
    """
    logging.debug("RAW JSON RESPONSE: %s", raw_response)
    try:
        parsed_response = json.loads(raw_response)
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON: {e}, content: {raw_response}")
    
    required_keys = {"title", "content", "next_action"}
    if not required_keys.issubset(parsed_response):
        raise ValueError("JSON missing required keys")
    return parsed_response

def make_api_call(
    messages: List[dict], 
    max_tokens: int, 
    is_final_answer: bool = False, 
    custom_client: Optional[Any] = None
) -> Any:
    """
    Отправляет API-запрос с заданными сообщениями и возвращает результат.
    
    При is_final_answer=True возвращает строку финального ответа.
    Иначе ожидается, что ответ будет в формате JSON с ключами "title", "content", "next_action".
    
    Аргументы:
        messages (List[dict]): История сообщений для запроса.
        max_tokens (int): Максимальное число токенов в ответе.
        is_final_answer (bool): Флаг финального ответа.
        custom_client (Any, optional): Альтернативный клиент для API-вызова.
        
    Возвращает:
        Результат API-вызова (str для финального ответа или dict для промежуточного шага).
    """
    current_client = custom_client if custom_client is not None else client
    # Выбираем модель в зависимости от типа ответа
    model = MODEL_FINAL if is_final_answer else MODEL_ITERATIONS

    for attempt in range(3):
        try:
            if is_final_answer:
                response = current_client.chat.completions.create(
                    model=model,
                    messages=messages,
                    max_tokens=max_tokens,
                    temperature=0.7
                )
                logging.info("Успешно получен финальный ответ.")
                return response.choices[0].message.content
            else:
                response = current_client.chat.completions.create(
                    model=model,
                    messages=messages,
                    max_tokens=max_tokens,
                    temperature=0.7,
                    response_format={"type": "json_object"}
                )
                raw_response = response.choices[0].message.content
                logging.info("Успешно получен промежуточный ответ.")
                return parse_json_response(raw_response)
        # TODO: При появлении информации о конкретных исключениях заменить Exception на более специфичные
        except Exception as e:
            logging.exception("Попытка %d завершилась неудачно", attempt + 1)
            if attempt == 2:
                error_message = (
                    f"Не удалось сгенерировать "
                    f"{'финальный ответ' if is_final_answer else 'шаг'} за 3 попытки. Ошибка: {str(e)}"
                )
                if is_final_answer:
                    return f"Error: {error_message}"
                else:
                    return {
                        "title": "Error",
                        "content": error_message,
                        "next_action": "final_answer"
                    }
            time.sleep(RETRY_DELAY * (2 ** attempt))

def generate_response(
    prompt: str, 
    context: Optional[str] = None, 
    custom_client: Optional[Any] = None, 
    max_steps: int = 25
) -> Generator[Tuple[List[Tuple[str, str, float]], Optional[float]], None, None]:
    """
    Генерирует цепочку рассуждений и финальный ответ по заданному запросу.
    
    Новый подход:
    1. **Анализ вопроса:** Определите ключевые компоненты вопроса и разбейте его на под-вопросы.
    2. **Разбиение на под-вопросы:** Для каждого под-вопроса сформируйте 2-3 промежуточные мысли, такие как:
       - Переосмысление вопроса для лучшего понимания
       - Контекст, необходимый для ответа
       - Предположения и связь концепций
    3. **Оценка мыслей:** Оцените ясность, релевантность, логическую последовательность и охват концепций каждой мысли.
    4. **Построение цепочки рассуждений:** Соедините самые сильные мысли в логичную цепочку.
    5. **Обратный ход и альтернативные пути:** Если цепочка не отвечает полностью на вопрос, рассмотрите альтернативные варианты рассуждений.
    
    Аргументы:
        prompt (str): Запрос для генерации ответа.
        context (str, optional): Контекст для запроса.
        custom_client (Any, optional): Альтернативный клиент для API-вызовов.
        max_steps (int, optional): Максимальное количество шагов рассуждений.
    
    Возвращает:
        Генератор, yield'ящий кортеж (steps, total_thinking_time), где:
          - steps: список кортежей (название шага, содержание, время обработки)
          - total_thinking_time: общее время обработки (либо None до финального шага)
    """
    system_message = (
        "Вы – интеллектуальный помощник и эксперт по разработке на Python. "
        "Ваша задача — ответить на вопрос, исследуя несколько путей рассуждений. "
        "Сначала проанализируйте вопрос, выдельте ключевые информационные компоненты и разбейте его на логические под-вопросы. "
        "Для каждого под-вопроса сформируйте 2-3 промежуточные мысли, включающие: переосмысление, контекст, предположения и связь концепций. "
        "Оцените ясность, релевантность, логическую последовательность и охват концепций каждой мысли. "
        "Постройте итоговую цепочку рассуждений, объединяя лучшие мысли, и при необходимости рассмотрите альтернативные пути. "
        "Ответ должен быть в формате JSON с обязательными ключами: "
        '"title" — краткое название шага, "content" — описание действий, "next_action" — "continue" или "final_answer". '
        "Пример:\n"
        '{"title": "Анализ задачи", "content": "Выделение ключевых элементов вопроса, разбивка на под-вопросы и формирование промежуточных мыслей...", "next_action": "continue"}\n'
        "Дополнительные требования: используйте русский язык и избегайте Unicode-кодировок (например, пишите \"Привет\", а не \\\"\\u041f\\u0440\\u0438...\\\").'
    )
    
    # Формирование начальных сообщений
    user_content = f"Я хотел бы узнать про autogen. Контекст: {context}. {prompt}" if context else prompt
    messages = [
        {"role": "system", "content": system_message},
        {"role": "user", "content": user_content},
        {"role": "assistant", "content": "Спасибо! Начинаю анализ вопроса и построение цепочки рассуждений..."}
    ]

    steps: List[Tuple[str, str, float]] = []
    total_thinking_time = 0.0
    step_count = 1

    # Итеративная генерация шагов цепочки рассуждений
    while step_count <= max_steps:
        start_time = time.time()
        step_data = make_api_call(messages, max_tokens=DEFAULT_MAX_TOKENS, custom_client=custom_client)
        elapsed = time.time() - start_time
        total_thinking_time += elapsed

        step_title = step_data.get('title', 'Без названия')
        step_content = step_data.get('content', '')
        steps.append((f"Step {step_count}: {step_title}", step_content, elapsed))
        
        # Добавляем результат шага в историю сообщений
        messages.append({"role": "assistant", "content": json.dumps(step_data, ensure_ascii=False)})

        if step_data.get('next_action') == 'final_answer':
            break

        step_count += 1
        yield steps, None  # Промежуточный вывод без общего времени

    # Запрос на получение окончательного ответа
    messages.append({
        "role": "user",
        "content": "Предоставьте окончательный ответ без формата JSON, сохранив исходное форматирование из подсказки."
    })

    start_time = time.time()
    final_answer = make_api_call(messages, max_tokens=FINAL_MAX_TOKENS, is_final_answer=True, custom_client=custom_client)
    final_elapsed = time.time() - start_time
    total_thinking_time += final_elapsed

    steps.append(("Final Answer", final_answer, final_elapsed))
    yield steps, total_thinking_time

def generate_code(prompt: str, custom_client: Optional[Any] = None) -> str:
    """
    Генерирует код на основе предоставленного запроса, используя модель, оптимизированную для генерации кода.
    
    Аргументы:
        prompt (str): Запрос для генерации кода.
        custom_client (Any, optional): Альтернативный клиент для API-вызова.
    
    Возвращает:
        str: Сгенерированный код.
    """
    current_client = custom_client if custom_client is not None else client
    response = current_client.chat.completions.create(
        model=MODEL_CODE,
        messages=[{"role": "user", "content": prompt}],
        max_tokens=DEFAULT_MAX_TOKENS,
        temperature=0.7
    )
    return response.choices[0].message.content

if __name__ == "__main__":
    # Пример использования: генерация цепочки рассуждений
    context = "https://console.groq.com/docs/autogen"
    prompt = "учитывай контекст autogen в ответ"
    
    for steps, total_time in generate_response(prompt, context):
        if total_time is not None:
            print(f"Общее время обработки: {total_time:.2f} секунд")
        for title, content, t in steps:
            print(f"{title}: {content} (Время: {t:.2f} секунд)")
    
    # Пример использования: генерация кода
    code_prompt = "Напиши функцию на Python, которая сортирует список чисел."
    generated_code = generate_code(code_prompt)
    print("\nСгенерированный код:")
    print(generated_code)