File size: 6,587 Bytes
8b1625b
 
81bdd6a
8b1625b
 
 
 
 
81bdd6a
 
 
 
ce6b8f5
81bdd6a
 
 
 
 
 
 
ce6b8f5
 
81bdd6a
 
 
 
 
 
 
 
 
 
 
 
8b1625b
81bdd6a
8b1625b
81bdd6a
 
 
 
 
 
 
ce6b8f5
81bdd6a
 
 
 
 
ce6b8f5
81bdd6a
 
 
 
 
 
 
 
ce6b8f5
8b1625b
ce6b8f5
59a3189
81bdd6a
 
ce6b8f5
81bdd6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ce6b8f5
 
 
 
 
 
 
 
 
 
 
 
 
 
81bdd6a
 
 
59a3189
81bdd6a
 
 
8b1625b
81bdd6a
8b1625b
81bdd6a
8b1625b
 
 
 
ce6b8f5
 
8b1625b
81bdd6a
 
501e2cb
81bdd6a
 
501e2cb
81bdd6a
 
 
 
501e2cb
81bdd6a
 
 
 
 
8b1625b
81bdd6a
 
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
import groq
import time
import os
import json
import logging

logging.basicConfig(level=logging.DEBUG)

client = groq.Groq()

def parse_json_response(raw_response):
    """
    Разбирает строку JSON и проверяет наличие обязательных ключей: title, content, next_action.
    """
    logging.debug(f"RAW JSON RESPONSE: {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 required_keys.issubset(parsed_response):
        return parsed_response
    else:
        raise ValueError("JSON missing required keys")

def make_api_call(messages, max_tokens, is_final_answer=False, custom_client=None):
    """
    Отправляет API-запрос с заданными сообщениями и возвращает результат.
    
    При is_final_answer=True возвращает текст финального ответа.
    В противном случае ожидается, что ответ будет в формате JSON с ключами "title", "content", "next_action".
    """
    current_client = custom_client if custom_client is not None else client

    for attempt in range(3):
        try:
            if is_final_answer:
                response = current_client.chat.completions.create(
                    model="llama3-8b-8192",
                    messages=messages,
                    max_tokens=max_tokens,
                    temperature=0.2
                )
                # Возвращаем строку, как и ожидается для финального ответа
                return response.choices[0].message.content
            else:
                response = current_client.chat.completions.create(
                    model="llama3-8b-8192",
                    messages=messages,
                    max_tokens=max_tokens,
                    temperature=0.2,
                    response_format={"type": "json_object"}
                )
                raw_response = response.choices[0].message.content
                return parse_json_response(raw_response)
        except Exception as e:
            logging.exception("Attempt %d failed", attempt + 1)
            if attempt == 2:
                error_message = f"Failed to generate {'final answer' if is_final_answer else 'step'} after 3 attempts. Error: {str(e)}"
                if is_final_answer:
                    return f"Error: {error_message}"
                else:
                    return {
                        "title": "Error",
                        "content": error_message,
                        "next_action": "final_answer"
                    }
            time.sleep(1)

def generate_response(prompt, custom_client=None):
    """
    Генерирует цепочку рассуждений и финальный ответ по заданному запросу.
    
    Аргументы:
        prompt (str): Запрос, на который необходимо сгенерировать ответ.
        custom_client (object, optional): Альтернативный клиент для API-вызовов.
    
    Возвращает:
        Генератор, yield'ящий кортеж (steps, total_thinking_time), где:
            - steps: список кортежей (название шага, содержание, время обработки)
            - total_thinking_time: общее время обработки (либо None до финального шага)
    """
    messages = [
        {"role": "system", "content": (
            "Вы – интеллектуальный помощник, который анализирует и объясняет свои рассуждения на русском языке шаг за шагом.\n"
            "### 🔹 Формат ответа\n"
            "Ваш ответ должен быть строго в JSON-формате без дополнительного текста или форматирования (например, без ```json```).\n"
            "Обязательные ключи:\n"
            '- "title" – краткое название шага.\n'
            '- "content" – описание действий.\n'
            '- "next_action" – "continue" или "final_answer".\n'
            "Пример:\n"
            '{"title": "Анализ задачи", "content": "Выделение ключевых элементов...", "next_action": "continue"}\n'
            "🔹 Дополнительные требования:\n"
            "- Используйте русский язык.\n"
            '- Избегайте Unicode-кодировок (например, писать "Привет", а не "\\u041f\\u0440\\u0438...").'
        )}, 
        {"role": "user", "content": prompt},
        {"role": "assistant", "content": "Спасибо! Начинаю анализ..."}
    ]

    steps = []
    step_count = 1
    total_thinking_time = 0

    while True:
        start_time = time.time()
        step_data = make_api_call(messages, max_tokens=500, custom_client=custom_client)
        end_time = time.time()
        thinking_time = end_time - start_time
        total_thinking_time += thinking_time

        steps.append((f"Step {step_count}: {step_data.get('title', 'Без названия')}", step_data.get('content', ''), thinking_time))
        messages.append({"role": "assistant", "content": json.dumps(step_data, ensure_ascii=False)})

        if step_data.get('next_action') == 'final_answer' or step_count >= 25:
            break

        step_count += 1
        yield steps, None  # Возвращаем промежуточные шаги без финального времени

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

    start_time = time.time()
    final_data = make_api_call(messages, max_tokens=1200, is_final_answer=True, custom_client=custom_client)
    end_time = time.time()
    thinking_time = end_time - start_time
    total_thinking_time += thinking_time

    steps.append(("Final Answer", final_data, thinking_time))
    yield steps, total_thinking_time