Introducción
La arquitectura inicial era muy sencilla:
Python
↓
ping 192.168.1.1
↓
resultado
↓
Ollama
↓
análisis
El problema de este planteamiento es que Python decidía previamente qué hacer. Ollama únicamente recibía los datos y los interpretaba.
En este nuevo experimento vamos a dar un paso más: construiremos un agente autónomo de diagnóstico de red.
Ahora Ollama podrá decidir qué herramienta necesita utilizar, Python ejecutará esa herramienta y el resultado volverá al modelo para que pueda decidir cuál será el siguiente paso.
La arquitectura será:
Usuario
↓
Ollama
↓
Decisión
↓
Python
↓
Herramienta
↓
Resultado
↓
Ollama
↓
Nueva decisión
↓
...
↓
Diagnóstico final
El objetivo es conseguir que el agente pueda recibir una petición como:
Mi conexión a Internet funciona mal.
y realizar progresivamente las comprobaciones necesarias.
1. Del chatbot al agente
La diferencia entre los dos experimentos es importante.
En la primera versión teníamos:
1. Python ejecuta ping.
2. Python obtiene el resultado.
3. Python envía el resultado a Ollama.
4. Ollama explica el resultado.
La secuencia estaba completamente programada.
En la nueva versión:
1. El usuario plantea un problema.
2. Ollama decide qué información necesita.
3. Python ejecuta una herramienta.
4. Python devuelve el resultado.
5. Ollama analiza el resultado.
6. Ollama decide qué hacer a continuación.
7. Python ejecuta otra herramienta.
8. El ciclo continúa.
9. Ollama genera el diagnóstico.
Por tanto, el programa incorpora un ciclo de:
decisión → acción → observación → nueva decisión.
2. Tecnologías utilizadas
El laboratorio utiliza:
- Python.
- Ollama.
llama3.1:latest.- API HTTP de Ollama.
- Tool calling.
subprocessde Python.- Herramientas de diagnóstico de red.
En el equipo utilizado para la prueba, Ollama estaba disponible mediante:
ollama --version
con una versión:
ollama version is 0.34.4
El modelo instalado era:
llama3.1:latest
Podemos comprobar los modelos disponibles mediante:
ollama list
3. Comprobar la API de Ollama
Ollama proporciona una API local.
La dirección utilizada en el laboratorio es:
http://localhost:11434
Podemos comprobar los modelos disponibles mediante:
curl http://localhost:11434/api/tags
La respuesta contiene información sobre los modelos instalados.
En nuestro caso:
llama3.1:latest
Por tanto, en Python utilizaremos:
MODEL = "llama3.1:latest"
4. Instalar requests
Python necesita comunicarse con Ollama mediante HTTP.
Instalamos la biblioteca:
pip install requests
En algunos sistemas:
pip3 install requests
5. Las herramientas del agente
Para evitar que el modelo pueda ejecutar comandos arbitrarios, vamos a proporcionar al agente herramientas concretas.
En esta primera versión tendrá dos herramientas:
network_info
Obtiene información sobre la configuración de red.
ping
Realiza una prueba ICMP contra un destino permitido.
La idea es:
Ollama
│
├── network_info()
│
└── ping(host)
Python es quien implementa realmente estas funciones.
Ollama solamente puede solicitarlas.
6. Tool calling
Una de las partes más interesantes del proyecto es el uso de tool calling.
En lugar de pedir al modelo que escriba comandos como:
ping -c 4 192.168.1.1
le proporcionamos una descripción estructurada de las herramientas disponibles.
Por ejemplo:
TOOLS = [
{
"type": "function",
"function": {
"name": "ping",
"description": "Realiza cuatro paquetes ICMP contra un host permitido.",
"parameters": {
"type": "object",
"properties": {
"host": {
"type": "string",
"description": "Dirección IP que se quiere comprobar."
}
},
"required": ["host"]
}
}
}
]
Ollama puede entonces solicitar conceptualmente:
ping(host="192.168.1.1")
Python recibe esa solicitud y ejecuta la función correspondiente.
7. Código completo del agente
Creamos:
agente_red.py
El siguiente código implementa el agente completo:
import json
import platform
import re
import subprocess
import requests
# ============================================================
# CONFIGURACIÓN
# ============================================================
OLLAMA_URL = "http://localhost:11434/api/chat"
MODEL = "llama3.1:latest"
MAX_STEPS = 10
# ============================================================
# COLORES
# ============================================================
RESET = "\033[0m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
RED = "\033[91m"
CYAN = "\033[96m"
# ============================================================
# HERRAMIENTA: INFORMACIÓN DE RED
# ============================================================
def network_info():
sistema = platform.system()
try:
if sistema == "Darwin":
resultado = subprocess.run(
["route", "-n", "get", "default"],
capture_output=True,
text=True,
timeout=5
)
elif sistema == "Linux":
resultado = subprocess.run(
["ip", "route"],
capture_output=True,
text=True,
timeout=5
)
elif sistema == "Windows":
resultado = subprocess.run(
["ipconfig"],
capture_output=True,
text=True,
timeout=5
)
else:
return {
"ok": False,
"error": f"Sistema no soportado: {sistema}"
}
return {
"ok": resultado.returncode == 0,
"system": sistema,
"output": resultado.stdout,
"error": resultado.stderr
}
except Exception as e:
return {
"ok": False,
"error": str(e)
}
# ============================================================
# HERRAMIENTA: PING
# ============================================================
def ping(host):
# --------------------------------------------------------
# Lista blanca
# --------------------------------------------------------
hosts_permitidos = {
"192.168.1.1",
"8.8.8.8",
"1.1.1.1"
}
if host not in hosts_permitidos:
return {
"ok": False,
"host": host,
"error": (
f"El host {host} no está permitido."
)
}
sistema = platform.system()
if sistema == "Windows":
comando = [
"ping",
"-n",
"4",
host
]
else:
comando = [
"ping",
"-c",
"4",
host
]
print()
print(
f"{CYAN}[PYTHON] Ejecutando:{RESET}"
)
print(
f"{CYAN}{' '.join(comando)}{RESET}"
)
try:
resultado = subprocess.run(
comando,
capture_output=True,
text=True,
timeout=15
)
return {
"ok": resultado.returncode == 0,
"host": host,
"returncode": resultado.returncode,
"stdout": resultado.stdout,
"stderr": resultado.stderr
}
except subprocess.TimeoutExpired:
return {
"ok": False,
"host": host,
"error": "El ping ha superado el tiempo máximo."
}
except Exception as e:
return {
"ok": False,
"host": host,
"error": str(e)
}
# ============================================================
# DEFINICIÓN DE HERRAMIENTAS
# ============================================================
TOOLS = [
{
"type": "function",
"function": {
"name": "network_info",
"description": (
"Obtiene información de la configuración "
"de red del ordenador, incluida la puerta "
"de enlace predeterminada."
),
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "ping",
"description": (
"Realiza cuatro paquetes ICMP contra un "
"host permitido y devuelve el resultado."
),
"parameters": {
"type": "object",
"properties": {
"host": {
"type": "string",
"description": (
"Dirección IP que se quiere "
"comprobar."
)
}
},
"required": [
"host"
]
}
}
}
]
# ============================================================
# EJECUTAR HERRAMIENTA
# ============================================================
def ejecutar_herramienta(nombre, argumentos):
print()
print(
f"{YELLOW}[ACCIÓN DEL AGENTE]{RESET}"
)
if nombre == "network_info":
print(
"→ Obtener información de la red"
)
return network_info()
if nombre == "ping":
host = argumentos.get("host")
print(
f"→ Ejecutar ping contra {host}"
)
if not host:
return {
"ok": False,
"error": "No se ha proporcionado host."
}
return ping(host)
return {
"ok": False,
"error": f"Herramienta desconocida: {nombre}"
}
# ============================================================
# EXTRAER PING DE UNA RESPUESTA TEXTUAL
# ============================================================
def detectar_ping_en_texto(texto):
if not texto:
return None
patron = (
r"(?:ping|hacer ping|ejecutar ping|"
r"comprobar.*?ping).*?"
r"(\d{1,3}(?:\.\d{1,3}){3})"
)
coincidencia = re.search(
patron,
texto,
re.IGNORECASE | re.DOTALL
)
if coincidencia:
return coincidencia.group(1)
ips_permitidas = [
"192.168.1.1",
"8.8.8.8",
"1.1.1.1"
]
texto_lower = texto.lower()
if "ping" in texto_lower:
for ip in ips_permitidas:
if ip in texto:
return ip
return None
# ============================================================
# COMUNICACIÓN CON OLLAMA
# ============================================================
def preguntar_ollama(messages):
payload = {
"model": MODEL,
"messages": messages,
"tools": TOOLS,
"stream": False
}
try:
respuesta = requests.post(
OLLAMA_URL,
json=payload,
timeout=120
)
respuesta.raise_for_status()
return respuesta.json()
except requests.exceptions.HTTPError:
print()
print(
f"{RED}[ERROR HTTP OLLAMA]{RESET}"
)
print(respuesta.text)
return None
except requests.exceptions.ConnectionError:
print()
print(
f"{RED}[ERROR] No se puede conectar "
f"con Ollama.{RESET}"
)
print(
"Comprueba que Ollama está ejecutándose."
)
return None
except Exception as e:
print()
print(
f"{RED}[ERROR OLLAMA] {e}{RESET}"
)
return None
# ============================================================
# AGENTE
# ============================================================
def ejecutar_agente(peticion):
system_prompt = """
Eres un agente autónomo de diagnóstico de red.
Tu objetivo es investigar problemas de conectividad.
Dispones de estas herramientas:
1. network_info
Obtiene información sobre la configuración
de red y la puerta de enlace.
2. ping
Permite comprobar conectividad mediante ICMP.
IMPORTANTE:
Cuando necesites utilizar una herramienta,
UTILIZA LA HERRAMIENTA.
NO escribas simplemente:
"Hay que hacer ping a 192.168.1.1."
Debes realizar una llamada a la herramienta
ping utilizando:
host = "192.168.1.1"
NO pidas al usuario que ejecute la herramienta.
Python ejecutará automáticamente la herramienta
que tú solicites.
Después de recibir el resultado de una herramienta,
analiza el resultado y decide qué hacer a continuación.
Puedes realizar varias acciones.
Ejemplo:
1. Obtener información de red.
2. Descubrir la puerta de enlace.
3. Hacer ping a la puerta de enlace.
4. Si responde, hacer ping a 8.8.8.8.
5. Comparar resultados.
6. Elaborar diagnóstico.
No inventes resultados.
No ejecutes comandos directamente.
Utiliza exclusivamente las herramientas disponibles.
Cuando ya tengas suficiente información,
NO solicites otra herramienta.
En ese momento proporciona un diagnóstico final.
"""
messages = [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": peticion
}
]
herramientas_ejecutadas = []
for paso in range(1, MAX_STEPS + 1):
print()
print("=" * 70)
print(
f"{BLUE}PASO {paso}{RESET}"
)
print("=" * 70)
respuesta = preguntar_ollama(messages)
if respuesta is None:
return
message = respuesta.get(
"message",
{}
)
contenido = message.get(
"content",
""
)
tool_calls = message.get(
"tool_calls",
[]
)
if contenido:
print()
print(
f"{GREEN}[OLLAMA]{RESET}"
)
print(contenido)
messages.append(message)
if tool_calls:
for tool_call in tool_calls:
funcion = tool_call.get(
"function",
{}
)
nombre = funcion.get(
"name"
)
argumentos = funcion.get(
"arguments",
{}
)
if isinstance(
argumentos,
str
):
try:
argumentos = json.loads(
argumentos
)
except json.JSONDecodeError:
argumentos = {}
resultado = ejecutar_herramienta(
nombre,
argumentos
)
herramientas_ejecutadas.append(
nombre
)
print()
print(
f"{CYAN}[RESULTADO]{RESET}"
)
print(
json.dumps(
resultado,
indent=4,
ensure_ascii=False
)
)
messages.append({
"role": "tool",
"content": json.dumps(
resultado,
ensure_ascii=False
)
})
continue
host_detectado = detectar_ping_en_texto(
contenido
)
if host_detectado:
print()
print(
f"{YELLOW}"
"[AGENTE] Ollama ha indicado que "
f"necesita hacer ping a {host_detectado}"
f"{RESET}"
)
print(
f"{YELLOW}"
"[AGENTE] Convirtiendo la intención "
"en una acción..."
f"{RESET}"
)
resultado = ejecutar_herramienta(
"ping",
{
"host": host_detectado
}
)
herramientas_ejecutadas.append(
"ping"
)
print()
print(
f"{CYAN}[RESULTADO]{RESET}"
)
print(
json.dumps(
resultado,
indent=4,
ensure_ascii=False
)
)
messages.append({
"role": "user",
"content": (
"Has indicado que era necesario "
f"realizar un ping a {host_detectado}.\n\n"
"Python ha ejecutado automáticamente "
"esa acción.\n\n"
"Resultado:\n"
+ json.dumps(
resultado,
indent=4,
ensure_ascii=False
)
+ "\n\n"
"Analiza el resultado y decide "
"el siguiente paso."
)
})
continue
print()
print("=" * 70)
print(
f"{GREEN}DIAGNÓSTICO FINAL{RESET}"
)
print("=" * 70)
print()
if contenido:
print(contenido)
else:
print(
"Ollama no ha proporcionado "
"un diagnóstico."
)
print()
print(
f"{CYAN}Herramientas ejecutadas:{RESET}"
)
for herramienta in herramientas_ejecutadas:
print(
f" - {herramienta}"
)
return
print()
print("=" * 70)
print(
f"{RED}LÍMITE DE PASOS ALCANZADO{RESET}"
)
print("=" * 70)
# ============================================================
# COMPROBAR OLLAMA
# ============================================================
def comprobar_ollama():
try:
respuesta = requests.get(
"http://localhost:11434/api/tags",
timeout=5
)
respuesta.raise_for_status()
datos = respuesta.json()
modelos = [
modelo.get("name")
for modelo in datos.get(
"models",
[]
)
]
print(
f"{GREEN}[+] Ollama disponible{RESET}"
)
print(
f"{GREEN}[+] Modelo: {MODEL}{RESET}"
)
if MODEL not in modelos:
print(
f"{RED}[!] El modelo {MODEL} "
f"no está instalado.{RESET}"
)
print()
print(
"Modelos disponibles:"
)
for modelo in modelos:
print(
f" - {modelo}"
)
return False
return True
except Exception as e:
print(
f"{RED}[!] Error conectando "
f"con Ollama: {e}{RESET}"
)
return False
# ============================================================
# MAIN
# ============================================================
def main():
print()
print("=" * 70)
print(" AGENTE AUTÓNOMO DE DIAGNÓSTICO DE RED")
print(" Python + Ollama + Tool Calling")
print("=" * 70)
print()
if not comprobar_ollama():
return
print()
print(
"Ejemplo de petición:"
)
print()
print(
"Mi conexión a Internet funciona mal"
)
print()
peticion = input(
"Usuario > "
).strip()
if not peticion:
print(
"No se ha introducido ninguna petición."
)
return
ejecutar_agente(
peticion
)
# ============================================================
# INICIO
# ============================================================
if __name__ == "__main__":
main()
8. Ejecutar el agente
Ejecutamos:
python3 agente_red.py
El programa mostrará:
======================================================================
AGENTE AUTÓNOMO DE DIAGNÓSTICO DE RED
Python + Ollama + Tool Calling
======================================================================
[+] Ollama disponible
[+] Modelo: llama3.1:latest
Ejemplo de petición:
Mi conexión a Internet funciona mal
Usuario >
Introducimos:
Mi conexión a Internet funciona mal
9. El agente empieza a investigar
Una posible secuencia de ejecución será:
======================================================================
PASO 1
======================================================================
[OLLAMA]
Necesito conocer la configuración de red para
identificar la puerta de enlace.
[ACCIÓN DEL AGENTE]
→ Obtener información de la red
Python ejecutará la herramienta:
network_info
y devolverá la información a Ollama.
El modelo puede descubrir:
gateway: 192.168.1.1
10. Ollama decide realizar un ping
Ahora aparece una de las características importantes del agente.
Ollama puede decidir:
Necesito comprobar si la puerta de enlace
192.168.1.1 responde.
Pero no queremos que se limite a decirlo.
El agente debe ejecutar la acción.
Por eso Python puede recibir una llamada:
ping
host = 192.168.1.1
y ejecutará:
ping -c 4 192.168.1.1
El resultado vuelve a Ollama.
Por ejemplo:
4 packets transmitted
4 packets received
0.0% packet loss
11. Ollama puede continuar investigando
Aquí aparece la diferencia fundamental respecto al primer experimento.
Ollama puede observar:
192.168.1.1 responde correctamente.
y decidir que necesita otra prueba.
Por ejemplo:
La puerta de enlace responde.
Ahora comprobaré conectividad externa.
Puede solicitar:
ping
host = 8.8.8.8
Python ejecutará nuevamente la herramienta.
El resultado volverá al modelo.
12. El ciclo agente
El funcionamiento completo puede representarse así:
┌───────────────────────────┐
│ USUARIO │
│ │
│ "Internet funciona mal" │
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ OLLAMA │
│ │
│ Decide qué información │
│ necesita │
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ PYTHON │
│ │
│ Ejecuta herramienta │
└─────────────┬─────────────┘
│
▼
Resultado real
│
▼
┌───────────────────────────┐
│ OLLAMA │
│ │
│ Analiza el resultado │
│ │
│ ¿Necesita otra prueba? │
└─────────────┬─────────────┘
│
┌─────┴─────┐
│ │
SÍ NO
│ │
▼ ▼
herramienta diagnóstico
Este bucle es la parte fundamental del agente.
13. ¿Qué ocurre si Ollama escribe la acción en lugar de utilizar una herramienta?
Durante las pruebas con modelos locales puede ocurrir que el modelo escriba:
Necesito hacer ping a 192.168.1.1.
en lugar de generar una llamada de herramienta.
Para evitar que el agente se quede detenido, hemos añadido una pequeña función:
def detectar_ping_en_texto(texto):
Esta función identifica si Ollama está indicando que quiere realizar un ping.
Si detecta:
ping 192.168.1.1
Python transforma esa intención en una acción:
ping("192.168.1.1")
y ejecuta el diagnóstico.
Esto proporciona una capa adicional de tolerancia frente a modelos locales que no siempre utilizan tool calling de forma consistente.
14. Control de las herramientas
Un aspecto importante del diseño es que Ollama no recibe acceso directo a la consola.
El modelo puede solicitar:
ping
pero Python controla qué ocurre realmente.
Además, el código utiliza una lista blanca:
hosts_permitidos = {
"192.168.1.1",
"8.8.8.8",
"1.1.1.1"
}
Por tanto, esta primera versión está deliberadamente limitada a unos destinos concretos.
Esto permite experimentar con el concepto de agente manteniendo separadas dos responsabilidades:
Ollama
→ decide
Python
→ ejecuta
15. ¿Por qué esto es diferente de un chatbot?
Un chatbot tradicional podría responder:
Para comprobar la conectividad, puedes ejecutar
ping 192.168.1.1.
Nuestro agente hace algo diferente.
El modelo puede decidir:
Necesito comprobar la puerta de enlace.
y solicitar la herramienta.
Python ejecuta:
ping 192.168.1.1
El resultado se devuelve al modelo.
El modelo vuelve a razonar sobre la información obtenida y puede solicitar otra acción.
Por tanto, existe interacción entre:
modelo → herramienta → observación → modelo → herramienta.