Agentes
Agentes
Sección titulada «Agentes»El modulo wippy/agent proporciona un framework para construir agentes de IA con uso de herramientas, streaming, delegacion, traits y memoria. Los agentes se definen declarativamente y se ejecutan a traves de un patron de contexto/runner.
Configuracion
Sección titulada «Configuracion»Agrega el modulo a tu proyecto:
wippy add wippy/agentwippy installEl modulo de agentes requiere wippy/llm y un host de procesos. Declara ambas dependencias:
version: "1.0"namespace: app
entries: - name: os_env kind: env.storage.os
- name: processes kind: process.host lifecycle: auto_start: true
- name: dep.llm kind: ns.dependency component: wippy/llm version: "*" parameters: - name: env_storage value: app:os_env - name: process_host value: app:processes
- name: dep.agent kind: ns.dependency component: wippy/agent version: "*" parameters: - name: process_host value: app:processesDefiniciones de Agentes
Sección titulada «Definiciones de Agentes»Los agentes son entradas de registro con meta.type: agent.gen1:
entries: - name: assistant kind: registry.entry meta: type: agent.gen1 name: assistant title: Assistant comment: A helpful chat assistant prompt: | You are a helpful assistant. Be concise and direct. Answer questions clearly. model: gpt-4o max_tokens: 1024 temperature: 0.7Campos del Agente
Sección titulada «Campos del Agente»| Campo | Tipo | Descripcion |
|---|---|---|
meta.type | string | Debe ser agent.gen1 |
meta.name | string | Identificador del agente |
prompt | string | Prompt del sistema |
model | string | Nombre o clase del modelo |
max_tokens | number | Maximo de tokens de salida (por defecto 512) |
temperature | number | Temperatura de muestreo (por defecto 0; rango dependiente del proveedor) |
thinking_effort | number | Solo se reenvia al modelo cuando > 0 (escala definida por el proveedor) |
tools | array | IDs de registro de herramientas |
traits | array | Referencias a traits |
delegates | array | Referencias a agentes delegados |
memory | array | Elementos de memoria estatica (strings) |
memory_contract | table | Configuracion de memoria dinamica |
Contexto del Agente
Sección titulada «Contexto del Agente»El contexto del agente es el punto de entrada principal. Crea un contexto, configuralo opcionalmente y luego carga un agente:
imports: agent_context: wippy.agent:contextlocal agent_context = require("agent_context")
local ctx = agent_context.new()local runner, err = ctx:load_agent("app:assistant")if err then error("Failed to load agent: " .. tostring(err))endMetodos del Contexto
Sección titulada «Metodos del Contexto»| Metodo | Descripcion |
|---|---|
agent_context.new(options?) | Crear nuevo contexto |
:add_tools(specs) | Agregar herramientas en tiempo de ejecucion |
:add_delegates(specs) | Agregar agentes delegados |
:configure_delegate_tools(config) | Configurar como los delegados se exponen como herramientas |
:set_memory_contract(config) | Configurar memoria dinamica |
:set_context_merger(fn) | Proporcionar una funcion para fusionar actualizaciones del contexto en tiempo de ejecucion |
:update_context(updates) | Actualizar contexto en tiempo de ejecucion |
:load_agent(spec_or_id, options?) | Cargar y compilar agente, retorna runner |
:switch_to_agent(id, options?) | Cambiar a otro agente, retorna (boolean, string?) |
:switch_to_model(name) | Cambiar modelo del agente actual, retorna (boolean, string?) |
:get_current_agent() | Obtener runner actual |
:get_config() | Retornar un resumen de la configuracion del contexto |
Opciones del Contexto
Sección titulada «Opciones del Contexto»local ctx = agent_context.new({ context = { session_id = "abc", user_id = "u1" }, delegate_tools = { enabled = true }, enable_cache = true,})| Opcion | Descripcion |
|---|---|
context | Contexto base en tiempo de ejecucion reenviado a herramientas y delegados |
delegate_tools | Configuracion predeterminada de delegate-tool (sobrescrita por configure_delegate_tools) |
enable_cache | Habilitar marcadores de cache de prompt (modelos Claude). Por defecto es true. |
Carga por Especificacion Inline
Sección titulada «Carga por Especificacion Inline»Carga un agente sin una entrada de registro:
local runner, err = ctx:load_agent({ id = "inline-agent", name = "helper", prompt = "You are a helpful assistant.", model = "gpt-4o", max_tokens = 1024, tools = { "app.tools:search" },})Ejecucion de Pasos
Sección titulada «Ejecucion de Pasos»El runner ejecuta un solo paso de razonamiento. Pasa un constructor de prompts con la conversacion:
local prompt = require("prompt")
local conversation = prompt.new()conversation:add_user("What is the capital of France?")
local response, err = runner:step(conversation)if err then error(tostring(err))end
print(response.result)Opciones de Step
Sección titulada «Opciones de Step»local response, err = runner:step(conversation, { context = { session_id = "abc" }, stream_target = { reply_to = process.pid(), topic = "stream" }, tool_call = "auto",})| Opcion | Tipo | Descripcion |
|---|---|---|
context | table | Contexto en tiempo de ejecucion combinado con el contexto del agente |
stream_target | table | Streaming: { reply_to, topic } |
tool_call | string | "auto", "any", "none" o un nombre de herramienta |
Respuesta de Step
Sección titulada «Respuesta de Step»| Campo | Tipo | Descripcion |
|---|---|---|
result | string | Texto generado |
tokens | table | Uso de tokens |
finish_reason | string | Razon de detencion |
tool_calls | table? | Llamadas a herramientas para ejecutar |
delegate_calls | table? | Invocaciones de delegados |
Estadisticas del Runner
Sección titulada «Estadisticas del Runner»local stats = runner:get_stats()-- stats.id, stats.name, stats.total_tokensDefiniciones de Herramientas
Sección titulada «Definiciones de Herramientas»Las herramientas son entradas function.lua con meta.type: tool. Definilas en un _index.yaml separado:
version: "1.0"namespace: app.tools
entries: - name: calculate kind: function.lua meta: type: tool title: Calculate input_schema: | { "type": "object", "properties": { "expression": { "type": "string", "description": "Math expression to evaluate" } }, "required": ["expression"], "additionalProperties": false } llm_alias: calculate llm_description: Evaluate a mathematical expression. source: file://calculate.lua modules: [expr] method: handlerlocal expr = require("expr")
local function handler(args) local result, err = expr.eval(args.expression) if err then return { error = tostring(err) } end return { result = result }end
return { handler = handler }Metadatos de Herramientas
Sección titulada «Metadatos de Herramientas»| Campo | Tipo | Descripcion |
|---|---|---|
meta.type | string | Debe ser tool |
meta.input_schema | string/table | JSON Schema para los argumentos de la herramienta |
meta.llm_alias | string | Nombre expuesto al LLM |
meta.llm_description | string | Descripcion expuesta al LLM |
meta.exclusive | boolean | Si es true, cancela llamadas concurrentes a herramientas |
Referencia de Herramientas en Agentes
Sección titulada «Referencia de Herramientas en Agentes»Lista los IDs de registro de herramientas en la definicion del agente:
- name: assistant kind: registry.entry meta: type: agent.gen1 name: assistant prompt: You are a helpful assistant with tools. model: gpt-4o max_tokens: 1024 tools: - app.tools:calculate - app.tools:search - app.tools:* # wildcard: all tools in namespaceLas herramientas tambien pueden referenciarse con alias personalizados y contexto:
tools: - id: app.tools:search alias: web_search context: api_key: "${SEARCH_API_KEY}"Ejecucion de Herramientas
Sección titulada «Ejecucion de Herramientas»Cuando un paso del agente retorna tool_calls, ejecutalas y alimenta los resultados de vuelta:
local json = require("json")local funcs = require("funcs")
local function execute_and_continue(runner, conversation) while true do local response, err = runner:step(conversation) if err then return nil, err end
local tool_calls = response.tool_calls if not tool_calls or #tool_calls == 0 then return response.result, nil end
for _, tc in ipairs(tool_calls) do local result, call_err = funcs.call(tc.registry_id, tc.arguments) local result_str if call_err then result_str = json.encode({ error = tostring(call_err) }) else result_str = json.encode(result) end
conversation:add_function_call(tc.name, tc.arguments, tc.id) conversation:add_function_result(tc.name, result_str, tc.id) end endendCampos de Llamada a Herramienta
Sección titulada «Campos de Llamada a Herramienta»| Campo | Tipo | Descripcion |
|---|---|---|
id | string | Identificador unico de la llamada |
name | string | Nombre de la herramienta (alias o llm_alias) |
arguments | table | Argumentos parseados |
registry_id | string | ID de registro completo para funcs.call() |
funcs.call(tc.registry_id, tc.arguments) para ejecutar herramientas. El campo registry_id mapea directamente a la entrada de la herramienta en el registro.
Streaming
Sección titulada «Streaming»Transmite respuestas del agente en tiempo real usando stream_target:
local TOPIC = "agent_stream"
local function stream_step(runner, conversation) local stream_ch = process.listen(TOPIC)
local done_ch = channel.new(1) coroutine.spawn(function() local response, err = runner:step(conversation, { stream_target = { reply_to = process.pid(), topic = TOPIC, }, }) done_ch:send({ response = response, err = err }) end)
local full_text = "" while true do local result = channel.select({ stream_ch:case_receive(), done_ch:case_receive(), }) if not result.ok then break end
if result.channel == done_ch then process.unlisten(stream_ch) local r = result.value return full_text, r.response, r.err end
local chunk = result.value if chunk.type == "chunk" then io.write(chunk.content or "") full_text = full_text .. (chunk.content or "") elseif chunk.type == "done" then -- wait for the step to complete local r, ok = done_ch:receive() process.unlisten(stream_ch) if ok and r then return full_text, r.response, r.err end return full_text, nil, nil end end
process.unlisten(stream_ch) return full_text, nil, nilendEl stream usa los mismos tipos de chunk que el streaming directo de LLM: "chunk", "thinking", "tool_call", "error", "done".
coroutine.spawn para ejecutar runner:step() en una corrutina separada para poder recibir chunks del stream de forma concurrente. Usa channel.select para multiplexar los canales de stream y completado.
Delegados
Sección titulada «Delegados»Los agentes pueden delegar a otros agentes. Los delegados aparecen como herramientas para el agente padre:
- name: coordinator kind: registry.entry meta: type: agent.gen1 name: coordinator prompt: Route questions to the right specialist. model: gpt-4o max_tokens: 1024 delegates: - id: app:code_agent name: ask_coder rule: for programming questions - id: app:math_agent name: ask_mathematician rule: for math problemsLas llamadas a delegados aparecen en response.delegate_calls:
local response = runner:step(conversation)
if response.delegate_calls then for _, dc in ipairs(response.delegate_calls) do -- dc.agent_id - target agent registry ID -- dc.name - delegate tool name -- dc.arguments - forwarded message endendLos delegados tambien pueden agregarse en tiempo de ejecucion:
ctx:add_delegates({ { id = "app:specialist", name = "ask_specialist", rule = "for domain questions" },})Los traits son capacidades reutilizables que aportan prompts, herramientas y comportamiento a los agentes:
- name: assistant kind: registry.entry meta: type: agent.gen1 name: assistant prompt: You are a helpful assistant. model: gpt-4o traits: - time_aware - id: custom_trait context: key: valueTraits Incorporados
Sección titulada «Traits Incorporados»| Trait | Descripcion |
|---|---|
time_aware | Inyecta la fecha y hora actual en el prompt |
El trait time_aware acepta opciones de contexto:
traits: - id: time_aware context: timezone: America/New_York time_interval: 15Traits Personalizados
Sección titulada «Traits Personalizados»Los traits son entradas de registro con meta.type: agent.trait. Pueden aportar:
- prompt - texto estatico agregado al prompt del sistema
- build_func_id - funcion llamada en tiempo de compilacion para aportar herramientas, prompts y delegados
- prompt_func_id - funcion llamada en cada paso para inyectar contenido dinamico
- step_func_id - funcion llamada en cada paso para efectos secundarios
Memoria
Sección titulada «Memoria»Memoria Estatica
Sección titulada «Memoria Estatica»Elementos de memoria simples agregados al prompt del sistema:
- name: assistant kind: registry.entry meta: type: agent.gen1 name: assistant prompt: You are a helpful assistant. model: gpt-4o memory: - "User prefers concise answers" - "Always cite sources when possible"Contrato de Memoria Dinamica
Sección titulada «Contrato de Memoria Dinamica»Configura la recuperacion de memoria dinamica desde una fuente externa:
memory_contract: implementation_id: app:memory_store context: user_id: "${user_id}" options: max_items: 3 max_length: 1000 recall_cooldown: 1 min_conversation_length: 2El contrato de memoria se invoca durante runner:step() para recuperar elementos relevantes basados en el contexto de la conversacion. Los resultados se inyectan como mensajes de desarrollador.
| Opcion | Por defecto | Descripcion |
|---|---|---|
max_items | 3 | Maximo de elementos de memoria por recuperacion |
max_length | 1000 | Longitud total maxima de caracteres |
recall_cooldown | 1 | Minimo de pasos entre recuperaciones |
min_conversation_length | 2 | Minimo de turnos de conversacion antes de la primera recuperacion |
Contrato de Resolucion
Sección titulada «Contrato de Resolucion»Cuando load_agent() recibe un identificador de tipo string, primero intenta resolverlo a traves del contrato wippy.agent:resolver. Si no hay un resolver vinculado o el resolver retorna nil, recurre a la busqueda en el registro.
Esto permite a las aplicaciones implementar resolucion personalizada de agentes, como cargar definiciones de agentes desde una base de datos.
Vincular un Resolver
Sección titulada «Vincular un Resolver»Define una funcion de resolucion y vinculala al contrato:
entries: - name: agent_resolver.resolve kind: function.lua source: file://agent_resolver.lua method: resolve modules: - logger imports: agent_registry: wippy.agent.discovery:registry
- name: agent_resolver_binding kind: contract.binding contracts: - contract: wippy.agent:resolver default: true methods: resolve: app:agent_resolver.resolveImplementacion del Resolver
Sección titulada «Implementacion del Resolver»El resolver recibe { agent_id = "..." } y retorna una tabla de especificacion del agente o nil:
local agent_registry = require("agent_registry")
local CUSTOM_PREFIX = "custom:"
function resolve(args) local agent_id = args.agent_id if not agent_id then return nil, "agent_id is required" end
if agent_id:sub(1, #CUSTOM_PREFIX) == CUSTOM_PREFIX then local id = agent_id:sub(#CUSTOM_PREFIX + 1)
-- load from database, config file, or any other source return { id = agent_id, name = "custom-agent", prompt = "You are a custom agent.", model = "class:balanced", max_tokens = 1024, tools = {}, } end
-- fall back to registry local spec, err = agent_registry.get_by_id(agent_id) if not spec then spec, err = agent_registry.get_by_name(agent_id) end return spec, errend
return { resolve = resolve,}Orden de Resolucion
Sección titulada «Orden de Resolucion»- Intenta el contrato
wippy.agent:resolver(si esta vinculado) - Intenta busqueda en el registro por ID
- Intenta busqueda en el registro por nombre
- Retorna error si no se encuentra
Este patron habilita aplicaciones multi-tenant donde los agentes se configuran por usuario o por workspace y se almacenan fuera del registro del framework.
Ver Tambien
Sección titulada «Ver Tambien»- LLM - Modulo LLM subyacente
- Construir un Agente LLM - Tutorial paso a paso
- Vision General del Framework - Uso de modulos del framework