Spaces:
Runtime error
Runtime error
lll
Browse files- my_tools.py +86 -63
my_tools.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
# my_tools.py
|
| 2 |
-
|
| 3 |
import os
|
| 4 |
import math
|
| 5 |
import time
|
|
@@ -7,7 +5,7 @@ import asyncio
|
|
| 7 |
import subprocess
|
| 8 |
import requests
|
| 9 |
import pandas as pd
|
| 10 |
-
from io import BytesIO
|
| 11 |
from bs4 import BeautifulSoup
|
| 12 |
from duckduckgo_search import DDGS
|
| 13 |
import wikipedia
|
|
@@ -40,20 +38,19 @@ class GeminiLLM(LLM):
|
|
| 40 |
|
| 41 |
def __init__(self, **kwargs):
|
| 42 |
super().__init__(**kwargs)
|
| 43 |
-
# Resolver FieldInfo si es necesario
|
| 44 |
actual_model_name = self.model_name
|
| 45 |
if not isinstance(actual_model_name, str):
|
| 46 |
-
|
| 47 |
-
if
|
| 48 |
-
actual_model_name =
|
| 49 |
if not isinstance(actual_model_name, str):
|
| 50 |
actual_model_name = "models/gemini-1.5-flash-latest"
|
| 51 |
|
| 52 |
actual_temperature = self.temperature
|
| 53 |
if not isinstance(actual_temperature, (float, int)):
|
| 54 |
-
|
| 55 |
-
if
|
| 56 |
-
actual_temperature =
|
| 57 |
if not isinstance(actual_temperature, (float, int)):
|
| 58 |
actual_temperature = 0.0
|
| 59 |
|
|
@@ -78,9 +75,9 @@ class GeminiLLM(LLM):
|
|
| 78 |
def metadata(self):
|
| 79 |
actual_model_name_meta = self.model_name
|
| 80 |
if not isinstance(actual_model_name_meta, str):
|
| 81 |
-
|
| 82 |
-
if
|
| 83 |
-
actual_model_name_meta =
|
| 84 |
if not isinstance(actual_model_name_meta, str):
|
| 85 |
actual_model_name_meta = "models/gemini-1.5-flash-latest"
|
| 86 |
return LLMMetadata(
|
|
@@ -201,7 +198,6 @@ def analyze_table(table_md: str, question: str) -> str:
|
|
| 201 |
verifica la conmutatividad de la matriz; en otro caso, devuelve el CSV equivalente.
|
| 202 |
"""
|
| 203 |
try:
|
| 204 |
-
# Quitar líneas de separación y vacías
|
| 205 |
lines = [l for l in table_md.splitlines() if l.strip() and '---' not in l]
|
| 206 |
rows = [[c.strip() for c in l.strip().strip('|').split('|')] for l in lines]
|
| 207 |
if len(rows) < 2:
|
|
@@ -265,16 +261,26 @@ def classify_botanical(items_list_str: str) -> str:
|
|
| 265 |
"""
|
| 266 |
Clasifica botánicamente una lista de alimentos (en inglés o español) en Verduras, Frutas u Otros.
|
| 267 |
"""
|
| 268 |
-
# Mapeo inglés → español
|
| 269 |
mapping = {
|
| 270 |
"tomato": "tomate", "pepper": "pimiento", "bell pepper": "pimiento",
|
| 271 |
-
"
|
| 272 |
-
"
|
| 273 |
-
"
|
| 274 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
}
|
| 276 |
-
fruits = {"tomate", "pepino", "calabacín", "berenjena", "pimiento", "aguacate", "calabaza", "guisante", "judía verde", "maíz"}
|
| 277 |
-
vegetables = {"zanahoria", "patata", "batata", "cebolla", "ajo", "puerro", "apio", "lechuga", "espinaca", "brócoli", "pepino", "pepino"}
|
| 278 |
|
| 279 |
items = []
|
| 280 |
for raw in items_list_str.split(','):
|
|
@@ -282,13 +288,18 @@ def classify_botanical(items_list_str: str) -> str:
|
|
| 282 |
itm_es = mapping.get(itm, itm)
|
| 283 |
items.append(itm_es)
|
| 284 |
|
| 285 |
-
vegs = [i for i in items if i in
|
| 286 |
-
fruits_found = [i for i in items if i in
|
| 287 |
-
others = [i for i in items if i not in
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
return (
|
| 289 |
-
f"Verduras: {', '.join(
|
| 290 |
-
f"Frutas: {', '.join(
|
| 291 |
-
f"Otros: {', '.join(
|
| 292 |
)
|
| 293 |
|
| 294 |
def scrape_wikipedia_table(page_title: str, section: str, table_index: int = 0) -> str:
|
|
@@ -380,11 +391,11 @@ all_tools = [
|
|
| 380 |
tool_descriptions = "\n".join([
|
| 381 |
f"{t.metadata.name}: {t.metadata.description} "
|
| 382 |
+ {
|
| 383 |
-
"classify_botanical_foods": "(Ej: classify_botanical_foods('
|
| 384 |
"read_excel_data": "(Ej: read_excel_data('ventas.xlsx', sheet_name=0))",
|
| 385 |
"analyze_markdown_table": "(Ej: analyze_markdown_table('| A | B |\\n|---|---|\\n|1|2|', '¿Es conmut?'))",
|
| 386 |
-
"web_search": "(Ej: web_search('
|
| 387 |
-
"scrape_wiki_table": "(Ej: scrape_wiki_table('
|
| 388 |
"reverse_text": "(Ej: reverse_text('Hola'))",
|
| 389 |
"execute_code": "(Ej: execute_code('5*7'))",
|
| 390 |
}.get(t.metadata.name, "")
|
|
@@ -392,30 +403,50 @@ tool_descriptions = "\n".join([
|
|
| 392 |
])
|
| 393 |
|
| 394 |
# -------------------------------------------------------------------
|
| 395 |
-
# 6) PROMPT DE SISTEMA MEJORADO
|
| 396 |
# -------------------------------------------------------------------
|
| 397 |
system_prompt = f"""
|
| 398 |
Eres Alfred, un agente ReAct eficiente y preciso. Tu objetivo es responder correctamente usando las herramientas disponibles.
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
|
| 420 |
Herramientas disponibles (USAR EXÁCTAMENTE estos nombres):
|
| 421 |
{tool_descriptions}
|
|
@@ -430,31 +461,23 @@ alfred_agent = ReActAgent.from_tools(
|
|
| 430 |
llm=llm,
|
| 431 |
system_prompt=system_prompt,
|
| 432 |
verbose=True,
|
| 433 |
-
max_iterations=25,
|
| 434 |
callback_manager=llm.callback_manager,
|
| 435 |
-
handle_parsing_errors=True
|
| 436 |
)
|
| 437 |
|
| 438 |
def basic_agent_response(question: str) -> str:
|
| 439 |
"""
|
| 440 |
-
|
| 441 |
-
De lo contrario, usa ReActAgent.query().
|
| 442 |
"""
|
| 443 |
try:
|
| 444 |
-
# Forzar uso de read_excel_data si aparece Excel en la pregunta
|
| 445 |
if "attached excel" in question.lower() or "archivo excel" in question.lower():
|
| 446 |
-
# En el entorno SAIA normalmente inyectan la ruta real; aquí usamos un placeholder.
|
| 447 |
return read_excel_data("data/attached.xlsx")
|
| 448 |
resp = alfred_agent.query(question)
|
| 449 |
if hasattr(resp, 'response') and resp.response is not None:
|
| 450 |
return str(resp.response)
|
| 451 |
elif resp is not None:
|
| 452 |
return str(resp)
|
| 453 |
-
|
| 454 |
-
return "No se generó una respuesta válida."
|
| 455 |
except Exception as e:
|
| 456 |
return f"Error crítico del agente: {e}"
|
| 457 |
-
|
| 458 |
-
# --- FIN DE my_tools.py ---
|
| 459 |
-
|
| 460 |
-
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import math
|
| 3 |
import time
|
|
|
|
| 5 |
import subprocess
|
| 6 |
import requests
|
| 7 |
import pandas as pd
|
| 8 |
+
from io import BytesIO, StringIO
|
| 9 |
from bs4 import BeautifulSoup
|
| 10 |
from duckduckgo_search import DDGS
|
| 11 |
import wikipedia
|
|
|
|
| 38 |
|
| 39 |
def __init__(self, **kwargs):
|
| 40 |
super().__init__(**kwargs)
|
|
|
|
| 41 |
actual_model_name = self.model_name
|
| 42 |
if not isinstance(actual_model_name, str):
|
| 43 |
+
field_def = self.__fields__.get("model_name")
|
| 44 |
+
if field_def and hasattr(field_def, 'default'):
|
| 45 |
+
actual_model_name = field_def.default
|
| 46 |
if not isinstance(actual_model_name, str):
|
| 47 |
actual_model_name = "models/gemini-1.5-flash-latest"
|
| 48 |
|
| 49 |
actual_temperature = self.temperature
|
| 50 |
if not isinstance(actual_temperature, (float, int)):
|
| 51 |
+
temp_field_def = self.__fields__.get("temperature")
|
| 52 |
+
if temp_field_def and hasattr(temp_field_def, 'default'):
|
| 53 |
+
actual_temperature = temp_field_def.default
|
| 54 |
if not isinstance(actual_temperature, (float, int)):
|
| 55 |
actual_temperature = 0.0
|
| 56 |
|
|
|
|
| 75 |
def metadata(self):
|
| 76 |
actual_model_name_meta = self.model_name
|
| 77 |
if not isinstance(actual_model_name_meta, str):
|
| 78 |
+
field_meta = self.__fields__.get("model_name")
|
| 79 |
+
if field_meta and hasattr(field_meta, 'default'):
|
| 80 |
+
actual_model_name_meta = field_meta.default
|
| 81 |
if not isinstance(actual_model_name_meta, str):
|
| 82 |
actual_model_name_meta = "models/gemini-1.5-flash-latest"
|
| 83 |
return LLMMetadata(
|
|
|
|
| 198 |
verifica la conmutatividad de la matriz; en otro caso, devuelve el CSV equivalente.
|
| 199 |
"""
|
| 200 |
try:
|
|
|
|
| 201 |
lines = [l for l in table_md.splitlines() if l.strip() and '---' not in l]
|
| 202 |
rows = [[c.strip() for c in l.strip().strip('|').split('|')] for l in lines]
|
| 203 |
if len(rows) < 2:
|
|
|
|
| 261 |
"""
|
| 262 |
Clasifica botánicamente una lista de alimentos (en inglés o español) en Verduras, Frutas u Otros.
|
| 263 |
"""
|
|
|
|
| 264 |
mapping = {
|
| 265 |
"tomato": "tomate", "pepper": "pimiento", "bell pepper": "pimiento",
|
| 266 |
+
"green beans": "judía verde", "beans": "judía verde",
|
| 267 |
+
"zucchini": "calabacín", "eggplant": "berenjena", "cucumber": "pepino",
|
| 268 |
+
"broccoli": "brócoli", "celery": "apio", "lettuce": "lechuga",
|
| 269 |
+
"corn": "maíz", "peas": "guisante", "pea": "guisante",
|
| 270 |
+
"spinach": "espinaca", "kale": "col rizada",
|
| 271 |
+
"sweet potatoes": "batata", "sweet potato": "batata", "potato": "patata",
|
| 272 |
+
"onion": "cebolla", "garlic": "ajo", "carrot": "zanahoria",
|
| 273 |
+
"okra": "okra", "cabbage": "col", "cauliflower": "coliflor"
|
| 274 |
+
}
|
| 275 |
+
vegetables_es = {
|
| 276 |
+
"zanahoria","patata","batata","cebolla","ajo","puerro","apio",
|
| 277 |
+
"lechuga","espinaca","brócoli","calabacín","berenjena","pepino",
|
| 278 |
+
"judía verde","maíz","okra","col rizada","col","coliflor"
|
| 279 |
+
}
|
| 280 |
+
fruits_es = {
|
| 281 |
+
"tomate","pepino","calabacín","berenjena","pimiento","aguacate",
|
| 282 |
+
"calabaza","guisante","judía verde","maíz"
|
| 283 |
}
|
|
|
|
|
|
|
| 284 |
|
| 285 |
items = []
|
| 286 |
for raw in items_list_str.split(','):
|
|
|
|
| 288 |
itm_es = mapping.get(itm, itm)
|
| 289 |
items.append(itm_es)
|
| 290 |
|
| 291 |
+
vegs = [i for i in items if i in vegetables_es and i not in fruits_es]
|
| 292 |
+
fruits_found = [i for i in items if i in fruits_es]
|
| 293 |
+
others = [i for i in items if i not in vegetables_es and i not in fruits_es]
|
| 294 |
+
|
| 295 |
+
vegs_sorted = sorted(set(vegs))
|
| 296 |
+
fruits_sorted = sorted(set(fruits_found))
|
| 297 |
+
others_sorted = sorted(set(others))
|
| 298 |
+
|
| 299 |
return (
|
| 300 |
+
f"Verduras: {', '.join(vegs_sorted)}\n"
|
| 301 |
+
f"Frutas: {', '.join(fruits_sorted)}\n"
|
| 302 |
+
f"Otros: {', '.join(others_sorted)}"
|
| 303 |
)
|
| 304 |
|
| 305 |
def scrape_wikipedia_table(page_title: str, section: str, table_index: int = 0) -> str:
|
|
|
|
| 391 |
tool_descriptions = "\n".join([
|
| 392 |
f"{t.metadata.name}: {t.metadata.description} "
|
| 393 |
+ {
|
| 394 |
+
"classify_botanical_foods": "(Ej: classify_botanical_foods('milk, eggs, broccoli, celery, lettuce'))",
|
| 395 |
"read_excel_data": "(Ej: read_excel_data('ventas.xlsx', sheet_name=0))",
|
| 396 |
"analyze_markdown_table": "(Ej: analyze_markdown_table('| A | B |\\n|---|---|\\n|1|2|', '¿Es conmut?'))",
|
| 397 |
+
"web_search": "(Ej: web_search('Hokkaido Nippon-Ham Fighters roster'))",
|
| 398 |
+
"scrape_wiki_table": "(Ej: scrape_wiki_table('Malko Competition', 'Winners', 0))",
|
| 399 |
"reverse_text": "(Ej: reverse_text('Hola'))",
|
| 400 |
"execute_code": "(Ej: execute_code('5*7'))",
|
| 401 |
}.get(t.metadata.name, "")
|
|
|
|
| 403 |
])
|
| 404 |
|
| 405 |
# -------------------------------------------------------------------
|
| 406 |
+
# 6) PROMPT DE SISTEMA MEJORADO with few-shot examples
|
| 407 |
# -------------------------------------------------------------------
|
| 408 |
system_prompt = f"""
|
| 409 |
Eres Alfred, un agente ReAct eficiente y preciso. Tu objetivo es responder correctamente usando las herramientas disponibles.
|
| 410 |
+
A continuación tienes ejemplos de cómo usar cada herramienta:
|
| 411 |
+
|
| 412 |
+
### EJEMPLO 1: Clasificación botánica
|
| 413 |
+
Usuario: "I have a grocery list: milk, eggs, broccoli, celery, lettuce. Please give me only the vegetables, alphabetized, comma-separated."
|
| 414 |
+
Agente (pensando): "La pregunta menciona 'grocery list' y 'vegetables' -> usar classify_botanical_foods"
|
| 415 |
+
Agente (acción):
|
| 416 |
+
{{"tool": "classify_botanical_foods", "input": "milk, eggs, broccoli, celery, lettuce"}}
|
| 417 |
+
Observación: "Verduras: broccoli, celery, lettuce\nFrutas: \nOtros: eggs, milk"
|
| 418 |
+
Agente (respuesta final): "Verduras: broccoli, celery, lettuce"
|
| 419 |
+
|
| 420 |
+
### EJEMPLO 2: Excel ventas de comida
|
| 421 |
+
Usuario: "Attached is an Excel file with sales data. What were the total sales from food only?"
|
| 422 |
+
Agente (pensando): "Menciona 'Excel' y 'food' -> usar read_excel_data"
|
| 423 |
+
Agente (acción):
|
| 424 |
+
{{"tool": "read_excel_data", "input": "data/attached.xlsx"}}
|
| 425 |
+
Observación (CSV): "item,type,sales\nBurger,food,1000\nSoda,drink,200\nPizza,food,1500"
|
| 426 |
+
Agente (respuesta final): "Total food sales: $2500.00"
|
| 427 |
+
|
| 428 |
+
### EJEMPLO 3: Roster de jugadores
|
| 429 |
+
Usuario: "Who are the pitchers with the numbers before and after Taishō Tamai as of July 2023? Use last names only, Roman characters."
|
| 430 |
+
Agente (pensando): "La pregunta menciona 'Taishō Tamai' y 'pitchers' -> usar scrape_wiki_table o web_search"
|
| 431 |
+
Agente (acción):
|
| 432 |
+
{{"tool": "scrape_wiki_table", "input": ["Hokkaido Nippon-Ham Fighters roster", "Players", 0]}}
|
| 433 |
+
Observación (CSV): "Number,Name,Position\n... ,Tamai Taishō,Pitcher\n45,Uehara,Pitcher\n47,Kakui,Pitcher, ..."
|
| 434 |
+
Agente (procesar): "Tamai tiene número 46. El pitcher 45 es Uehara, el 47 es Kakui."
|
| 435 |
+
Agente (respuesta final): "Uehara, Kakui"
|
| 436 |
+
|
| 437 |
+
### FLUJO GENERAL:
|
| 438 |
+
1. LEE la pregunta y detecta palabras clave:
|
| 439 |
+
- "grocery list", "vegetables" -> classify_botanical_foods
|
| 440 |
+
- "attached Excel", "sales" -> read_excel_data
|
| 441 |
+
- "roster", "pitchers", "number" -> scrape_wiki_table o web_search
|
| 442 |
+
- "Malko Competition" -> scrape_wiki_table
|
| 443 |
+
- "tabla Markdown" -> analyze_markdown_table
|
| 444 |
+
- "invertir texto" -> reverse_text
|
| 445 |
+
- "ejecutar código" -> execute_code
|
| 446 |
+
2. PRODUCE el "TOOL CALL" en formato JSON con el nombre exacto de la herramienta.
|
| 447 |
+
3. EJECUTA la herramienta y recibe la salida.
|
| 448 |
+
4. PROCESA la salida (filter, sumar, ordenar) en Python si es necesario.
|
| 449 |
+
5. RESPONDE con el formato exacto que SAIA espera (solo la parte solicitada, sin texto extra).
|
| 450 |
|
| 451 |
Herramientas disponibles (USAR EXÁCTAMENTE estos nombres):
|
| 452 |
{tool_descriptions}
|
|
|
|
| 461 |
llm=llm,
|
| 462 |
system_prompt=system_prompt,
|
| 463 |
verbose=True,
|
| 464 |
+
max_iterations=25,
|
| 465 |
callback_manager=llm.callback_manager,
|
| 466 |
+
handle_parsing_errors=True
|
| 467 |
)
|
| 468 |
|
| 469 |
def basic_agent_response(question: str) -> str:
|
| 470 |
"""
|
| 471 |
+
Detecta "Excel adjunto" o usa ReActAgent.query para el resto.
|
|
|
|
| 472 |
"""
|
| 473 |
try:
|
|
|
|
| 474 |
if "attached excel" in question.lower() or "archivo excel" in question.lower():
|
|
|
|
| 475 |
return read_excel_data("data/attached.xlsx")
|
| 476 |
resp = alfred_agent.query(question)
|
| 477 |
if hasattr(resp, 'response') and resp.response is not None:
|
| 478 |
return str(resp.response)
|
| 479 |
elif resp is not None:
|
| 480 |
return str(resp)
|
| 481 |
+
return "No se generó una respuesta válida."
|
|
|
|
| 482 |
except Exception as e:
|
| 483 |
return f"Error crítico del agente: {e}"
|
|
|
|
|
|
|
|
|
|
|
|