Spaces:
Runtime error
Runtime error
dddd
Browse files- my_tools.py +37 -86
my_tools.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
import os
|
| 4 |
import math
|
|
@@ -9,19 +9,17 @@ import pandas as pd
|
|
| 9 |
from io import StringIO
|
| 10 |
from duckduckgo_search import DDGS
|
| 11 |
import wikipedia
|
| 12 |
-
from llama_index import ServiceContext, LLMPredictor
|
| 13 |
from llama_index.core.llms import ChatMessage, LLMMetadata, LLM, CompletionResponse
|
| 14 |
from llama_index.core.tools import FunctionTool
|
| 15 |
from llama_index.core.agent import ReActAgent
|
| 16 |
from llama_index.indices.object_index import ObjectIndex
|
| 17 |
from llama_index.indices.vector_store import VectorStoreIndex
|
| 18 |
-
from llama_index.core.callbacks import CallbackManager
|
| 19 |
from llama_index.core.callbacks.llama_debug import LlamaDebugHandler
|
| 20 |
from pydantic import Field
|
| 21 |
import google.generativeai as genai
|
| 22 |
|
| 23 |
# -------------------------------------------------------------------
|
| 24 |
-
# 1) GeminiLLM
|
| 25 |
# -------------------------------------------------------------------
|
| 26 |
class GeminiLLM(LLM):
|
| 27 |
model_name: str = Field(default="models/gemini-1.5-flash-latest")
|
|
@@ -117,13 +115,11 @@ class GeminiLLM(LLM):
|
|
| 117 |
|
| 118 |
|
| 119 |
# -------------------------------------------------------------------
|
| 120 |
-
# 2)
|
| 121 |
# -------------------------------------------------------------------
|
| 122 |
-
|
| 123 |
HEADERS = {'User-Agent':'Mozilla/5.0'}
|
| 124 |
|
| 125 |
def buscar_web(query: str, max_attempts: int = 2) -> str:
|
| 126 |
-
"""Búsqueda real en DuckDuckGo con reintentos."""
|
| 127 |
for i in range(max_attempts):
|
| 128 |
try:
|
| 129 |
with DDGS(headers=HEADERS, timeout=25) as ddgs:
|
|
@@ -135,139 +131,94 @@ def buscar_web(query: str, max_attempts: int = 2) -> str:
|
|
| 135 |
if i < max_attempts-1:
|
| 136 |
time.sleep(5*(i+1))
|
| 137 |
else:
|
| 138 |
-
return f"Error
|
| 139 |
|
| 140 |
def reverse_text(text: str) -> str:
|
| 141 |
return text[::-1]
|
| 142 |
|
| 143 |
def analyze_table(table_md: str, question: str) -> str:
|
| 144 |
-
"""
|
| 145 |
-
Parsea tabla Markdown y responde preguntas sencillas.
|
| 146 |
-
Ejemplo específico: acciones no conmutativas.
|
| 147 |
-
"""
|
| 148 |
try:
|
| 149 |
-
# Convertir Markdown a CSV-like
|
| 150 |
lines = [l.strip() for l in table_md.strip().splitlines() if l.strip()]
|
| 151 |
-
# Quitar las líneas de separador '---'
|
| 152 |
content = [l for l in lines if not set(l) <= set("|- ")]
|
| 153 |
-
# Extraer filas
|
| 154 |
rows = [ [c.strip() for c in r.split("|")[1:-1]] for r in content ]
|
| 155 |
df = pd.DataFrame(rows[1:], columns=rows[0])
|
| 156 |
-
# Si la pregunta menciona 'conmutativa', detectamos contrajemplos
|
| 157 |
if "no conmut" in question.lower():
|
| 158 |
-
S =
|
| 159 |
counter = set()
|
| 160 |
for x in S:
|
| 161 |
for y in S:
|
| 162 |
a = df.loc[df[rows[0][0]]==x, y].values[0]
|
| 163 |
b = df.loc[df[rows[0][0]]==y, x].values[0]
|
| 164 |
if a != b:
|
| 165 |
-
counter.
|
| 166 |
return ", ".join(sorted(counter)) or "No hay contraejemplos"
|
| 167 |
-
# Para otras preguntas, devolvemos la tabla en texto
|
| 168 |
return df.to_csv(index=False)
|
| 169 |
except Exception as e:
|
| 170 |
return f"Error analyze_table: {e}"
|
| 171 |
|
| 172 |
def execute_code(code: str) -> str:
|
| 173 |
-
"""Ejecuta código Python en un subproceso seguro."""
|
| 174 |
try:
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
if
|
| 178 |
-
return f"Error
|
| 179 |
-
return
|
| 180 |
except Exception as e:
|
| 181 |
-
return f"Error
|
| 182 |
|
| 183 |
def no_tool_solution(query: str) -> str:
|
| 184 |
-
return "Procedo
|
| 185 |
|
| 186 |
# -------------------------------------------------------------------
|
| 187 |
-
# 3)
|
| 188 |
# -------------------------------------------------------------------
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
table_tool = FunctionTool.from_defaults(fn=analyze_table, name="analyze_table",
|
| 195 |
-
description="Parsea tabla Markdown y responde preguntas sobre conmutatividad u otras sencillas.")
|
| 196 |
-
code_tool = FunctionTool.from_defaults(fn=execute_code, name="execute_code",
|
| 197 |
-
description="Ejecuta fragmentos de código Python para cálculos o procesamiento.")
|
| 198 |
-
fallback_tool = FunctionTool.from_defaults(fn=no_tool_solution, name="no_tool_solution",
|
| 199 |
-
description="Fallback: responde usando únicamente conocimiento interno si otras herramientas fallan.")
|
| 200 |
|
| 201 |
# -------------------------------------------------------------------
|
| 202 |
-
# 4)
|
| 203 |
# -------------------------------------------------------------------
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
obj_index = ObjectIndex.from_objects(all_tools, index_cls=VectorStoreIndex)
|
| 207 |
tool_retriever = obj_index.as_retriever(similarity_top_k=3)
|
| 208 |
|
| 209 |
# -------------------------------------------------------------------
|
| 210 |
# 5) Prompt de sistema
|
| 211 |
# -------------------------------------------------------------------
|
| 212 |
-
|
| 213 |
system_prompt = (
|
| 214 |
-
"Eres Alfred, un agente ReAct
|
| 215 |
-
"Sigue este flujo:\n"
|
| 216 |
"1) Analiza la pregunta.\n"
|
| 217 |
-
"2)
|
| 218 |
-
"3)
|
| 219 |
-
"4) Si
|
| 220 |
-
"5)
|
| 221 |
-
"
|
| 222 |
-
"7) Si nada ayuda, llama no_tool_solution y responde con tu conocimiento.\n"
|
| 223 |
-
"8) Si la pregunta involucra audio, video o imágenes no procesables, informa que no puedes acceder.\n"
|
| 224 |
-
"9) Proporciona siempre una respuesta final clara al usuario.\n"
|
| 225 |
-
"\nHerramientas disponibles:\n{tool_descriptions}\n"
|
| 226 |
-
"No muestres tus pensamientos al usuario; solo la respuesta final."
|
| 227 |
)
|
| 228 |
|
| 229 |
# -------------------------------------------------------------------
|
| 230 |
-
# 6) Inicializar
|
| 231 |
# -------------------------------------------------------------------
|
|
|
|
| 232 |
|
| 233 |
-
# Contexto de LLM
|
| 234 |
-
service_ctx = ServiceContext.from_defaults(
|
| 235 |
-
llm=GeminiLLM(model_name="models/gemini-1.5-flash-latest", temperature=0.0)
|
| 236 |
-
)
|
| 237 |
-
|
| 238 |
-
# Crear el agente ReAct
|
| 239 |
alfred_agent = ReActAgent.from_tools(
|
| 240 |
tool_retriever=tool_retriever,
|
| 241 |
-
|
| 242 |
system_prompt=system_prompt,
|
| 243 |
-
verbose=True,
|
| 244 |
-
max_iterations=20
|
| 245 |
)
|
| 246 |
|
| 247 |
# -------------------------------------------------------------------
|
| 248 |
-
# 7) Función
|
| 249 |
# -------------------------------------------------------------------
|
| 250 |
-
|
| 251 |
def basic_agent_response(question: str) -> str:
|
| 252 |
-
print(f"🤖 Alfred recibió: {question}")
|
| 253 |
try:
|
| 254 |
-
|
| 255 |
-
return
|
| 256 |
except Exception as e:
|
| 257 |
-
return f"Error crítico
|
| 258 |
|
| 259 |
-
# -------------------------------------------------------------------
|
| 260 |
-
# 8) Prueba rápida (descomentar para test local)
|
| 261 |
-
# -------------------------------------------------------------------
|
| 262 |
-
if __name__ == "__main__":
|
| 263 |
-
tests = [
|
| 264 |
-
"¿Cuánto es 37 por 19?",
|
| 265 |
-
".tfel",
|
| 266 |
-
"|*|a|b|\n|---|---|---|\n|a|b|c|\n|b|c|a|\nprovide the subset involved in non-commutative",
|
| 267 |
-
"I'm making a grocery list para mi mamá..., incluye milk, eggs, bell pepper, zucchini. Solo vegetales."
|
| 268 |
-
]
|
| 269 |
-
for q in tests:
|
| 270 |
-
print("Q:", q)
|
| 271 |
-
print("A:", basic_agent_response(q))
|
| 272 |
-
print("-"*40)
|
| 273 |
|
|
|
|
| 1 |
+
# my_tools.py
|
| 2 |
|
| 3 |
import os
|
| 4 |
import math
|
|
|
|
| 9 |
from io import StringIO
|
| 10 |
from duckduckgo_search import DDGS
|
| 11 |
import wikipedia
|
|
|
|
| 12 |
from llama_index.core.llms import ChatMessage, LLMMetadata, LLM, CompletionResponse
|
| 13 |
from llama_index.core.tools import FunctionTool
|
| 14 |
from llama_index.core.agent import ReActAgent
|
| 15 |
from llama_index.indices.object_index import ObjectIndex
|
| 16 |
from llama_index.indices.vector_store import VectorStoreIndex
|
|
|
|
| 17 |
from llama_index.core.callbacks.llama_debug import LlamaDebugHandler
|
| 18 |
from pydantic import Field
|
| 19 |
import google.generativeai as genai
|
| 20 |
|
| 21 |
# -------------------------------------------------------------------
|
| 22 |
+
# 1) GeminiLLM personalizado
|
| 23 |
# -------------------------------------------------------------------
|
| 24 |
class GeminiLLM(LLM):
|
| 25 |
model_name: str = Field(default="models/gemini-1.5-flash-latest")
|
|
|
|
| 115 |
|
| 116 |
|
| 117 |
# -------------------------------------------------------------------
|
| 118 |
+
# 2) Herramientas
|
| 119 |
# -------------------------------------------------------------------
|
|
|
|
| 120 |
HEADERS = {'User-Agent':'Mozilla/5.0'}
|
| 121 |
|
| 122 |
def buscar_web(query: str, max_attempts: int = 2) -> str:
|
|
|
|
| 123 |
for i in range(max_attempts):
|
| 124 |
try:
|
| 125 |
with DDGS(headers=HEADERS, timeout=25) as ddgs:
|
|
|
|
| 131 |
if i < max_attempts-1:
|
| 132 |
time.sleep(5*(i+1))
|
| 133 |
else:
|
| 134 |
+
return f"Error buscar_web tras {max_attempts} intentos: {e}"
|
| 135 |
|
| 136 |
def reverse_text(text: str) -> str:
|
| 137 |
return text[::-1]
|
| 138 |
|
| 139 |
def analyze_table(table_md: str, question: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
try:
|
|
|
|
| 141 |
lines = [l.strip() for l in table_md.strip().splitlines() if l.strip()]
|
|
|
|
| 142 |
content = [l for l in lines if not set(l) <= set("|- ")]
|
|
|
|
| 143 |
rows = [ [c.strip() for c in r.split("|")[1:-1]] for r in content ]
|
| 144 |
df = pd.DataFrame(rows[1:], columns=rows[0])
|
|
|
|
| 145 |
if "no conmut" in question.lower():
|
| 146 |
+
S = df.columns.tolist()
|
| 147 |
counter = set()
|
| 148 |
for x in S:
|
| 149 |
for y in S:
|
| 150 |
a = df.loc[df[rows[0][0]]==x, y].values[0]
|
| 151 |
b = df.loc[df[rows[0][0]]==y, x].values[0]
|
| 152 |
if a != b:
|
| 153 |
+
counter.update([x,y])
|
| 154 |
return ", ".join(sorted(counter)) or "No hay contraejemplos"
|
|
|
|
| 155 |
return df.to_csv(index=False)
|
| 156 |
except Exception as e:
|
| 157 |
return f"Error analyze_table: {e}"
|
| 158 |
|
| 159 |
def execute_code(code: str) -> str:
|
|
|
|
| 160 |
try:
|
| 161 |
+
res = subprocess.run(["python","- << 'EOF'\n" + code + "\nEOF"], shell=True,
|
| 162 |
+
capture_output=True, text=True, timeout=5)
|
| 163 |
+
if res.stderr:
|
| 164 |
+
return f"Error código: {res.stderr.strip()}"
|
| 165 |
+
return res.stdout.strip() or "(sin salida)"
|
| 166 |
except Exception as e:
|
| 167 |
+
return f"Error ejecutar código: {e}"
|
| 168 |
|
| 169 |
def no_tool_solution(query: str) -> str:
|
| 170 |
+
return "Procedo con conocimiento interno."
|
| 171 |
|
| 172 |
# -------------------------------------------------------------------
|
| 173 |
+
# 3) FunctionTools
|
| 174 |
# -------------------------------------------------------------------
|
| 175 |
+
search_tool = FunctionTool.from_defaults(fn=buscar_web, name="web_search", description="Búsqueda DDG (3 resultados).")
|
| 176 |
+
reverse_tool = FunctionTool.from_defaults(fn=reverse_text, name="reverse_text", description="Invierte texto.")
|
| 177 |
+
table_tool = FunctionTool.from_defaults(fn=analyze_table, name="analyze_table", description="Parses Markdown tables y responde conmutatividad.")
|
| 178 |
+
code_tool = FunctionTool.from_defaults(fn=execute_code, name="execute_code", description="Ejecuta Python para cálculos.")
|
| 179 |
+
fallback_tool = FunctionTool.from_defaults(fn=no_tool_solution, name="no_tool_solution", description="Fallback: conocimiento interno.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
# -------------------------------------------------------------------
|
| 182 |
+
# 4) ObjectIndex + Retriever
|
| 183 |
# -------------------------------------------------------------------
|
| 184 |
+
all_tools = [search_tool, reverse_tool, table_tool, code_tool, fallback_tool]
|
| 185 |
+
obj_index = ObjectIndex.from_objects(all_tools, index_cls=VectorStoreIndex)
|
|
|
|
| 186 |
tool_retriever = obj_index.as_retriever(similarity_top_k=3)
|
| 187 |
|
| 188 |
# -------------------------------------------------------------------
|
| 189 |
# 5) Prompt de sistema
|
| 190 |
# -------------------------------------------------------------------
|
|
|
|
| 191 |
system_prompt = (
|
| 192 |
+
"Eres Alfred, un agente ReAct paso a paso.\n"
|
|
|
|
| 193 |
"1) Analiza la pregunta.\n"
|
| 194 |
+
"2) Decide la herramienta adecuada.\n"
|
| 195 |
+
"3) Usa web_search, reverse_text, analyze_table, execute_code o no_tool_solution.\n"
|
| 196 |
+
"4) Si no puedes procesar audio/video, indícalo.\n"
|
| 197 |
+
"5) Responde claramente.\n"
|
| 198 |
+
"Herramientas: {tool_descriptions}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
)
|
| 200 |
|
| 201 |
# -------------------------------------------------------------------
|
| 202 |
+
# 6) Inicializar agente
|
| 203 |
# -------------------------------------------------------------------
|
| 204 |
+
llm = GeminiLLM(model_name="models/gemini-1.5-flash-latest", temperature=0.0)
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
alfred_agent = ReActAgent.from_tools(
|
| 207 |
tool_retriever=tool_retriever,
|
| 208 |
+
llm=llm,
|
| 209 |
system_prompt=system_prompt,
|
| 210 |
+
verbose=True,
|
| 211 |
+
max_iterations=20
|
| 212 |
)
|
| 213 |
|
| 214 |
# -------------------------------------------------------------------
|
| 215 |
+
# 7) Función pública
|
| 216 |
# -------------------------------------------------------------------
|
|
|
|
| 217 |
def basic_agent_response(question: str) -> str:
|
|
|
|
| 218 |
try:
|
| 219 |
+
resp = alfred_agent.query(question)
|
| 220 |
+
return resp.response or "No se generó respuesta."
|
| 221 |
except Exception as e:
|
| 222 |
+
return f"Error crítico del agente: {e}"
|
| 223 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|