Initial commit
This commit is contained in:
210
templates/cloudflare-workers/worker-agent-hono.ts
Normal file
210
templates/cloudflare-workers/worker-agent-hono.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Cloudflare Workers + Hono + OpenAI Agents
|
||||
*
|
||||
* Demonstrates:
|
||||
* - Integrating agents with Hono framework
|
||||
* - Multiple agent endpoints
|
||||
* - Streaming with Hono
|
||||
* - Type-safe routing
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { z } from 'zod';
|
||||
import { Agent, run, tool } from '@openai/agents';
|
||||
|
||||
// ========================================
|
||||
// Agents
|
||||
// ========================================
|
||||
|
||||
const summarizerAgent = new Agent({
|
||||
name: 'Summarizer',
|
||||
instructions: 'Summarize text concisely in 2-3 sentences.',
|
||||
model: 'gpt-4o-mini',
|
||||
});
|
||||
|
||||
const translatorAgent = new Agent({
|
||||
name: 'Translator',
|
||||
instructions: 'Translate text accurately while preserving meaning and tone.',
|
||||
model: 'gpt-4o-mini',
|
||||
});
|
||||
|
||||
const analyzerTool = tool({
|
||||
name: 'analyze_sentiment',
|
||||
description: 'Analyze sentiment',
|
||||
parameters: z.object({
|
||||
text: z.string(),
|
||||
}),
|
||||
execute: async ({ text }) => {
|
||||
// Simplified sentiment analysis
|
||||
const positive = ['good', 'great', 'excellent', 'love', 'amazing'];
|
||||
const negative = ['bad', 'terrible', 'hate', 'awful', 'poor'];
|
||||
|
||||
const lowerText = text.toLowerCase();
|
||||
const posCount = positive.filter(w => lowerText.includes(w)).length;
|
||||
const negCount = negative.filter(w => lowerText.includes(w)).length;
|
||||
|
||||
if (posCount > negCount) return 'Positive sentiment detected';
|
||||
if (negCount > posCount) return 'Negative sentiment detected';
|
||||
return 'Neutral sentiment detected';
|
||||
},
|
||||
});
|
||||
|
||||
const analyzerAgent = new Agent({
|
||||
name: 'Analyzer',
|
||||
instructions: 'Analyze text for sentiment, tone, and key themes.',
|
||||
tools: [analyzerTool],
|
||||
model: 'gpt-4o-mini',
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Hono App
|
||||
// ========================================
|
||||
|
||||
type Bindings = {
|
||||
OPENAI_API_KEY: string;
|
||||
};
|
||||
|
||||
const app = new Hono<{ Bindings: Bindings }>();
|
||||
|
||||
// Enable CORS
|
||||
app.use('/*', cors());
|
||||
|
||||
// Health check
|
||||
app.get('/', (c) => {
|
||||
return c.json({
|
||||
service: 'OpenAI Agents API',
|
||||
version: '1.0.0',
|
||||
agents: ['summarizer', 'translator', 'analyzer'],
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Summarizer Endpoint
|
||||
// ========================================
|
||||
|
||||
app.post('/api/summarize', async (c) => {
|
||||
try {
|
||||
const { text } = await c.req.json();
|
||||
|
||||
if (!text) {
|
||||
return c.json({ error: 'Missing text parameter' }, 400);
|
||||
}
|
||||
|
||||
// Set API key from environment
|
||||
process.env.OPENAI_API_KEY = c.env.OPENAI_API_KEY;
|
||||
|
||||
const result = await run(summarizerAgent, text);
|
||||
|
||||
return c.json({
|
||||
summary: result.finalOutput,
|
||||
tokens: result.usage.totalTokens,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
return c.json({ error: error.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Translator Endpoint
|
||||
// ========================================
|
||||
|
||||
app.post('/api/translate', async (c) => {
|
||||
try {
|
||||
const { text, targetLanguage } = await c.req.json();
|
||||
|
||||
if (!text || !targetLanguage) {
|
||||
return c.json({ error: 'Missing required parameters' }, 400);
|
||||
}
|
||||
|
||||
process.env.OPENAI_API_KEY = c.env.OPENAI_API_KEY;
|
||||
|
||||
const result = await run(
|
||||
translatorAgent,
|
||||
`Translate the following to ${targetLanguage}: ${text}`
|
||||
);
|
||||
|
||||
return c.json({
|
||||
translation: result.finalOutput,
|
||||
sourceLanguage: 'auto-detected',
|
||||
targetLanguage,
|
||||
tokens: result.usage.totalTokens,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
return c.json({ error: error.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Analyzer Endpoint (with Streaming)
|
||||
// ========================================
|
||||
|
||||
app.post('/api/analyze', async (c) => {
|
||||
try {
|
||||
const { text, stream = false } = await c.req.json();
|
||||
|
||||
if (!text) {
|
||||
return c.json({ error: 'Missing text parameter' }, 400);
|
||||
}
|
||||
|
||||
process.env.OPENAI_API_KEY = c.env.OPENAI_API_KEY;
|
||||
|
||||
// Non-streaming
|
||||
if (!stream) {
|
||||
const result = await run(analyzerAgent, `Analyze this text: ${text}`);
|
||||
return c.json({
|
||||
analysis: result.finalOutput,
|
||||
tokens: result.usage.totalTokens,
|
||||
});
|
||||
}
|
||||
|
||||
// Streaming
|
||||
const streamResult = await run(analyzerAgent, `Analyze: ${text}`, {
|
||||
stream: true,
|
||||
});
|
||||
|
||||
const { readable, writable } = new TransformStream();
|
||||
const writer = writable.getWriter();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Stream in background
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of streamResult) {
|
||||
if (event.type === 'raw_model_stream_event') {
|
||||
const chunk = event.data?.choices?.[0]?.delta?.content || '';
|
||||
if (chunk) {
|
||||
await writer.write(encoder.encode(`data: ${JSON.stringify({ chunk })}\n\n`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await streamResult.completed;
|
||||
await writer.write(encoder.encode(`data: ${JSON.stringify({ done: true })}\n\n`));
|
||||
} catch (error: any) {
|
||||
await writer.write(encoder.encode(`data: ${JSON.stringify({ error: error.message })}\n\n`));
|
||||
} finally {
|
||||
await writer.close();
|
||||
}
|
||||
})();
|
||||
|
||||
return new Response(readable, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
return c.json({ error: error.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Export Worker
|
||||
// ========================================
|
||||
|
||||
export default app;
|
||||
173
templates/cloudflare-workers/worker-text-agent.ts
Normal file
173
templates/cloudflare-workers/worker-text-agent.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Cloudflare Workers with OpenAI Agents SDK
|
||||
*
|
||||
* Demonstrates:
|
||||
* - Running text agents in Cloudflare Workers
|
||||
* - Handling agent requests via fetch()
|
||||
* - Streaming responses to clients
|
||||
* - Error handling in Workers environment
|
||||
*
|
||||
* NOTE: OpenAI Agents SDK has experimental Cloudflare Workers support
|
||||
* Some features may not work due to runtime limitations
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { Agent, run, tool } from '@openai/agents';
|
||||
|
||||
// ========================================
|
||||
// Agent Definition
|
||||
// ========================================
|
||||
|
||||
const searchTool = tool({
|
||||
name: 'search_docs',
|
||||
description: 'Search documentation',
|
||||
parameters: z.object({
|
||||
query: z.string(),
|
||||
}),
|
||||
execute: async ({ query }) => {
|
||||
// In production, query a vector database or search API
|
||||
return `Found documentation about: ${query}`;
|
||||
},
|
||||
});
|
||||
|
||||
const docsAgent = new Agent({
|
||||
name: 'Documentation Assistant',
|
||||
instructions: 'Help users find information in our documentation. Use the search tool when needed.',
|
||||
tools: [searchTool],
|
||||
model: 'gpt-4o-mini', // Use smaller model for cost efficiency
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Cloudflare Worker
|
||||
// ========================================
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
// Handle CORS preflight
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (request.method !== 'POST') {
|
||||
return new Response('Method not allowed', { status: 405 });
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse request body
|
||||
const { message, stream = false } = await request.json() as {
|
||||
message: string;
|
||||
stream?: boolean;
|
||||
};
|
||||
|
||||
if (!message || typeof message !== 'string') {
|
||||
return new Response(JSON.stringify({ error: 'Invalid message' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
// Set OPENAI_API_KEY from environment
|
||||
process.env.OPENAI_API_KEY = env.OPENAI_API_KEY;
|
||||
|
||||
// ========================================
|
||||
// Non-Streaming Response
|
||||
// ========================================
|
||||
|
||||
if (!stream) {
|
||||
const result = await run(docsAgent, message, {
|
||||
maxTurns: 5,
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
response: result.finalOutput,
|
||||
agent: result.currentAgent?.name,
|
||||
tokens: result.usage.totalTokens,
|
||||
}), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Streaming Response
|
||||
// ========================================
|
||||
|
||||
const streamResult = await run(docsAgent, message, {
|
||||
stream: true,
|
||||
maxTurns: 5,
|
||||
});
|
||||
|
||||
// Create readable stream for response
|
||||
const { readable, writable } = new TransformStream();
|
||||
const writer = writable.getWriter();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Stream events to client
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of streamResult) {
|
||||
if (event.type === 'raw_model_stream_event') {
|
||||
const chunk = event.data?.choices?.[0]?.delta?.content || '';
|
||||
if (chunk) {
|
||||
await writer.write(encoder.encode(`data: ${JSON.stringify({ type: 'chunk', content: chunk })}\n\n`));
|
||||
}
|
||||
} else if (event.type === 'agent_updated_stream_event') {
|
||||
await writer.write(encoder.encode(`data: ${JSON.stringify({ type: 'agent_change', agent: event.agent.name })}\n\n`));
|
||||
}
|
||||
}
|
||||
|
||||
await streamResult.completed;
|
||||
|
||||
// Send final message
|
||||
await writer.write(encoder.encode(`data: ${JSON.stringify({
|
||||
type: 'done',
|
||||
tokens: streamResult.result.usage.totalTokens
|
||||
})}\n\n`));
|
||||
|
||||
} catch (error: any) {
|
||||
await writer.write(encoder.encode(`data: ${JSON.stringify({ type: 'error', message: error.message })}\n\n`));
|
||||
} finally {
|
||||
await writer.close();
|
||||
}
|
||||
})();
|
||||
|
||||
return new Response(readable, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Worker error:', error);
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
error: error.message || 'Internal server error',
|
||||
}), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// Environment Types
|
||||
// ========================================
|
||||
|
||||
interface Env {
|
||||
OPENAI_API_KEY: string;
|
||||
}
|
||||
Reference in New Issue
Block a user