diff --git a/docs/src/content/docs/agents/built-in/google-agent.mdx b/docs/src/content/docs/agents/built-in/google-agent.mdx new file mode 100644 index 00000000..d032a557 --- /dev/null +++ b/docs/src/content/docs/agents/built-in/google-agent.mdx @@ -0,0 +1,317 @@ +```markdown +--- +title: Google AI Agent +description: Documentation for the Google AI Agent +--- + +The `GoogleAIAgent` is a versatile agent class within the Multi-Agent Orchestrator framework, designed to integrate with Google's Gemini API. This agent empowers you to harness Google's advanced language models for a wide range of natural language processing tasks. + +## Key Features + +- Integration with Google's Gemini API +- Support for various Gemini models +- Streaming and non-streaming response options +- Customizable inference configuration +- Conversation history management for context-aware interactions +- Flexible system prompt customization with variable support +- Support for retrievers to enrich responses with external knowledge +- Flexible initialization using API key or custom client + +## Configuration Options + +The `GoogleAIAgentOptions` class, which extends the base `AgentOptions`, provides the following configuration fields: + +### Required Fields + +- `name`: The agent's name. +- `description`: A description of the agent's capabilities. +- Authentication (choose one): + - `apiKey`: Your Google Gemini API key. + - `client`: A custom Google Gemini client instance. + +### Optional Fields +- `model`: Google Gemini model identifier (e.g., 'gemini-pro'). Defaults to `GOOGLE_MODEL_ID_GEMINI_PRO`. +- `streaming`: Enables streaming responses. Defaults to `false`. +- `retriever`: A custom retriever instance for providing additional context. +- `inferenceConfig`: Configuration for model inference: + - `maxOutputTokens`: Maximum tokens to generate (default: 1000). + - `temperature`: Controls randomness (0-1). + - `topP`: Controls diversity via nucleus sampling. + - `stopSequences`: Sequences that stop generation. +- `customSystemPrompt`: System prompt configuration: + - `template`: Template string with optional variable placeholders. + - `variables`: Key-value pairs for template variables. + +## Creating a GoogleAIAgent + +Here are several examples demonstrating different ways to create and configure a `GoogleAIAgent`: + +### Basic Examples + +**1. Minimal Configuration** + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + + + +```typescript +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'A versatile AI assistant', + apiKey: 'your-google-ai-api-key' +}); +``` + + + +
+ +**2. Using Custom Client** + + + +```typescript +import { GoogleGenerativeAI } from '@google/generative-ai'; +const customClient = new GoogleGenerativeAI('your-google-ai-api-key'); + +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'A versatile AI assistant', + client: customClient +}); +``` + + + + +
+ +**3. Custom Model and Streaming** + + + +```typescript +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'A streaming-enabled assistant', + apiKey: 'your-google-ai-api-key', + model: 'gemini-pro', + streaming: true +}); +``` + + + +
+ + +**4. With Inference Configuration** + + + +```typescript +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'An assistant with custom inference settings', + apiKey: 'your-google-ai-api-key', + inferenceConfig: { + maxOutputTokens: 500, + temperature: 0.7, + topP: 0.9, + stopSequences: ['Human:', 'AI:'] + } +}); +``` + + + +
+ +**5. With Simple System Prompt** + + + +```typescript +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'An assistant with custom prompt', + apiKey: 'your-google-ai-api-key', + customSystemPrompt: { + template: 'You are a helpful AI assistant focused on technical support.' + } +}); +``` + + + +
+ +**6. With System Prompt Variables** + + + +```typescript +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'An assistant with variable prompt', + apiKey: 'your-google-ai-api-key', + customSystemPrompt: { + template: 'You are an AI assistant specialized in {{DOMAIN}}. Always use a {{TONE}} tone.', + variables: { + DOMAIN: 'customer support', + TONE: 'friendly and helpful' + } + } +}); +``` + + + +
+ +**7. With Custom Retriever** + + + +```typescript +const retriever = new CustomRetriever({ + // Retriever configuration +}); +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'An assistant with retriever', + apiKey: 'your-google-ai-api-key', + retriever: retriever +}); +``` + + +
+ +**8. Combining Multiple Options** + + + +```typescript +const agent = new GoogleAIAgent({ + name: 'Google AI Assistant', + description: 'An assistant with multiple options', + apiKey: 'your-google-ai-api-key', + model: 'gemini-pro', + streaming: true, + inferenceConfig: { + maxOutputTokens: 500, + temperature: 0.7 + }, + customSystemPrompt: { + template: 'You are an AI assistant specialized in {{DOMAIN}}.', + variables: { + DOMAIN: 'technical support' + } + } +}); +``` + + +
+ +**9. Complete Example with All Options** + +Here's a comprehensive example demonstrating all available configuration options: + + + +```typescript +import { GoogleAIAgent } from 'multi-agent-orchestrator'; + +const agent = new GoogleAIAgent({ + // Required fields + name: 'Advanced Google AI Assistant', + description: 'A fully configured AI assistant powered by Google Gemini', + apiKey: 'your-google-ai-api-key', + + // Optional fields + model: 'gemini-pro', // Choose Google Gemini model + streaming: true, // Enable streaming responses + retriever: customRetriever, // Custom retriever for additional context + + // Inference configuration + inferenceConfig: { + maxOutputTokens: 500, // Maximum tokens to generate + temperature: 0.7, // Control randomness (0-1) + topP: 0.9, // Control diversity via nucleus sampling + stopSequences: ['Human:', 'AI:'] // Sequences that stop generation + }, + + // Custom system prompt with variables + customSystemPrompt: { + template: `You are an AI assistant specialized in {{DOMAIN}}. + Your core competencies: + {{SKILLS}} + + Communication style: + - Maintain a {{TONE}} tone + - Focus on {{FOCUS}} + - Prioritize {{PRIORITY}}`, + variables: { + DOMAIN: 'scientific research', + SKILLS: [ + '- Advanced data analysis', + '- Statistical methodology', + '- Research design', + '- Technical writing' + ], + TONE: 'professional and academic', + FOCUS: 'accuracy and clarity', + PRIORITY: 'evidence-based insights' + } + } +}); +``` + + + +## Using the GoogleAIAgent + +You can use the `GoogleAIAgent` either directly or within the Multi-Agent Orchestrator framework. + +### Direct Usage + +Call the agent directly when you need to use a single agent without orchestrator routing: + + + +```typescript +const classifierResult = { + selectedAgent: agent, + confidence: 1.0 +}; + +const response = await orchestrator.agentProcessRequest( + "What is the capital of France?", + "user123", + "session456", + classifierResult +); +``` + + + +### Using with the Orchestrator + +Add the agent to the Multi-Agent Orchestrator for use in a multi-agent system: + + + +```typescript +const orchestrator = new MultiAgentOrchestrator(); +orchestrator.addAgent(agent); + +const response = await orchestrator.routeRequest( + "What is the capital of France?", + "user123", + "session456" +); +``` + + diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 494c1b35..ef098ea9 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-agent-orchestrator", - "version": "0.1.1", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-agent-orchestrator", - "version": "0.1.1", + "version": "0.1.2", "license": "Apache-2.0", "dependencies": { "@anthropic-ai/sdk": "^0.24.3", @@ -18,8 +18,8 @@ "@aws-sdk/client-lex-runtime-v2": "^3.621.0", "@aws-sdk/lib-dynamodb": "^3.621.0", "@aws-sdk/util-dynamodb": "^3.621.0", + "@google/generative-ai": "^0.21.0", "axios": "^1.7.2", - "chai": "^5.1.2", "eslint-config-prettier": "^9.1.0", "natural": "^7.0.7", "openai": "^4.52.7", @@ -2524,6 +2524,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@google/generative-ai": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz", + "integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -4271,14 +4279,6 @@ "node": ">=8" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "engines": { - "node": ">=12" - } - }, "node_modules/async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -4598,21 +4598,6 @@ } ] }, - "node_modules/chai": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", - "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4637,14 +4622,6 @@ "node": ">=10" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "engines": { - "node": ">= 16" - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4825,14 +4802,6 @@ } } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6861,11 +6830,6 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "node_modules/loupe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", - "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7498,14 +7462,6 @@ "node": ">=8" } }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pg": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", diff --git a/typescript/package.json b/typescript/package.json index 0b57005d..848e51fc 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -36,6 +36,7 @@ "@aws-sdk/client-lex-runtime-v2": "^3.621.0", "@aws-sdk/lib-dynamodb": "^3.621.0", "@aws-sdk/util-dynamodb": "^3.621.0", + "@google/generative-ai": "^0.21.0", "axios": "^1.7.2", "eslint-config-prettier": "^9.1.0", "natural": "^7.0.7", diff --git a/typescript/src/agents/googleAIAgent.ts b/typescript/src/agents/googleAIAgent.ts new file mode 100644 index 00000000..2360fb1b --- /dev/null +++ b/typescript/src/agents/googleAIAgent.ts @@ -0,0 +1,88 @@ +import { Agent, type AgentOptions } from "./agent";; +import { type ConversationMessage, ParticipantRole } from "../types"; +import { GenerativeModel, GoogleGenerativeAI } from "@google/generative-ai"; +import { Logger } from "../utils/logger"; + +export interface GoogleAIAgentOptions extends AgentOptions { + apiKey: string; + modelId?: string; + baseUrl?: string; + streaming?: boolean; + inferenceConfig?: { + maxOutputTokens?: number; + temperature?: number; + topP?: number; + stopSequences?: string[]; + }; +} + +const DEFAULT_MAX_OUTPUT_TOKENS = 1000; + +export class GoogleAIAgent extends Agent { + private genAI: GoogleGenerativeAI; + private modelId: string; + private client: GenerativeModel; + private baseUrl: string | undefined; + private streaming: boolean; + private inferenceConfig: { + maxOutputTokens?: number; + temperature?: number; + topP?: number; + stopSequences?: string[]; + }; + + constructor(options: GoogleAIAgentOptions) { + super(options); + this.genAI = new GoogleGenerativeAI(options.apiKey); + this.modelId = options.modelId ?? 'gemini-pro'; + this.baseUrl = options.baseUrl; + this.client = this.genAI.getGenerativeModel({ model: this.modelId }, {baseUrl: this.baseUrl}); + this.streaming = options.streaming ?? false; + this.inferenceConfig = { + maxOutputTokens: options.inferenceConfig?.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS, + temperature: options.inferenceConfig?.temperature, + topP: options.inferenceConfig?.topP, + stopSequences: options.inferenceConfig?.stopSequences, + }; + } + /* eslint-disable @typescript-eslint/no-unused-vars */ + async processRequest( + inputText: string, + userId: string, + sessionId: string, + chatHistory: ConversationMessage[], + additionalParams?: Record + ): Promise> { + const chat = this.client.startChat(); + + for (const msg of chatHistory) { + chat.sendMessage(msg.role.toLowerCase(), msg.content ? msg.content[0]?.text || '' : ''); + } + + const { maxOutputTokens, temperature, topP, stopSequences } = this.inferenceConfig; + const generationConfig = { maxOutputTokens, temperature, topP, stopSequences }; + if (this.streaming) { + return this.handleStreamingResponse(chat, inputText, generationConfig); + } else { + return this.handleSingleResponse(chat, inputText, generationConfig); + } + } + + private async handleSingleResponse(chat: any, inputText: string, generationConfig: any): Promise { + try { + const result = await chat.sendMessage(inputText, generationConfig); + const response = result.response; + return { role: ParticipantRole.ASSISTANT, content: [{ text: response.text() }] }; + } catch (error) { + Logger.logger.error('Error in Google Generative AI call:', error); + throw error; + } + } + + private async *handleStreamingResponse(chat: any, inputText: string, generationConfig: any): AsyncIterable { + const result = await chat.sendMessageStream(inputText, generationConfig); + for await (const chunk of result.stream) { + yield chunk.text(); + } + } +} diff --git a/typescript/src/classifiers/googleAIClassifier.ts b/typescript/src/classifiers/googleAIClassifier.ts new file mode 100644 index 00000000..7f61c4ed --- /dev/null +++ b/typescript/src/classifiers/googleAIClassifier.ts @@ -0,0 +1,111 @@ +import { GenerativeModel, GoogleGenerativeAI, SchemaType, type Tool } from "@google/generative-ai"; +import { type ConversationMessage } from "../types"; +import { isClassifierToolInput } from "../utils/helpers"; +import { Logger } from "../utils/logger"; +import { Classifier, type ClassifierResult } from "./classifier" + +const GOOGLE_GENERATIVE_AI_MODEL_ID = 'gemini-pro'; + +export interface GoogleGenerativeAIClassifierOptions { + modelId?: string; + inferenceConfig?: { + maxOutputTokens?: number; + temperature?: number; + topP?: number; + stopSequences?: string[]; + }; + apiKey: string; + baseUrl?: string; +} + +export class GoogleAIClassifier extends Classifier { + private genAI: GoogleGenerativeAI; + private client: GenerativeModel; + protected inferenceConfig: { + maxOutputTokens?: number; + temperature?: number; + topP?: number; + stopSequences?: string[]; + }; + private baseUrl: string | undefined; + private tools: Tool[]; + + constructor(options: GoogleGenerativeAIClassifierOptions) { + super(); + + if (!options.apiKey) { + throw new Error("Google Generative AI API key is required"); + } + this.genAI = new GoogleGenerativeAI(options.apiKey); + this.modelId = options.modelId || GOOGLE_GENERATIVE_AI_MODEL_ID; + + const defaultMaxOutputTokens = 1000; + this.inferenceConfig = { + maxOutputTokens: options.inferenceConfig?.maxOutputTokens ?? defaultMaxOutputTokens, + temperature: options.inferenceConfig?.temperature, + topP: options.inferenceConfig?.topP, + stopSequences: options.inferenceConfig?.stopSequences, + }; + this.baseUrl = options.baseUrl; + this.client = this.genAI.getGenerativeModel({ model: this.modelId }, {baseUrl: this.baseUrl}); + this.tools = [{ + functionDeclarations: [{ + name: 'analyzePrompt', + description: 'Analyze the user input and provide structured output', + parameters: { + type: SchemaType.OBJECT, + properties: { + userinput: { type: SchemaType.STRING }, + selected_agent: { type: SchemaType.STRING }, + confidence: { type: SchemaType.NUMBER }, + }, + required: ['userinput', 'selected_agent', 'confidence'], + }, + }], + }]; + } + /* eslint-disable @typescript-eslint/no-unused-vars */ + async processRequest( + inputText: string, + chatHistory: ConversationMessage[] + ): Promise { + const chat = this.client.startChat({ + tools: this.tools, + systemInstruction: { + role: "system", + parts: [{text: this.systemPrompt}] + } + }); + + try { + const result = await chat.sendMessage(inputText, { + // maxOutputTokens: this.inferenceConfig.maxOutputTokens, + // temperature: this.inferenceConfig.temperature, + // topP: this.inferenceConfig.topP, + // stopSequences: this.inferenceConfig.stopSequences, + }); + + const toolCall = result.response.functionCalls()?.[0]; + + if (!toolCall || toolCall.name !== "analyzePrompt") { + throw new Error("No valid tool call found in the response"); +} + + const toolInput = toolCall.args; + + if (!isClassifierToolInput(toolInput)) { + throw new Error("Tool input does not match expected structure"); + } + + const intentClassifierResult: ClassifierResult = { + selectedAgent: this.getAgentById(toolInput.selected_agent), + confidence: parseFloat(toolInput.confidence), + }; + return intentClassifierResult; + + } catch (error) { + Logger.logger.error("Error processing request:", error); + throw error; + } + } +}