yoshizen commited on
Commit
f67a1d3
·
verified ·
1 Parent(s): 57f5985

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -217
app.py CHANGED
@@ -1,21 +1,13 @@
1
  """
2
- Финальный агент для Agent Challenge (LangGraph)
3
  """
4
 
5
  import os
6
- import json
7
  import re
8
  import math
9
- import requests
10
- from typing import List, Dict, Any, Optional, TypedDict, Annotated, Literal, Union
11
- from datetime import datetime
12
-
13
- # Импорт необходимых компонентов LangGraph
14
- from langgraph.graph import StateGraph, END
15
- from langgraph.prebuilt import ToolNode, tools_condition
16
-
17
- # Импорт инструментов LangChain
18
- from langchain_core.tools import tool
19
 
20
  # Безопасная обработка токена Hugging Face
21
  # Токен должен быть установлен как переменная окружения HUGGINGFACE_TOKEN
@@ -25,25 +17,17 @@ HUGGINGFACE_TOKEN = os.environ.get("HUGGINGFACE_TOKEN")
25
  # Инициализация клиента Hugging Face
26
  client = None
27
  try:
28
- from huggingface_hub import InferenceClient
29
  client = InferenceClient(
30
  model="mistralai/Mixtral-8x7B-Instruct-v0.1", # Рекомендуемая модель
31
- token=HUGGINGFACE_TOKEN,
32
- timeout=120 # Увеличим таймаут для больших моделей
33
  )
34
- except ImportError:
35
- print("Ошибка: библиотека huggingface_hub не установлена. Установите: pip install huggingface_hub")
36
  except Exception as e:
37
  print(f"Ошибка инициализации InferenceClient: {e}. Проверьте токен и доступность модели.")
38
 
39
  # --- Определение инструментов ---
40
 
41
- @tool
42
  def calculator(expression: str) -> str:
43
- """Выполняет математические вычисления.
44
- Пример входа: "(2 + 3) * 4 / 2"
45
- Возвращает результат вычисления или сообщение об ошибке.
46
- """
47
  try:
48
  # Ограничение на доступные функции для безопасности
49
  allowed_names = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}
@@ -52,7 +36,7 @@ def calculator(expression: str) -> str:
52
  allowed_names["max"] = max
53
  allowed_names["min"] = min
54
 
55
- # Удаление потенциально опасных символов (хотя eval все равно рискован)
56
  safe_expression = re.sub(r"[^0-9\.\+\-\*\/\(\)\s]|\b(import|exec|eval|open|lambda|\_\_)\b", "", expression)
57
 
58
  if safe_expression != expression:
@@ -63,210 +47,132 @@ def calculator(expression: str) -> str:
63
  except Exception as e:
64
  return f"Ошибка в вычислении: {str(e)}"
65
 
66
- @tool
67
  def web_search(query: str) -> str:
68
- """Выполняет поиск в интернете по заданному запросу.
69
- Пример входа: "прогноз погоды в Париже"
70
- Возвращает результаты поиска (симуляция).
71
- Для реального использования замените на API поисковой системы (например, Tavily, Serper).
72
- """
73
- print(f"--- Выполняется поиск: {query} ---")
74
- # --- Симуляция поиска ---
75
- # В реальном приложении здесь будет вызов API поисковика
76
- try:
77
- # Пример использования requests (закомментировано, т.к. нет реального API)
78
- # headers = {"X-API-KEY": os.environ.get("SEARCH_API_KEY"), "Content-Type": "application/json"}
79
- # data = json.dumps({"query": query, "max_results": 3})
80
- # response = requests.post("https://api.tavily.com/search", headers=headers, data=data)
81
- # response.raise_for_status()
82
- # results = response.json()["results"]
83
- # return json.dumps(results)
84
-
85
- # Простая симуляция для теста
86
- if "погода" in query.lower():
87
- return json.dumps([{"title": "Прогноз погоды", "content": "В городе, который вы ищете, сегодня солнечно, +25C."}])
88
- elif "hugging face" in query.lower():
89
- return json.dumps([{"title": "Hugging Face", "content": "Hugging Face - это платформа и сообщество для работы с моделями машинного обучения."}])
90
- elif "langgraph" in query.lower():
91
- return json.dumps([{"title": "LangGraph", "content": "LangGraph - это библиотека для создания агентов с состоянием на основе LangChain."}])
92
- else:
93
- return json.dumps([{"title": "Результат поиска", "content": f"По вашему запросу '{query}' найдена общая информация."}])
94
-
95
- except requests.exceptions.RequestException as e:
96
- return f"Ошибка сети при поиске: {e}"
97
- except Exception as e:
98
- return f"Ошибка при выполнении поиска: {str(e)}"
99
-
100
- @tool
101
- def get_current_datetime() -> str:
102
- """Возвращает текущую дату и время.
103
- Не требует входных данных.
104
- """
105
- now = datetime.now()
106
- return f"Текущая дата и время: {now.strftime('%Y-%m-%d %H:%M:%S')}"
107
-
108
- # Список инструментов для агента
109
- tools_list = [calculator, web_search, get_current_datetime]
110
 
111
- # --- Определение состояния и графа ---
112
-
113
- class AgentState(TypedDict):
114
- """Состояние агента LangGraph."""
115
- messages: List[Union[Dict[str, str], Any]] # История сообщений (включая вызовы инструментов)
116
-
117
- # Узел агента (LLM для принятия решений)
118
- def agent_node(state: AgentState) -> Dict[str, Any]:
119
- """Вызывает LLM для определения следующего шага (вызов инструмента или финальный ответ)."""
120
  if client is None:
121
- raise ValueError("Клиент Hugging Face не инициализирован.")
122
-
123
- # Формируем промпт для LLM
124
- # Важно: Промпт должен быть адаптирован под конкретную модель (Mixtral)
125
- # и формат вывода инструментов LangChain/LangGraph
126
 
127
- # Преобразуем state["messages"] в формат, понятный Mixtral
128
- prompt_messages = []
129
- for msg in state["messages"]:
130
- if isinstance(msg, dict) and "role" in msg and "content" in msg:
131
- prompt_messages.append(msg)
132
- elif hasattr(msg, "type") and msg.type == "human":
133
- prompt_messages.append({"role": "user", "content": msg.content})
134
- elif hasattr(msg, "type") and msg.type == "ai":
135
- # Обработка вызовов инструментов в ответе AI
136
- content = msg.content
137
- if hasattr(msg, "tool_calls") and msg.tool_calls:
138
- tool_calls_str = json.dumps([tc["name"] for tc in msg.tool_calls])
139
- content += f"\n(Вызов инструментов: {tool_calls_str})"
140
- prompt_messages.append({"role": "assistant", "content": content})
141
- elif hasattr(msg, "type") and msg.type == "tool":
142
- prompt_messages.append({
143
- "role": "tool",
144
- "content": f"Результат инструмента {msg.name}: {msg.content}",
145
- "name": msg.name # Добавляем имя инструмента для контекста
146
- })
147
- else:
148
- # Пропускаем или логируем неизвестные типы сообщений
149
- print(f"Пропущено сообщение неизвестного типа: {type(msg)}")
150
-
151
- print("--- Промпт для LLM ---")
152
- # print(json.dumps(prompt_messages, indent=2, ensure_ascii=False))
153
- print("...") # Не выводим весь промпт, может быть большим
 
 
 
 
 
 
 
 
 
 
154
 
155
- # Вызов LLM
156
- response = client.chat_completion(
157
- messages=prompt_messages,
158
- tool_choice="auto", # Позволяем модели решать, использовать ли инструмент
159
- tools=[tool.get_input_schema().schema() for tool in tools_list], # Передаем схему инструментов
160
- temperature=0.1, # Низкая температура для более предсказуемых вызовов
161
- max_tokens=1500
162
- )
163
-
164
- ai_message = response["choices"][0]["message"]
165
 
166
- print("--- Ответ LLM ---")
167
- print(ai_message)
168
 
169
- # Возвращаем сообщение для добавления в состояние графом
170
- return {"messages": [ai_message]}
171
-
172
- # Создание графа
173
- workflow = StateGraph(AgentState)
174
-
175
- # Добавление узлов
176
- workflow.add_node("agent", agent_node)
177
- workflow.add_node("tools", ToolNode(tools_list))
178
-
179
- # Определение ребер
180
- workflow.set_entry_point("agent")
181
-
182
- # Условное ребро: после агента решаем, вызывать ли инструменты или завершать
183
- workflow.add_conditional_edges(
184
- "agent",
185
- # Функция conditions.tools_condition проверяет, есть ли tool_calls в последнем сообщении
186
- tools_condition,
187
- # Если есть вызовы -> к узлу tools, иначе -> к концу (END)
188
- {
189
- "tools": "tools",
190
- END: END,
191
- },
192
- )
193
-
194
- # Ребро от узла инструментов обратно к агенту для обработки результата
195
- workflow.add_edge("tools", "agent")
196
-
197
- # Компиляция графа
198
- agent_graph = workflow.compile()
199
-
200
- # --- Функция для запуска агента ---
201
- def run_final_agent(query: str) -> str:
202
- """Запускает финального агента LangGraph для ответа на вопрос."""
203
- if agent_graph is None:
204
- return "Ошибка: Граф агента не скомпилирован."
205
 
206
- # Начальное состояние с запросом пользователя
207
- initial_state = {"messages": [{"role": "user", "content": query}]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
- final_state = None
210
  try:
211
- # Запуск графа
212
- final_state = agent_graph.invoke(initial_state, {"recursion_limit": 10})
213
-
214
  except Exception as e:
215
- print(f"Ошибка выполнения графа: {e}")
216
- return f"Произошла ошибка во время обработки запроса: {e}"
217
-
218
- # Извлечение финального ответа из состояния
219
- if final_state and "messages" in final_state and final_state["messages"]:
220
- # Ищем последнее сообщение от ассистента без вызова инструментов
221
- for msg in reversed(final_state["messages"]):
222
- # Проверяем, что это сообщение от AI и нет активных tool_calls
223
- is_ai = (isinstance(msg, dict) and msg.get("role") == "assistant") or (hasattr(msg, "type") and msg.type == "ai")
224
- has_tool_calls = (isinstance(msg, dict) and msg.get("tool_calls")) or (hasattr(msg, "tool_calls") and msg.tool_calls)
225
-
226
- if is_ai and not has_tool_calls:
227
- return msg.get("content") if isinstance(msg, dict) else msg.content
228
-
229
- # Если не нашли чистого ответа, возвращаем последнее сообщение AI
230
- last_ai_msg = next((m for m in reversed(final_state["messages"]) if (isinstance(m, dict) and m.get("role") == "assistant") or (hasattr(m, "type") and m.type == "ai")), None)
231
- if last_ai_msg:
232
- return last_ai_msg.get("content") if isinstance(last_ai_msg, dict) else last_ai_msg.content
233
-
234
- return "Не удалось получить финальный ответ от агента."
235
 
236
- # --- API для Hugging Face Spaces ---
237
- # Если запускается как веб-приложение
238
  if __name__ == "__main__":
239
- from fastapi import FastAPI, Request
240
- from fastapi.responses import JSONResponse
241
- import uvicorn
242
-
243
- app = FastAPI(title="Agent Challenge - Финальный агент")
244
-
245
- @app.get("/")
246
- async def root():
247
- return {"message": "Агент готов к работе! Отправьте POST запрос на /agent с JSON {'query': 'ваш вопрос'}"}
248
-
249
- @app.post("/agent")
250
- async def agent_endpoint(request: Request):
251
- try:
252
- data = await request.json()
253
- query = data.get("query", "")
254
-
255
- if not query:
256
- return JSONResponse(
257
- status_code=400,
258
- content={"error": "Запрос должен содержать поле 'query'"}
259
- )
260
-
261
- response = run_final_agent(query)
262
- return {"answer": response}
263
-
264
- except Exception as e:
265
- return JSONResponse(
266
- status_code=500,
267
- content={"error": f"Ошибка обработки запроса: {str(e)}"}
268
- )
269
-
270
- # Для локального запуска (не используется в Hugging Face Spaces)
271
- if os.environ.get("RUN_LOCAL") == "true":
272
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
  """
2
+ Простой агент для Agent Challenge с использованием Gradio
3
  """
4
 
5
  import os
 
6
  import re
7
  import math
8
+ import json
9
+ import gradio as gr
10
+ from huggingface_hub import InferenceClient
 
 
 
 
 
 
 
11
 
12
  # Безопасная обработка токена Hugging Face
13
  # Токен должен быть установлен как переменная окружения HUGGINGFACE_TOKEN
 
17
  # Инициализация клиента Hugging Face
18
  client = None
19
  try:
 
20
  client = InferenceClient(
21
  model="mistralai/Mixtral-8x7B-Instruct-v0.1", # Рекомендуемая модель
22
+ token=HUGGINGFACE_TOKEN
 
23
  )
 
 
24
  except Exception as e:
25
  print(f"Ошибка инициализации InferenceClient: {e}. Проверьте токен и доступность модели.")
26
 
27
  # --- Определение инструментов ---
28
 
 
29
  def calculator(expression: str) -> str:
30
+ """Выполняет математические вычисления."""
 
 
 
31
  try:
32
  # Ограничение на доступные функции для безопасности
33
  allowed_names = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}
 
36
  allowed_names["max"] = max
37
  allowed_names["min"] = min
38
 
39
+ # Удаление потенциально опасных символов
40
  safe_expression = re.sub(r"[^0-9\.\+\-\*\/\(\)\s]|\b(import|exec|eval|open|lambda|\_\_)\b", "", expression)
41
 
42
  if safe_expression != expression:
 
47
  except Exception as e:
48
  return f"Ошибка в вычислении: {str(e)}"
49
 
 
50
  def web_search(query: str) -> str:
51
+ """Выполняет поиск в интернете по заданному запросу (симуляция)."""
52
+ # Простая симуляция для теста
53
+ if "погода" in query.lower():
54
+ return городе, который вы ищете, сегодня солнечно, +25C."
55
+ elif "hugging face" in query.lower():
56
+ return "Hugging Face - это платформа и сообщество для работы с моделями машинного обучения."
57
+ elif "python" in query.lower():
58
+ return "Python - высокоуровневый язык программирования общего назначения, созданный Гвидо ван Россумом."
59
+ else:
60
+ return f"По вашему запросу '{query}' найдена общая информация."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ # --- Функция для запуска агента ---
63
+ def run_agent(query: str) -> str:
64
+ """Запускает агента для ответа на вопрос."""
 
 
 
 
 
 
65
  if client is None:
66
+ return "Ошибка: Клиент Hugging Face не инициализирован. Проверьте токен и доступность модели."
 
 
 
 
67
 
68
+ # Определение инструментов для модели
69
+ tools = [
70
+ {
71
+ "type": "function",
72
+ "function": {
73
+ "name": "calculator",
74
+ "description": "Выполняет математические вычисления",
75
+ "parameters": {
76
+ "type": "object",
77
+ "properties": {
78
+ "expression": {
79
+ "type": "string",
80
+ "description": "Математическое выражение для вычисления"
81
+ }
82
+ },
83
+ "required": ["expression"]
84
+ }
85
+ }
86
+ },
87
+ {
88
+ "type": "function",
89
+ "function": {
90
+ "name": "web_search",
91
+ "description": "Ищет информацию в интернете",
92
+ "parameters": {
93
+ "type": "object",
94
+ "properties": {
95
+ "query": {
96
+ "type": "string",
97
+ "description": "Поисковый запрос"
98
+ }
99
+ },
100
+ "required": ["query"]
101
+ }
102
+ }
103
+ }
104
+ ]
105
 
106
+ # Начальное сообщение от пользователя
107
+ messages = [{"role": "user", "content": query}]
 
 
 
 
 
 
 
 
108
 
109
+ # Максимальное количество итераций
110
+ max_iterations = 5
111
 
112
+ for _ in range(max_iterations):
113
+ # Вызов модели
114
+ response = client.chat_completion(
115
+ messages=messages,
116
+ tools=tools,
117
+ tool_choice="auto",
118
+ temperature=0.1,
119
+ max_tokens=1024
120
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ # Получение ответа модели
123
+ assistant_message = response["choices"][0]["message"]
124
+ messages.append(assistant_message)
125
+
126
+ # Проверка на наличие вызовов инструментов
127
+ if "tool_calls" in assistant_message and assistant_message["tool_calls"]:
128
+ for tool_call in assistant_message["tool_calls"]:
129
+ # Получение имени и аргументов инструмента
130
+ function_name = tool_call["function"]["name"]
131
+ function_args = json.loads(tool_call["function"]["arguments"])
132
+
133
+ # Вызов соответствующего инструмента
134
+ if function_name == "calculator":
135
+ result = calculator(function_args["expression"])
136
+ elif function_name == "web_search":
137
+ result = web_search(function_args["query"])
138
+ else:
139
+ result = f"Инструмент {function_name} не найден."
140
+
141
+ # Добавление результата в сообщения
142
+ messages.append({
143
+ "role": "tool",
144
+ "tool_call_id": tool_call["id"],
145
+ "name": function_name,
146
+ "content": result
147
+ })
148
+ else:
149
+ # Если нет вызовов инструментов, возвращаем ответ
150
+ return assistant_message["content"]
151
+
152
+ # Если достигнуто максимальное количество итераций, возвращаем последний ответ
153
+ return messages[-1]["content"]
154
+
155
+ # --- Создание Gradio интерфейса ---
156
+ def gradio_interface(query):
157
+ """Обработчик для Gradio интерфейса."""
158
+ if not query.strip():
159
+ return "Пожалуйста, введите вопрос."
160
 
 
161
  try:
162
+ response = run_agent(query)
163
+ return response
 
164
  except Exception as e:
165
+ return f"Произошла ошибка: {str(e)}"
166
+
167
+ # Создание интерфейса
168
+ demo = gr.Interface(
169
+ fn=gradio_interface,
170
+ inputs=gr.Textbox(lines=2, placeholder="Введите ваш вопрос здесь..."),
171
+ outputs="text",
172
+ title="Agent Challenge - Финальный агент",
173
+ description="Этот агент может отвечать на вопросы, выполнять математические вычисления и искать информацию."
174
+ )
 
 
 
 
 
 
 
 
 
 
175
 
176
+ # Запуск интерфейса
 
177
  if __name__ == "__main__":
178
+ demo.launch()