Skip to content

开发外部 AI Agent ​

本文档将指导你如何开发一个外部 AI Agent,并通过 WebSocket 协议接入 Mythra Agent Studio。

概述 ​

外部 AI Agent 允许你使用自己的服务来实现 AI Agent 的逻辑,通过 WebSocket 与 Mythra Agent Studio 进行通信。这种方式适合需要更复杂自定义逻辑、使用特定 AI 模型或框架的场景。

技术选型 ​

你可以使用任何支持 WebSocket 的编程语言和框架来开发外部 Agent。以下是一些流行的选择:

语言推荐框架特点
Node.jsws、Socket.io、uWebSockets.js生态丰富、开发效率高、适合快速原型
Pythonwebsockets、FastAPI + WebSocketAI 生态完善、适合机器学习场景
Gogorilla/websocket、melody高性能、适合高并发场景
JavaSpring WebSocket、Netty企业级应用、稳定性强

本文以 Node.js + ws 库为例,演示如何开发一个简单的外部 Agent。

开发步骤 ​

1. 初始化项目 ​

首先创建一个新的 Node.js 项目:

bash
mkdir my-external-agent
cd my-external-agent
npm init -y
npm install ws openai

2. 创建 WebSocket 服务 ​

创建 server.js 文件,实现一个基本的 WebSocket 服务:

javascript
const WebSocket = require('ws');

const PORT = 8080;
const wss = new WebSocket.Server({ port: PORT });

console.log(`External Agent WebSocket server is running on ws://localhost:${PORT}`);

wss.on('connection', (ws) => {
  console.log('Client connected');

  ws.on('message', (data) => {
    handleMessage(ws, data);
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

3. 处理用户输入 ​

当客户端连接后,会发送 user-input 消息。我们需要解析这个消息并处理:

javascript
function handleMessage(ws, data) {
  try {
    const message = JSON.parse(data.toString());

    if (message.type === 'user-input') {
      handleUserInput(ws, message);
    }
  } catch (error) {
    console.error('Failed to parse message:', error);
  }
}

function handleUserInput(ws, message) {
  const { userInput, sessionId, workDir } = message;
  console.log(`Received user input: ${userInput}`);
  console.log(`Session ID: ${sessionId}, Work directory: ${workDir}`);

  // 开始处理用户请求
  processUserRequest(ws, userInput);
}

4. 发送响应消息 ​

根据 Agent 消息协议,我们需要按照特定的格式发送响应消息。

4.1 发送响应开始消息 ​

javascript
function sendMessage(ws, type, event, content = '', extra = {}) {
  const message = {
    id: generateId(),
    type,
    event,
    ...extra,
  };

  if (content) {
    message.content = content;
  }

  ws.send(JSON.stringify(message));
}

function generateId() {
  return Math.random().toString(36).substring(2, 15);
}

function processUserRequest(ws, userInput) {
  // 发送响应开始消息
  sendMessage(ws, 'response-state', 'start');

  // 发送迭代开始消息(可选)
  sendMessage(ws, 'iteration-state', 'start', 'iteration 0');

  // 发送模型调用开始消息(可选)
  sendMessage(ws, 'model-invocation-state', 'start');

  // 发送聊天内容
  sendChatOutput(ws, userInput);

  // 发送模型调用结束消息(可选)
  sendMessage(ws, 'model-invocation-state', 'end');

  // 发送迭代结束消息(可选)
  sendMessage(ws, 'iteration-state', 'end', 'iteration 0');

  // 发送响应结束消息
  sendMessage(ws, 'response-state', 'end');
}

4.2 发送聊天内容 ​

聊天内容需要分多次发送,使用 start、delta、end 事件。这是因为大语言模型(LLM)API 通常以流式输出(streaming)的方式返回内容,每生成一个 token 就发送给客户端,让用户能够实时看到输出。

首先安装 OpenAI SDK:

bash
npm install openai

然后实现流式聊天输出:

javascript
const OpenAI = require('openai');

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: process.env.OPENAI_API_BASE_URL || 'https://api.openai.com/v1',
});

async function sendChatOutput(ws, userInput) {
  const chatId = generateId();

  // 发送聊天内容开始
  ws.send(JSON.stringify({
    id: chatId,
    type: 'chat-output',
    event: 'start',
  }));

  // 调用 OpenAI API(流式输出)
  const stream = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: userInput }],
    stream: true,
  });

  // 逐个 token 发送
  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || '';
    if (content) {
      ws.send(JSON.stringify({
        id: chatId,
        type: 'chat-output',
        event: 'delta',
        content,
      }));
    }
  }

  // 发送聊天内容结束
  ws.send(JSON.stringify({
    id: chatId,
    type: 'chat-output',
    event: 'end',
  }));
}

5. 完整示例代码 ​

以下是完整的 server.js 文件:

javascript
const WebSocket = require('ws');
const OpenAI = require('openai');

const PORT = 8080;
const wss = new WebSocket.Server({ port: PORT });

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: process.env.OPENAI_API_BASE_URL || 'https://api.openai.com/v1',
});

console.log(`External Agent WebSocket server is running on ws://localhost:${PORT}`);

wss.on('connection', (ws) => {
  console.log('Client connected');

  ws.on('message', (data) => {
    handleMessage(ws, data);
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

function handleMessage(ws, data) {
  try {
    const message = JSON.parse(data.toString());

    if (message.type === 'user-input') {
      handleUserInput(ws, message);
    }
  } catch (error) {
    console.error('Failed to parse message:', error);
  }
}

function handleUserInput(ws, message) {
  const { userInput, sessionId, workDir } = message;
  console.log(`Received user input: ${userInput}`);
  console.log(`Session ID: ${sessionId}, Work directory: ${workDir}`);

  processUserRequest(ws, userInput);
}

function generateId() {
  return Math.random().toString(36).substring(2, 15);
}

function sendMessage(ws, type, event, content = '', extra = {}) {
  const message = {
    id: generateId(),
    type,
    event,
    ...extra,
  };

  if (content) {
    message.content = content;
  }

  ws.send(JSON.stringify(message));
}

async function processUserRequest(ws, userInput) {
  // 发送响应开始消息
  sendMessage(ws, 'response-state', 'start');

  // 发送迭代开始消息(可选)
  sendMessage(ws, 'iteration-state', 'start', 'iteration 0');

  // 发送模型调用开始消息(可选)
  sendMessage(ws, 'model-invocation-state', 'start');

  // 发送聊天内容
  await sendChatOutput(ws, userInput);

  // 发送模型调用结束消息(可选)
  sendMessage(ws, 'model-invocation-state', 'end');

  // 发送迭代结束消息(可选)
  sendMessage(ws, 'iteration-state', 'end', 'iteration 0');

  // 发送响应结束消息
  sendMessage(ws, 'response-state', 'end');
}

async function sendChatOutput(ws, userInput) {
  const chatId = generateId();

  // 发送聊天内容开始
  ws.send(JSON.stringify({
    id: chatId,
    type: 'chat-output',
    event: 'start',
  }));

  // 调用 OpenAI API(流式输出)
  const stream = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: userInput }],
    stream: true,
  });

  // 逐个 token 发送
  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || '';
    if (content) {
      ws.send(JSON.stringify({
        id: chatId,
        type: 'chat-output',
        event: 'delta',
        content,
      }));
    }
  }

  // 发送聊天内容结束
  ws.send(JSON.stringify({
    id: chatId,
    type: 'chat-output',
    event: 'end',
  }));
}

6. 启动服务 ​

在启动服务之前,需要设置 OpenAI API Key 环境变量:

bash
export OPENAI_API_KEY='your-api-key-here'

然后启动服务:

bash
node server.js

服务启动后,你会在控制台看到:

External Agent WebSocket server is running on ws://localhost:8080

接入 Mythra Agent Studio ​

服务启动后,你可以按照 创建自定义 Agent 文档中的说明,在 Agent Manager 中创建一个外部 Agent:

  1. 选择「External AI Agent」类型
  2. 在 URL 字段中填写:ws://localhost:8080
  3. 保存配置

现在你可以在 Mythra Agent Studio 中与你的外部 Agent 进行对话了!

进阶功能 ​

调用工具 ​

如果你的 Agent 需要调用工具(如文件操作、命令执行等),可以发送 tool-call 消息:

javascript
function callTool(ws, func, args) {
  const toolCallId = generateId();

  // 发送工具调用开始
  ws.send(JSON.stringify({
    id: toolCallId,
    type: 'tool-call',
    event: 'start',
    func,
    args,
  }));

  // 执行工具调用(这里需要实现具体的工具逻辑)
  const result = executeTool(func, args);

  // 发送工具调用结束
  ws.send(JSON.stringify({
    id: toolCallId,
    type: 'tool-call',
    event: 'end',
    func,
    args,
  }));

  return result;
}

发送思考内容 ​

如果你想让 Agent 展示思考过程,可以发送 think-output 消息。思考内容通常来自 LLM 的推理过程(如 OpenAI 的 o1 模型)或 Agent 自己的逻辑:

javascript
async function sendThinkOutput(ws, thinkPrompt) {
  const thinkId = generateId();

  // 发送思考内容开始
  ws.send(JSON.stringify({
    id: thinkId,
    type: 'think-output',
    event: 'start',
  }));

  // 调用 LLM 获取思考过程(以 OpenAI o1 模型为例)
  const stream = await openai.chat.completions.create({
    model: 'o1-preview',
    messages: [{ role: 'user', content: thinkPrompt }],
    stream: true,
  });

  // 逐个 token 发送思考内容
  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || '';
    if (content) {
      ws.send(JSON.stringify({
        id: thinkId,
        type: 'think-output',
        event: 'delta',
        content,
      }));
    }
  }

  // 发送思考内容结束
  ws.send(JSON.stringify({
    id: thinkId,
    type: 'think-output',
    event: 'end',
  }));
}

错误处理 ​

当发生错误时,可以发送 error 消息:

javascript
function sendError(ws, errorMessage) {
  ws.send(JSON.stringify({
    id: generateId(),
    type: 'error',
    event: 'error',
    content: errorMessage,
  }));
}

最佳实践 ​

  1. 使用环境变量:将端口号、API 密钥等配置信息放在环境变量中
  2. 日志记录:记录关键操作和错误信息,便于调试
  3. 连接管理:处理客户端断开连接的情况,释放相关资源
  4. 流式响应:对于长文本响应,使用 delta 事件分块发送,提升用户体验
  5. 超时处理:设置合理的超时时间,避免长时间等待

相关文档 ​

Happy Coding!