Personalizar un controlador generado customize-generated-handler
La plataforma crea un controlador de trabajo para cada acción generada. El controlador devuelve inicialmente datos de ejemplo para que pueda probar la experiencia completa.
Utilice esta guía para comprender el contrato del controlador y reemplazar los datos de ejemplo con las API o fuentes de datos.
Recorrido: Busque el controlador generado → comprender sus entradas y el resultado → conectar el sistema → mantener el contrato del widget alineado → probar e implementar.
Buscar el controlador generado
Abra el repositorio de controladores seleccionado durante la incorporación:
actions/
└── <action-name>/
└── index.js
Las pruebas coincidentes se almacenan por separado:
test/
└── actions/
└── <action-name>.test.js
Edite el index.js generado. No cambie archivos de tiempo de ejecución como entry.js.
Contrato de controlador
Cada controlador exporta una función asincrónica:
module.exports = async (args) => {
return {
content: [
{ type: 'text', text: 'Response for the LLM platform.' }
],
structuredContent: {
// Data for the widget.
}
};
};
La función recibe un objeto args y devuelve un objeto result.
Entrada: args
args contiene los parámetros definidos para la acción en LLM Apps.
Para una acción con category y query parámetros:
module.exports = async ({ category = '', query = '' } = {}) => {
// Use the validated action arguments.
};
El motor en tiempo de ejecución valida el esquema de entrada cuando los metadatos de la acción incluyen inputSchema, como lo hace después de la implementación. La detección de controladores locales sin actions.json no aplica la validación de esquemas. El controlador siempre debe aplicar reglas empresariales como valores admitidos, longitudes máximas y combinaciones permitidas.
Salida: content
Devolver siempre content. Es una matriz de partes de contenido leídas por la plataforma LLM y por hosts que no muestran widgets.
content: [
{
type: 'text',
text: 'Found 3 products matching your search.'
}
]
Tenga esta respuesta concisa. No incluya credenciales, errores internos ni datos que el usuario no tenga autorización para ver.
Salida: structuredContent
Devolver structuredContent cuando la acción tenga un widget. Debe ser un objeto sin formato, no una matriz vacía.
structuredContent: {
products: [
{
id: 'P-100',
name: 'Frescopa House Blend',
price: '$14.99'
}
],
total: 1
}
structuredContent se envía al widget, no al LLM. Devuelve solo los campos requeridos por la interfaz.
Para una acción de sólo texto, structuredContent se puede omitir.
El contrato controlador-widget
El controlador y el widget comparten un contrato: la forma de structuredContent.
Action arguments
↓
Handler
├── content → LLM text response
└── structuredContent → Widget
↓
bridge.toolResult
El widget lee el resultado del controlador del puente SDK de aplicaciones LLM:
export default async function decorate(block, bridge) {
const result = await bridge.toolResult;
const products = result?.structuredContent?.products ?? [];
// Render products.
}
Si el controlador devuelve:
structuredContent: {
products: [...],
total: 3
}
el widget debe leer structuredContent.products y structuredContent.total.
Al cambiar el nombre o el tipo de un campo, el widget se puede romper. Actualice el controlador, el widget y las pruebas juntos.
Reemplazar datos de ejemplo
Los controladores generados generalmente contienen una matriz de muestra en memoria. Reemplace esa búsqueda de datos con una llamada del lado del servidor al sistema.
const API_ORIGIN = process.env.PRODUCT_API_ORIGIN;
const API_TOKEN = process.env.PRODUCT_API_TOKEN;
module.exports = async ({ query = '' } = {}) => {
const normalizedQuery = String(query).trim();
if (!normalizedQuery || normalizedQuery.length > 200) {
return {
content: [{ type: 'text', text: 'Enter a valid product search.' }],
structuredContent: { products: [], total: 0 }
};
}
if (!API_ORIGIN || !API_TOKEN) {
throw new Error('Product API configuration is unavailable.');
}
const origin = new URL(API_ORIGIN);
if (origin.protocol !== 'https:') {
throw new Error('Product API configuration must use HTTPS.');
}
const url = new URL('/v1/products', origin);
url.searchParams.set('query', normalizedQuery);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${API_TOKEN}` },
signal: AbortSignal.timeout(8000)
});
if (!response.ok) {
throw new Error('Product service request failed.');
}
const payload = await response.json();
if (!payload || !Array.isArray(payload.products)
|| !payload.products.every((product) =>
product
&& typeof product.id === 'string'
&& typeof product.name === 'string'
&& typeof product.price === 'string')) {
throw new Error('Product service returned an unexpected response.');
}
const products = payload.products.map(({ id, name, price }) => ({
id,
name,
price
}));
return {
content: [
{ type: 'text', text: `Found ${products.length} matching products.` }
],
structuredContent: {
products,
total: products.length
}
};
};
Mantenga el acceso a la red protegida en el controlador. Nunca coloque credenciales de API en un widget de JavaScript o control de código fuente.
Gestión de estados esperados
Conservar una forma de salida predecible para cada resultado.
Resultados encontrados
{
content: [{ type: 'text', text: 'Found 3 products.' }],
structuredContent: { products: [...], total: 3 }
}
Sin resultados
{
content: [{ type: 'text', text: 'No matching products were found.' }],
structuredContent: { products: [], total: 0 }
}
El widget ahora puede procesar un estado vacío sin saber si products existe.
En el caso de errores de servicio, devuelva o lance un error seguro sin exponer los seguimientos de pila, tokens, hosts internos o cuerpos de respuesta ascendentes.
Prueba del contrato
Actualice las pruebas generadas cada vez que cambie el controlador. Cubierta:
- Argumentos válidos y no válidos.
- Estados de resultados y sin resultados.
- Errores y tiempos de espera de API.
- Respuestas de API mal formadas.
contentsiempre está presente.structuredContentes un objeto sin formato.- La forma esperada por el widget.
Ejecutar:
npm test
Para pruebas de MCP local, vea Desarrollo y prueba de controladores locales.
Implementación del cambio
- Confirme e inserte los cambios del controlador.
- Si la forma de los datos ha cambiado, actualice y presione el widget.
- Implementar la aplicación en Fase.
- Probar el complemento ChatGPT.
- Una vez que Fase se haya realizado correctamente, implemente en Producción.
A continuación, consulte Personalizar un widget generado.