Руководство по A2A JavaScript SDK для начинающих
Введение
Протокол Agent-to-Agent (A2A) от Google — это открытый стандарт, разработанный для обеспечения возможности общения и сотрудничества различных ИИ-агентов между собой. A2A JavaScript SDK предоставляет разработчикам инструменты для легкого создания серверов и клиентов агентов, соответствующих протоколу A2A.
В этом руководстве мы создадим простого агента "Погодный помощник", чтобы изучить основы A2A JavaScript SDK. Этот агент сможет отвечать на вопросы о погодных условиях и демонстрировать основные концепции протокола A2A.
Предварительные требования
Прежде чем начать, убедитесь, что у вас установлены следующие инструменты:
- Node.js (v18 или выше)
- npm (менеджер пакетов Node.js)
- Базовые знания TypeScript
Установка A2A SDK
Сначала создайте новый каталог проекта и инициализируйте npm:
mkdir weather-agent
cd weather-agent
npm init -y
Затем установите A2A SDK и другие необходимые зависимости:
npm install @a2a-js/sdk express uuid
npm install --save-dev typescript ts-node @types/express @types/node @types/uuid
Создайте базовый файл конфигурации TypeScript tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"outDir": "./dist",
"strict": true
},
"include": ["src/**/*"]
}
Структура проекта
Наш агент "Погодный помощник" будет иметь следующую структуру файлов:
weather-agent/
├── src/
│ ├── index.ts # Основная точка входа
│ ├── agent-card.ts # Определение карточки агента
│ ├── agent-executor.ts # Исполнитель агента
│ └── weather-api.ts # Сервис API погоды
├── package.json
└── tsconfig.json
Давайте создадим эти файлы шаг за шагом.
Шаг 1: Определение карточки агента
Карточка агента — это как визитная карточка для вашего агента. Она описывает возможности, навыки и метаданные агента. Создайте файл с именем src/agent-card.ts:
import type { AgentCard } from "@a2a-js/sdk";
export const weatherAgentCard: AgentCard = {
name: "Weather Assistant",
description: "An agent that can answer questions about weather conditions.",
url: "http://localhost:3000/",
provider: {
organization: "A2A Weather Services",
url: "https://example.com/weather-services",
},
version: "0.1.0",
capabilities: {
streaming: true,
pushNotifications: false,
stateTransitionHistory: true,
},
securitySchemes: undefined,
security: undefined,
defaultInputModes: ["text/plain"],
defaultOutputModes: ["text/plain"],
skills: [
{
id: "weather_info",
name: "Weather Information",
description: "Get current weather conditions and forecasts for locations.",
tags: ["weather", "forecast", "temperature"],
examples: [
"What's the weather like in New York?",
"Will it rain tomorrow in London?",
"How hot is it in Tokyo right now?",
"What's the forecast for Beijing this week?",
"Is it snowing in Moscow?",
],
inputModes: ["text/plain"],
outputModes: ["text/plain"],
},
],
supportsAuthenticatedExtendedCard: false,
};
Шаг 2: Создание сервиса API погоды
Далее, давайте создадим имитационный сервис API погоды. В реальном приложении вы, вероятно, будете использовать настоящий API погоды, но для простоты мы будем использовать имитационный сервис. Создайте файл с именем src/weather-api.ts:
// Имитационные данные о погоде
const weatherData: Record<string, WeatherInfo> = {
"new york": {
temperature: 22,
condition: "Sunny",
humidity: 65,
windSpeed: 10,
},
"london": {
temperature: 18,
condition: "Cloudy",
humidity: 80,
windSpeed: 15,
},
"tokyo": {
temperature: 28,
condition: "Clear",
humidity: 70,
windSpeed: 8,
},
"beijing": {
temperature: 25,
condition: "Partly Cloudy",
humidity: 60,
windSpeed: 12,
},
"moscow": {
temperature: 5,
condition: "Snowy",
humidity: 85,
windSpeed: 20,
},
};
export interface WeatherInfo {
temperature: number; // Цельсий
condition: string;
humidity: number; // Процент
windSpeed: number; // км/ч
}
export async function getWeather(location: string): Promise<WeatherInfo | null> {
// Имитация задержки API
await new Promise((resolve) => setTimeout(resolve, 500));
const normalizedLocation = location.toLowerCase();
return weatherData[normalizedLocation] || null;
}
export async function getForecast(location: string): Promise<string> {
// Имитация задержки API
await new Promise((resolve) => setTimeout(resolve, 800));
const normalizedLocation = location.toLowerCase();
const weather = weatherData[normalizedLocation];
if (!weather) {
return "Прогноз для этого местоположения недоступен.";
}
// Генерация простого случайного прогноза
const conditions = ["Sunny", "Cloudy", "Rainy", "Clear", "Stormy", "Windy"];
const randomCondition = conditions[Math.floor(Math.random() * conditions.length)];
const tempChange = Math.floor(Math.random() * 5) - 2; // Изменение от -2 до +2 градусов
return `Прогноз показывает условия ${randomCondition} с температурой около ${
weather.temperature + tempChange
}°C.`;
}
Шаг 3: Реализация исполнителя агента
Исполнитель агента отвечает за обработку запросов пользователей и генерацию ответов. Это основная логика вашего агента. Создайте файл с именем src/agent-executor.ts:
import { v4 as uuidv4 } from "uuid";
import {
Task,
TaskStatusUpdateEvent,
Message,
} from "@a2a-js/sdk";
import {
AgentExecutor,
RequestContext,
ExecutionEventBus,
} from "@a2a-js/sdk/server";
import { getWeather, getForecast } from "./weather-api";
export class WeatherAgentExecutor implements AgentExecutor {
private cancelledTasks = new Set<string>();
public cancelTask = async (
taskId: string,
eventBus: ExecutionEventBus
): Promise<void> => {
this.cancelledTasks.add(taskId);
// Цикл выполнения отвечает за публикацию конечного состояния
};
async execute(
requestContext: RequestContext,
eventBus: ExecutionEventBus
): Promise<void> {
const userMessage = requestContext.userMessage;
const existingTask = requestContext.task;
// Определение ID для задачи и контекста
const taskId = requestContext.taskId;
const contextId = requestContext.contextId;
console.log(
`[WeatherAgentExecutor] Обработка сообщения ${userMessage.messageId} для задачи ${taskId} (контекст: ${contextId})`
);
// 1. Публикация начального события задачи, если это новая задача
if (!existingTask) {
const initialTask: Task = {
kind: "task",
id: taskId,
contextId: contextId,
status: {
state: "submitted",
timestamp: new Date().toISOString(),
},
history: [userMessage], // Начало истории с текущего сообщения пользователя
metadata: userMessage.metadata, // Перенос метаданных из сообщения, если они есть
};
eventBus.publish(initialTask);
}
// 2. Публикация обновления статуса "работает"
const workingStatusUpdate: TaskStatusUpdateEvent = {
kind: "status-update",
taskId: taskId,
contextId: contextId,
status: {
state: "working",
message: {
kind: "message",
role: "agent",
messageId: uuidv4(),
parts: [{ kind: "text", text: "Проверка данных о погоде..." }],
taskId: taskId,
contextId: contextId,
},
timestamp: new Date().toISOString(),
},
final: false,
};
eventBus.publish(workingStatusUpdate);
try {
// 3. Обработка сообщения пользователя
const userText = userMessage.parts
.filter((part) => part.kind === "text")
.map((part) => (part as { text: string }).text)
.join(" ");
// Простое извлечение местоположения (в реальном приложении используйте NLP)
const locationMatch = userText.match(
/(?:in|at|for)\s+([A-Za-z\s]+)(?:\?|$|\s)/
);
const location = locationMatch ? locationMatch[1].trim() : null;
if (!location) {
// Местоположение не найдено в сообщении
const noLocationUpdate: TaskStatusUpdateEvent = {
kind: "status-update",
taskId: taskId,
contextId: contextId,
status: {
state: "completed",
message: {
kind: "message",
role: "agent",
messageId: uuidv4(),
parts: [
{
kind: "text",
text: "Я не смог определить местоположение. Пожалуйста, укажите название города в вашем вопросе.",
},
],
taskId: taskId,
contextId: contextId,
},
timestamp: new Date().toISOString(),
},
final: true,
};
eventBus.publish(noLocationUpdate);
return;
}
// 4. Получение данных о погоде
const weatherData = await getWeather(location);
if (!weatherData) {
// Местоположение не найдено в нашей базе данных
const notFoundUpdate: TaskStatusUpdateEvent = {
kind: "status-update",
taskId: taskId,
contextId: contextId,
status: {
state: "completed",
message: {
kind: "message",
role: "agent",
messageId: uuidv4(),
parts: [
{
kind: "text",
text: `У меня нет данных о погоде для ${location}. Пожалуйста, попробуйте другое местоположение.`,
},
],
taskId: taskId,
contextId: contextId,
},
timestamp: new Date().toISOString(),
},
final: true,
};
eventBus.publish(notFoundUpdate);
return;
}
// 5. Получение прогноза, если пользователь спрашивает о нем
let forecastText = "";
if (
userText.toLowerCase().includes("forecast") ||
userText.toLowerCase().includes("tomorrow") ||
userText.toLowerCase().includes("week")
) {
forecastText = await getForecast(location);
forecastText = `\n\n${forecastText}`;
}
// 6. Публикация окончательного ответа
const responseText = `Текущая погода в ${location.charAt(0).toUpperCase() + location.slice(1)}:
- Температура: ${weatherData.temperature}°C
- Состояние: ${weatherData.condition}
- Влажность: ${weatherData.humidity}%
- Скорость ветра: ${weatherData.windSpeed} км/ч${forecastText}`;
const finalUpdate: TaskStatusUpdateEvent = {
kind: "status-update",
taskId: taskId,
contextId: contextId,
status: {
state: "completed",
message: {
kind: "message",
role: "agent",
messageId: uuidv4(),
parts: [{ kind: "text", text: responseText }],
taskId: taskId,
contextId: contextId,
},
timestamp: new Date().toISOString(),
},
final: true,
};
eventBus.publish(finalUpdate);
} catch (error: any) {
console.error(
`[WeatherAgentExecutor] Ошибка при обработке задачи ${taskId}:`,
error
);
// 7. Обработка ошибок
const errorUpdate: TaskStatusUpdateEvent = {
kind: "status-update",
taskId: taskId,
contextId: contextId,
status: {
state: "failed",
message: {
kind: "message",
role: "agent",
messageId: uuidv4(),
parts: [{ kind: "text", text: `Ошибка: ${error.message}` }],
taskId: taskId,
contextId: contextId,
},
timestamp: new Date().toISOString(),
},
final: true,
};
eventBus.publish(errorUpdate);
}
}
}
Шаг 4:
Создание основной точки входа
Наконец, давайте создадим основную точку входа для нашего агента. Этот файл настроит сервер Express и сконфигурирует маршруты A2A. Создайте файл с именем src/index.ts:
import express from "express";
import {
InMemoryTaskStore,
TaskStore,
A2AExpressApp,
DefaultRequestHandler,
} from "@a2a-js/sdk/server";
import { weatherAgentCard } from "./agent-card";
import { WeatherAgentExecutor } from "./agent-executor";
async function main() {
// 1. Создание TaskStore
const taskStore: TaskStore = new InMemoryTaskStore();
// 2. Создание AgentExecutor
const agentExecutor = new WeatherAgentExecutor();
// 3. Создание DefaultRequestHandler
const requestHandler = new DefaultRequestHandler(
weatherAgentCard,
taskStore,
agentExecutor
);
// 4. Создание и настройка A2AExpressApp
const appBuilder = new A2AExpressApp(requestHandler);
const expressApp = appBuilder.setupRoutes(express());
// 5. Запуск сервера
const PORT = process.env.PORT || 3000;
expressApp.listen(PORT, () => {
console.log(`[WeatherAgent] Сервер запущен на http://localhost:${PORT}`);
console.log(`[WeatherAgent] Карточка агента: http://localhost:${PORT}/.well-known/agent.json`);
console.log("[WeatherAgent] Нажмите Ctrl+C для остановки сервера");
});
}
main().catch(console.error);
Шаг 5: Запуск агента
Теперь, когда мы создали все необходимые файлы, давайте настроим структуру проекта и запустим нашего агента:
- Создайте структуру каталогов:
mkdir -p src
-
Создайте файлы, которые мы определили: -
src/agent-card.ts-src/weather-api.ts-src/agent-executor.ts-src/index.ts -
Добавьте скрипт запуска в ваш
package.json:
{
"scripts": {
"start": "ts-node src/index.ts"
}
}
- Запустите агента:
npm start
Теперь ваш агент "Погодный помощник" должен работать на http://localhost:3000.
Шаг 6: Тестирование агента с помощью CLI-клиента A2A
A2A SDK включает CLI-клиент, который мы можем использовать для тестирования нашего агента. Давайте создадим простой тестовый скрипт. Создайте файл с именем test-client.ts в корне проекта:
import { A2AClient } from "@a2a-js/sdk/client";
import { v4 as uuidv4 } from "uuid";
async function testAgent() {
const client = new A2AClient("http://localhost:3000");
try {
// Получение карточки агента
console.log("Получение карточки агента...");
const agentCard = await client.getAgentCard();
console.log(`Подключено к: ${agentCard.name} (${agentCard.version})`);
// Отправка сообщения агенту
const messageId = uuidv4();
const response = await client.sendMessage({
message: {
messageId: messageId,
kind: "message",
role: "user",
parts: [{ kind: "text", text: "Какая погода в Токио?" }],
},
});
if (response.error) {
console.error("Ошибка:", response.error);
return;
}
console.log("Ответ:", response.result);
// Если результат — задача, мы можем получать обновления в потоке
if (response.result.kind === "task") {
const taskId = response.result.id;
console.log(`Создана задача с ID: ${taskId}`);
// Получение обновлений задачи в потоке
console.log("Получение обновлений задачи в потоке...");
const stream = client.resubscribeTask({ id: taskId });
for await (const event of stream) {
console.log("Событие:", event);
// Если это финальное обновление, выходим из цикла
if (event.kind === "status-update" && event.final) {
console.log("Задача завершена.");
break;
}
}
}
} catch (error) {
console.error("Ошибка:", error);
}
}
testAgent();
Запустите тестовый клиент:
npx ts-node test-client.ts
Это отправит сообщение вашему агенту с вопросом о погоде в Токио и отобразит ответ.
Понимание потока протокола A2A
Давайте разберем, что происходит, когда клиент взаимодействует с нашим агентом "Погодный помощник":
-
Клиент отправляет сообщение: Клиент отправляет сообщение агенту, используя метод
sendMessage. -
Агент создает задачу: Агент создает задачу для обработки сообщения и возвращает ID задачи клиенту.
-
Агент обрабатывает задачу: Агент асинхронно обрабатывает задачу, обновляя статус задачи по мере продвижения.
-
Клиент получает обновления: Клиент может получать обновления о статусе задачи, получая события в потоке с помощью метода
resubscribeTask. -
Завершение задачи: Когда задача завершена, агент отправляет финальное обновление статуса с результатом.
Этот поток позволяет осуществлять асинхронную коммуникацию между клиентом и агентом, что особенно полезно для задач, которые требуют времени для выполнения.
Заключение
В этом руководстве мы создали простого агента "Погодный помощник" с использованием A2A JavaScript SDK. Мы узнали, как:
- Определить карточку агента для описания возможностей нашего агента
- Реализовать исполнителя агента для обработки запросов пользователей
- Настроить сервер A2A с использованием Express
- Тестировать нашего агента с использованием клиента A2A
Протокол A2A предоставляет стандартизированный способ коммуникации для агентов, что облегчает создание взаимодействующих ИИ-систем. С помощью A2A JavaScript SDK вы можете быстро создавать агентов, которые следуют этому протоколу, и интегрировать их в свои приложения.
Для более продвинутых функций, таких как аутентификация, push-уведомления и более сложная обработка задач, обратитесь к документации A2A JavaScript SDK.
Удачного кодирования!