Resumen
Discourse AI expone un punto de administración/API para transmitir la respuesta de un Agente de IA mediante una respuesta HTTP cruda con codificación por bloques (chunked).
- Protocolo: Codificación de transferencia HTTP cruda por bloques (NO Server-Sent Events).
- Implementación: Toma el control del socket de Rack para transmitir objetos JSON separados por saltos de línea.
- Efectos secundarios: Esto no es solo una API de completado; crea publicaciones reales de mensajes privados (PM) de Discourse.
Detalles del punto de acceso
- Ubicación:
plugins/discourse-ai/app/controllers/discourse_ai/admin/ai_agents_controller.rb:175-272 - Ruta:
POST /admin/plugins/discourse-ai/ai-agents/stream-reply.json - Alcance de la clave de API:
ai:stream_completion(registrado enplugins/discourse-ai/lib/ai_bot/entry_point.rb:283-286)
Encabezados de solicitud
POST /admin/plugins/discourse-ai/ai-agents/stream-reply.json
Api-Key: <your_api_key>
Api-Username: <your_username>
Content-Type: application/json
Parámetros del cuerpo de la solicitud
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
agent_id |
Entero | Opcional* | Identificador del agente. |
agent_name |
Cadena | Opcional* | Identificador alternativo del agente. |
query |
Cadena | Sí | El prompt o pregunta del usuario. |
username |
Cadena | Obligatorio si se omite user_unique_id |
Se usa en caso de que el PM del chat necesite asociarse con un usuario existente. |
user_unique_id |
Cadena | Obligatorio si se omite username |
Identifica al usuario final. Crea/reutiliza un usuario en espera (staged user) claveado por el campo personalizado ai-stream-conversation-unique-id. |
preferred_username |
Cadena | Opcional* | Nombre de usuario para el usuario (si no se usa user_unique_id). |
topic_id |
Entero | Opcional | Continuar una conversación de PM existente. |
custom_instructions |
Cadena | Opcional | Se agrega al contexto del prompt del agente. |
Nota: Debe identificar al usuario final mediante
username(usuario existente de Discourse) ouser_unique_id.
Formato de respuesta
El servidor devuelve una respuesta 200 OK con Transfer-Encoding: chunked. El flujo consiste en objetos JSON separados por saltos de línea.
Encabezados HTTP
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Cache-Control: no-cache, no-store, must-revalidate
Connection: close
X-Accel-Buffering: no
X-Content-Type-Options: nosniff
Estructura de la carga del flujo
- Bloque de contexto: Proporciona metadatos (ID del tema, ID del usuario bot, ID del agente).
- Bloques parciales: Contiene los fragmentos de texto transmitidos.
Ejemplo de flujo:
{"topic_id":42,"bot_user_id":7,"agent_id":123}
{"partial":"Hello"}
{"partial":" there"}
El cliente debe concatenar los campos partial para construir la respuesta final.
Flujo de trabajo y efectos secundarios
El punto de acceso realiza las siguientes acciones durante la ejecución:
- Crea una publicación de usuario con la
querycruda. - Transmite la respuesta de la IA mediante la respuesta por bloques.
- Crea la publicación de respuesta final de la IA con la respuesta acumulada.
- Para nuevos PM, puede titular automáticamente el tema.
Herramientas ejecutadas por el cliente personalizadas
Puede proporcionar definiciones de herramientas para permitir que el modelo llame a herramientas externas. El servidor pausa el flujo cuando se llama a una herramienta, permitiendo que el cliente ejecute la herramienta y reanude.
1. Solicitud inicial con herramientas
Incluya custom_tools en el cuerpo de la solicitud:
{
"agent_id": 123,
"query": "What's the weather?",
"user_unique_id": "external-user-42",
"custom_tools": [
{
"name": "client_weather",
"description": "Gets weather from the client runtime",
"parameters": [
{
"name": "city",
"description": "City to fetch weather for",
"type": "string",
"required": true
}
]
}
]
}
2. Evento de llamada a herramienta
Si el modelo llama a una herramienta, el flujo emite un evento tool_calls y se detiene:
{
"event": "tool_calls",
"tool_calls": [
{
"id": "tool_1",
"name": "client_weather",
"parameters": { "city": "Austin" }
}
],
"resume_token": "..."
}
El servidor persiste el estado de la conversación en Redis en este punto.
3. Reanudar con resultados de herramientas
El cliente ejecuta la herramienta y reanuda el flujo:
{
"resume_token": "...",
"tool_results": [
{
"tool_call_id": "tool_1",
"content": { "temperature_c": 23 }
}
]
}
El servidor recarga el estado del prompt guardado, inserta el resultado de la herramienta, continúa la generación y transmite más bloques partial.
Límites de herramientas
- Máximo de herramientas personalizadas: 20
- Máximo de resultados de herramientas: 20
- Tamaño máximo de la definición de herramienta personalizada: 10,000 bytes
- Tamaño máximo del contenido del resultado de la herramienta: 100 KB
- TTL de reanudación: 15 minutos
- Máximo de rondas de reanudación: 10
Referencias de implementación
- Controlador:
plugins/discourse-ai/app/controllers/discourse_ai/admin/ai_agents_controller.rb - Transmisor:
plugins/discourse-ai/lib/ai_bot/response_http_streamer.rb - Sesión de herramientas personalizadas:
plugins/discourse-ai/lib/ai_bot/stream_reply_custom_tools_session.rb
Ejemplos de prueba
Vea plugins/discourse-ai/spec/requests/admin/ai_agents_controller_spec.rb para ejemplos exhaustivos:
- Nueva conversación transmitida: Líneas 1248-1356
- Herramientas personalizadas + token de reanudación: Líneas 1358-1448
- Llamadas a herramientas en paralelo: Líneas 1467-1590
Implementación de ejemplo
Script de Ruby
require 'net/http'
require 'json'
require 'uri'
# Configuración
DISCOURSE_URL = '<your site URL>'
API_KEY = '<your API key>'
USERNAME = '<your username>'
AGENT_ID = -1 # O use agent_name
QUERY = "Hello, how are you today?"
USER_UNIQUE_ID ='<dejar vacío si se desea que el PM se envíe al usuario USERNAME>'
# Helper para crear la URI
uri = URI("#{DISCOURSE_URL}/admin/plugins/discourse-ai/ai-agents/stream-reply.json")
# Crear la solicitud HTTP
request = Net::HTTP::Post.new(uri)
request['Api-Key'] = API_KEY
request['Api-Username'] = USERNAME
request['Content-Type'] = 'application/json'
# Preparar el cuerpo de la solicitud
body = {
agent_id: AGENT_ID,
query: QUERY,
## descomente la línea siguiente si desea usar un usuario existente para la conversación. Además, `username` tiene precedencia sobre `user_unique_id` si se pasa.
# username: USERNAME,
## use el siguiente campo junto con `user_unique_id` para crear un nuevo usuario en espera. Al usar esto, omita pasar `username`.
# preferred_username: USER_UNIQUE_ID
}
body[:user_unique_id] = USER_UNIQUE_ID unless USER_UNIQUE_ID.empty?
body = body.to_json
request.body = body
http = Net::HTTP.new(uri.hostname, uri.port)
http.use_ssl = (uri.scheme == 'https')
http.request(request) do |response|
# Verificar si la respuesta es exitosa
if response.code == '200'
puts "Stream started successfully."
puts "Response headers: #{response.to_hash}"
puts "Streaming content:"
# Leer la respuesta por bloques
response.read_body do |chunk|
# La respuesta son objetos JSON separados por saltos de línea
chunk.each_line do |line|
line = line.strip
next if line.empty?
begin
json = JSON.parse(line)
if json['topic_id']
puts "\n--- Context Received ---"
puts "Topic ID: #{json['topic_id']}"
puts "Bot User ID: #{json['bot_user_id']}"
puts "Agent ID: #{json['agent_id']}"
elsif json['partial']
# Transmitir el contenido parcial
print json['partial']
elsif json['event'] == 'tool_calls'
puts "\n--- Tool Call Received ---"
puts JSON.pretty_generate(json)
# Aquí se manejaría la ejecución de la herramienta y la reanudación
# Para este ejemplo, solo lo imprimimos
end
# puts "<new word>"
rescue JSON::ParserError => e
puts "\nError parsing JSON: #{e.message}"
puts "Raw line: #{line}"
end
end
end
puts "\n--- Stream Finished ---"
else
puts "Error: #{response.code} #{response.message}"
puts "Response body: #{response.body}"
end
end
Notas
- Si desea que la conversación esté a nombre de un nuevo usuario en espera, pase
unique_user_idypreferred_usernamecomo el nuevo nombre de usuario deseado y omita el campousername. - Si desea conversar usando un usuario existente, pase el
usernamede ese usuario y omita los camposunique_user_idypreferred_username.