agent-server/nodejs/README.md
Rama main · réplica de lectura del repositorio original.
# bo-eval-server
A library-first evaluation server for LLM agents with modular architecture and programmatic API.
## Features
- 📚 **Library-First Architecture**: Programmatic API for custom integrations
- 🔌 **WebSocket Server**: Real-time agent connections (core)
- 🌐 **Optional HTTP API**: REST endpoints via separate wrapper
- 🤖 **Bidirectional RPC**: Call methods on connected agents
- ⚖️ **Optional LLM Judge**: GPT-4 evaluation (when configured)
- 📊 **Structured Logging**: JSON logging of all evaluations
- 🖥️ **Interactive CLI**: Built-in management interface
- ⚡ **Concurrent Evaluations**: Multi-agent support
- ✨ **No Configuration Required**: Works without config files or API keys
## Quick Start
### Basic WebSocket Server
```javascript
import { EvalServer } from 'bo-eval-server';
const server = new EvalServer({
authKey: 'hello',
host: '127.0.0.1',
port: 8080
});
server.onConnect(async client => {
console.log('Client connected:', client.id);
const response = await client.evaluate({
id: "test_eval",
name: "Capital of France",
tool: "chat",
input: { message: "What is the capital of France?" }
});
console.log('Response:', JSON.stringify(response, null, 2));
});
await server.start();
console.log('Server running on ws://127.0.0.1:8080');
```
### With Optional HTTP API
```javascript
import { EvalServer, HTTPWrapper } from 'bo-eval-server';
// Create core WebSocket server
const evalServer = new EvalServer({
authKey: 'hello',
port: 8080
});
// Add optional HTTP API wrapper
const httpWrapper = new HTTPWrapper(evalServer, {
port: 8081
});
// Set up client connection handler
evalServer.onConnect(async client => {
// Handle evaluations...
});
// Start both servers
await evalServer.start();
await httpWrapper.start();
console.log('WebSocket: ws://localhost:8080');
console.log('HTTP API: http://localhost:8081');
```
## Installation & Setup
```bash
# Install dependencies
npm install
# Run examples
npm start # Server with HTTP API
npm run lib:example # WebSocket-only server
npm run cli # Interactive CLI
npm run dev # Development mode
```
## Library Usage
### Core EvalServer API
The `EvalServer` class provides the core WebSocket-based evaluation server:
```javascript
import { EvalServer } from 'bo-eval-server';
const server = new EvalServer({
// Required
authKey: 'your-secret-key', // Client authentication key
// Optional
host: '127.0.0.1', // Server host (default: 'localhost')
port: 8080, // Server port (default: 8080)
clientsDir: './clients', // Client config directory
evalsDir: './evals' // Evaluations directory
});
// Event handlers
server.onConnect(clientProxy => {
// Called when client connects and is ready
});
server.onDisconnect(clientInfo => {
// Called when client disconnects
});
// Server lifecycle
await server.start();
await server.stop();
// Server status
console.log(server.getStatus());
```
### Client Proxy API
When a client connects, you receive a `ClientProxy` object:
```javascript
server.onConnect(async client => {
// Client information
console.log('Client ID:', client.id);
console.log('Tab ID:', client.tabId);
console.log('Base Client ID:', client.baseClientId);
console.log('Info:', client.getInfo());
// Execute evaluations
const result = await client.evaluate({
id: "eval_001", // Unique evaluation ID
name: "Test Evaluation", // Human-readable name
description: "Description", // Optional description
tool: "chat", // Tool to use: "chat", "action", etc.
input: { // Tool-specific input
message: "Your question here"
},
timeout: 30000, // Optional timeout (ms)
model: { // Optional nested model config
main_model: {
provider: "openai",
model: "gpt-4",
api_key: "sk-..."
},
mini_model: {
provider: "openai",
model: "gpt-4-mini",
api_key: "sk-..."
},
nano_model: {
provider: "groq",
model: "llama-3.1-8b-instant",
api_key: "gsk-..."
}
},
metadata: { // Optional metadata
tags: ['api', 'test']
}
});
// Send custom messages
client.sendMessage({
type: 'custom',
data: 'Hello client!'
});
});
```
### Advanced Usage with YAML Evaluations
```javascript
import { EvalServer, EvaluationLoader } from 'bo-eval-server';
const server = new EvalServer({
authKey: 'secret-key',
port: 8080
});
// Load evaluations from YAML files
await server.loadEvaluations('./evals');
// Access evaluation loader
const loader = server.evaluationLoader;
// Get evaluation statistics
const stats = loader.getStatistics();
console.log('Total evaluations:', stats.total);
console.log('Categories:', stats.categories);
// Filter evaluations
const chatEvals = loader.filterEvaluations({
tool: 'chat',
enabled: true
});
const actionAgentEvals = loader.getEvaluationsByCategory('action-agent');
// Create custom evaluations
const customEval = loader.createEvaluation({
name: 'Custom Test',
tool: 'chat',
input: { message: 'What is AI?' },
metadata: { tags: ['custom'] }
});
server.onConnect(async client => {
// Run YAML-loaded evaluation
const result1 = await client.evaluate(chatEvals[0]);
// Run custom evaluation
const result2 = await client.evaluate(customEval);
console.log('Results:', { result1, result2 });
});
await server.start();
```
### Optional LLM Judge System
```javascript
import { EvalServer, LLMJudge } from 'bo-eval-server';
const server = new EvalServer({
authKey: 'secret-key',
port: 8080
});
// Configure LLM judge (requires OPENAI_API_KEY)
if (process.env.OPENAI_API_KEY) {
const judge = new LLMJudge({
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4',
temperature: 0.1
});
server.setJudge(judge);
// Judge will automatically validate evaluation responses
// when evaluations have validation configuration
}
await server.start();
```
### Standalone Components
#### EvaluationLoader
```javascript
import { EvaluationLoader } from 'bo-eval-server/EvaluationLoader';
const loader = new EvaluationLoader('./evals');
// Load from directory
await loader.loadFromDirectory('./my-evals');
// Access evaluations
const all = loader.getAllEvaluations();
const byCategory = loader.getEvaluationsByCategory('action-agent');
const byId = loader.getEvaluationById('test-001');
// Filter evaluations
const filtered = loader.filterEvaluations({
tool: 'chat',
enabled: true,
category: 'research-agent'
});
// Create evaluations programmatically
const custom = loader.createEvaluation({
name: 'Custom Evaluation',
tool: 'chat',
input: { message: 'Hello world' }
});
```
#### LLM Judge
```javascript
import { LLMJudge } from 'bo-eval-server/judges/LLMJudge';
const judge = new LLMJudge({
apiKey: 'your-openai-key',
model: 'gpt-4',
temperature: 0.1
});
const evaluation = await judge.evaluate(
'Summarize this article',
'This article discusses...',
{
criteria: ['accuracy', 'completeness', 'clarity'],
model: 'gpt-4'
}
);
console.log('Score:', evaluation.score);
console.log('Reasoning:', evaluation.reasoning);
```
## HTTP API (Optional)
The `HTTPWrapper` provides REST endpoints for integration with external systems:
```javascript
import { EvalServer, HTTPWrapper } from 'bo-eval-server';
const evalServer = new EvalServer({ port: 8080 });
const httpWrapper = new HTTPWrapper(evalServer, {
port: 8081,
host: 'localhost'
});
await evalServer.start();
await httpWrapper.start();
// HTTP wrapper status
console.log(httpWrapper.getStatus());
```
### HTTP Endpoints
Once the HTTP wrapper is running, you can use these endpoints:
```bash
# Server status
curl http://localhost:8081/status
# List clients
curl http://localhost:8081/clients
# Trigger evaluation
curl -X POST http://localhost:8081/evaluate \
-H 'Content-Type: application/json' \
-d '{"clientId": "client-123", "evaluationId": "eval-001"}'
# OpenAI-compatible responses endpoint
curl -X POST http://localhost:8081/v1/responses \
-H 'Content-Type: application/json' \
-d '{"input": "What is 2+2?"}'
```
#### `/v1/responses` Endpoint Details
The `/v1/responses` endpoint provides an OpenAI-compatible interface for chat requests. **Each request automatically creates a new browser tab** to isolate the chat session.
**Behavior:**
1. Finds a client with existing tabs (skips dummy clients with 0 tabs)
2. Opens a new browser tab via Chrome DevTools Protocol (CDP)
3. Waits for the tab's DevTools to connect (up to 10 seconds)
4. Executes the chat request in the isolated new tab
5. Returns the response in OpenAI Responses API format
**Request Format:**
```json
{
"input": "Your question or prompt here",
"url": "about:blank",
"wait_timeout": 5000,
"model": {
"main_model": {
"provider": "openai",
"model": "gpt-4",
"api_key": "sk-..."
},
"mini_model": {
"provider": "openai",
"model": "gpt-4-mini",
"api_key": "sk-..."
},
"nano_model": {
"provider": "openai",
"model": "gpt-3.5-turbo",
"api_key": "sk-..."
}
}
}
```
**LiteLLM Provider with Endpoint:**
```json
{
"input": "Your question or prompt here",
"url": "about:blank",
"model": {
"main_model": {
"provider": "litellm",
"model": "qwen3:14b",
"endpoint": "http://localhost:4000"
},
"mini_model": {
"provider": "litellm",
"model": "qwen3:14b",
"endpoint": "http://localhost:4000"
},
"nano_model": {
"provider": "litellm",
"model": "qwen3:14b",
"endpoint": "http://localhost:4000"
}
}
}
```
**Endpoint Configuration Priority:**
1. Per-tier endpoint (e.g., `main_model.endpoint`) - highest priority
2. Top-level endpoint (e.g., `model.endpoint`) - applies to all tiers
3. Environment variable `LITELLM_ENDPOINT` - fallback
4. Default: `http://localhost:4000` (LiteLLMProvider built-in default)
**Conversation State Format (OpenAI Responses API):**
You can also provide conversation history using an array of messages:
```json
{
"input": [
{
"role": "system",
"content": "You are a web automation expert."
},
{
"role": "user",
"content": "Navigate to bloomberg.com"
},
{
"role": "assistant",
"content": "I've navigated to bloomberg.com. I can see the homepage."
},
{
"role": "user",
"content": "Summarize todays news"
}
],
"url": "https://bloomberg.com",
"model": {
"main_model": {
"provider": "litellm",
"model": "gemma3:12b",
"endpoint": "http://localhost:4000"
}
}
}
```
**Message Roles:**
- `system` - System prompt/instructions (extracted and used as system prompt)
- `user` - User messages
- `assistant` - Previous assistant responses (for conversation history)
**Requirements:**
- At least one `user` message must be present
- Each message must have `role` and `content` fields
- Maximum 100 messages per conversation
- Maximum 10,000 characters per message
**Response Format:**
```json
[
{
"id": "msg_...",
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Response text here",
"annotations": []
}
]
}
]
```
**Requirements:**
- At least one DevTools client must have a connected tab
- Chrome must be running with remote debugging enabled (port 9223)
**Benefits:**
- Each chat request runs in isolation
- Supports parallel requests in different tabs
- Better tracking and debugging per request
- Automatic cleanup via browser tab management
## CLI Usage
Interactive command-line interface for server management:
```bash
# Using npm scripts
npm run cli
# Using the binary
npx eval-server
# Or directly
node src/cli/index.js
```
The CLI provides commands for:
- Server management
- Client connections
- Evaluation execution
- Real-time monitoring
## Agent Protocol
Your agent needs to implement the WebSocket protocol:
### 1. Connect to WebSocket
```javascript
const ws = new WebSocket('ws://localhost:8080');
```
### 2. Send Registration
```javascript
ws.send(JSON.stringify({
type: 'register',
clientId: 'your-client-id',
secretKey: 'your-secret-key',
capabilities: ['chat', 'action']
}));
```
### 3. Send Ready Signal
```javascript
ws.send(JSON.stringify({
type: 'ready'
}));
```
### 4. Handle RPC Calls
```javascript
ws.on('message', (data) => {
const message = JSON.parse(data);
if (message.jsonrpc === '2.0' && message.method === 'evaluate') {
// Handle evaluation request
const result = await handleEvaluation(message.params);
// Send response
ws.send(JSON.stringify({
jsonrpc: '2.0',
id: message.id,
result: result
}));
}
});
```
## Architecture
```
src/
├── lib/ # Core library
│ ├── EvalServer.js # Main server class (WebSocket only)
│ ├── HTTPWrapper.js # Optional HTTP API wrapper
│ ├── EvaluationLoader.js # YAML evaluation loader
│ └── judges/ # Judge implementations
│ ├── Judge.js # Base judge interface
│ └── LLMJudge.js # LLM-based judge
├── cli/ # CLI implementation
│ ├── CLI.js # CLI class
│ └── index.js # CLI entry point
├── examples/ # Usage examples
│ ├── library-usage.js # Basic WebSocket-only example
│ └── with-http-wrapper.js # Example with HTTP API
└── [utilities] # Configuration, logging, etc.
```
### Design Principles
- **Library-First**: Everything built as composable modules
- **Optional Components**: HTTP API, LLM Judge, YAML loading all optional
- **Clean Architecture**: No external dependencies for core functionality
- **Event-Driven**: React to client connections with callbacks
- **Programmatic**: Full control through code, no required config files
## Examples
### Example 1: Simple Chat Evaluation
```javascript
import { EvalServer } from 'bo-eval-server';
const server = new EvalServer({ authKey: 'test', port: 8080 });
server.onConnect(async client => {
const response = await client.evaluate({
id: "chat_test",
name: "Simple Chat",
tool: "chat",
input: { message: "Hello, how are you?" }
});
console.log('Chat response:', response.output.response);
});
await server.start();
```
### Example 2: Action Agent Evaluation
```javascript
import { EvalServer } from 'bo-eval-server';
const server = new EvalServer({ authKey: 'test', port: 8080 });
server.onConnect(async client => {
const response = await client.evaluate({
id: "action_test",
name: "Click Button",
tool: "action",
input: {
objective: "Click the submit button on the form",
url: "https://example.com/form"
}
});
console.log('Action completed:', response.output.success);
});
await server.start();
```
### Example 3: Batch Evaluations
```javascript
import { EvalServer } from 'bo-eval-server';
const server = new EvalServer({ authKey: 'test', port: 8080 });
// Load evaluations from YAML
await server.loadEvaluations('./evals');
server.onConnect(async client => {
const chatEvals = server.evaluationLoader.filterEvaluations({
tool: 'chat',
enabled: true
});
// Run all chat evaluations
for (const evaluation of chatEvals.slice(0, 5)) {
try {
const result = await client.evaluate(evaluation);
console.log(`✅ ${evaluation.name}: ${result.status}`);
} catch (error) {
console.log(`❌ ${evaluation.name}: ${error.message}`);
}
}
});
await server.start();
```
## Environment Variables
```bash
# Optional - only needed if using LLM Judge
OPENAI_API_KEY=your-openai-api-key
# Optional - server configuration
PORT=8080
HOST=localhost
LOG_LEVEL=info
LOG_DIR=./logs
# Optional - RPC configuration
RPC_TIMEOUT=1500000
MAX_CONCURRENT_EVALUATIONS=10
```
## Documentation
- **[CLAUDE.md](./CLAUDE.md)** - Architecture and implementation details
- **[docs/](./docs/)** - Protocol specifications and setup guides
- **[examples/](./examples/)** - Working code examples
---
The library provides a clean, modular architecture for building custom evaluation workflows with LLM agents.