UNPKG

@polybiouslabs/polybious

Version:

Polybius is a next-generation intelligent agent framework built for adaptability across diverse domains. It merges contextual awareness, multi-agent collaboration, and predictive reasoning to deliver dynamic, self-optimizing performance.

347 lines (346 loc) 14.4 kB
import Anthropic from '@anthropic-ai/sdk'; import { logger } from '../config/logger'; import { env } from '../config/environment'; import { ToolRegistryManager } from '../tools/registry'; export class AIService { client; personality; toolRegistry; constructor(personality) { this.client = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY, }); this.personality = personality; // Initialize with only enabled tools from personality config const enabledTools = personality.tools ? personality.tools.filter(tool => tool.enabled).map(tool => tool.name) : []; this.toolRegistry = new ToolRegistryManager(enabledTools); } async filterContent(content) { const bannedTerms = this.personality.contentFilters.bannedTerms; const hasBasicFilter = !bannedTerms.some(term => content.toLowerCase().includes(term.toLowerCase())); if (!hasBasicFilter) return false; // Apply custom filters if any if (this.personality.contentFilters.customFilters) { return this.personality.contentFilters.customFilters.every(filter => filter(content)); } return true; } async generateTweet() { let attempts = 0; const maxAttempts = 10; while (attempts < maxAttempts) { const tweet = await this.generateRawTweet(); if (await this.filterContent(tweet)) { return tweet; } attempts++; } throw new Error('Failed to generate appropriate tweet after max attempts'); } async generateRawTweet() { try { logger.info('Generating tweet with Claude...', { functionCalling: this.personality.capabilities?.functionCalling || false, availableTools: this.personality.capabilities?.functionCalling ? this.toolRegistry.getAvailableTools().length : 0 }); let validTweet = false; let tweet = ''; let attempts = 0; while (!validTweet && attempts < 10) { attempts++; // Select tweet type based on configured probabilities const tweetType = this.selectTweetType(); const maxTokens = Math.min(tweetType.maxCharacters, this.personality.communicationStyle.maxTokens); // Prepare tools if function calling is enabled const tools = this.personality.capabilities?.functionCalling ? this.prepareToolsForClaude() : undefined; const messages = [ { role: 'user', content: tweetType.instruction, }, ]; // Add context from recent tool executions if available if (this.personality.capabilities?.functionCalling) { const context = await this.gatherContextualInformation(); if (context) { messages.unshift({ role: 'user', content: `Context for content generation: ${context}` }); } } const requestParams = { model: 'claude-3-sonnet-20240229', max_tokens: maxTokens, temperature: this.personality.communicationStyle.temperature, system: this.personality.systemPrompt, messages, }; if (tools && tools.length > 0) { requestParams.tools = tools; requestParams.tool_choice = { type: 'auto' }; } const response = await this.client.messages.create(requestParams); // Handle tool calls if present if (response.content.some(content => content.type === 'tool_use')) { const toolResults = await this.handleToolCalls(response.content); // Continue conversation with tool results messages.push({ role: 'assistant', content: response.content }); for (const toolResult of toolResults) { messages.push({ role: 'user', content: [{ type: 'tool_result', tool_use_id: toolResult.id, content: JSON.stringify(toolResult.result) }] }); } // Get final response after tool execution const finalResponse = await this.client.messages.create({ ...requestParams, messages, tools: undefined, // Don't allow more tool calls tool_choice: undefined }); const textContent = finalResponse.content.find(c => c.type === 'text'); if (textContent) { tweet = textContent.text; } } else { // Regular text response const content = response.content[0]; if (content.type === 'text') { tweet = content.text; } else { throw new Error('Unexpected response format from Claude'); } } if (tweet.length <= tweetType.maxCharacters) { validTweet = true; } else { logger.warn('Generated tweet exceeds character limit, retrying...', { length: tweet.length, maxCharacters: tweetType.maxCharacters, attempt: attempts, content: tweet }); } } if (!validTweet) { throw new Error('Failed to generate valid tweet within character limit after 10 attempts'); } logger.info('Tweet generated successfully', { tweet, length: tweet.length, personality: this.personality.name, usedTools: this.personality.capabilities?.functionCalling }); return tweet; } catch (error) { logger.error('Failed to generate tweet', { error }); throw error; } } selectTweetType() { const random = Math.random(); let cumulativeProbability = 0; for (const tweetType of this.personality.communicationStyle.tweetTypes || []) { cumulativeProbability += tweetType.probability; if (random <= cumulativeProbability) { return tweetType; } } // Fallback to first type if probabilities don't sum to 1 return this.personality.communicationStyle.tweetTypes?.[0] || { type: 'medium', probability: 1, maxCharacters: 200, instruction: 'Generate a thoughtful tweet' }; } prepareToolsForClaude() { if (!this.personality.capabilities?.functionCalling) { return []; } const availableTools = this.toolRegistry.getAvailableTools(); const enabledTools = this.personality.tools?.filter(tool => tool.enabled) || []; return enabledTools .filter(tool => availableTools.includes(tool.name)) .map(tool => ({ name: tool.name, description: tool.description, input_schema: tool.parameters })); } async handleToolCalls(content) { const toolCalls = content.filter(c => c.type === 'tool_use'); const results = []; for (const toolCall of toolCalls) { try { logger.info('Executing tool', { toolName: toolCall.name, toolId: toolCall.id, input: toolCall.input }); const result = await this.toolRegistry.executeTool(toolCall.name, toolCall.input); results.push({ id: toolCall.id, name: toolCall.name, result, success: true }); } catch (error) { logger.error('Tool execution failed', { toolName: toolCall.name, toolId: toolCall.id, error: error instanceof Error ? error.message : error }); results.push({ id: toolCall.id, name: toolCall.name, result: { error: error instanceof Error ? error.message : 'Unknown error' }, success: false }); } } return results; } async gatherContextualInformation() { if (!this.personality.capabilities?.functionCalling) { return null; } try { const context = []; // Get recent performance insights if available if (this.personality.capabilities?.dataAnalysis) { context.push('Generate content based on what has performed well historically'); } // Add performance context if analytics are enabled if (this.personality.capabilities?.dataAnalysis) { context.push('Focus on content that has performed well in the past'); } return context.length > 0 ? context.join('. ') : null; } catch (error) { logger.debug('Failed to gather contextual information', { error }); return null; } } // New method to generate content with specific tool usage async generateContentWithTools(prompt, tools = []) { if (!this.personality.capabilities?.functionCalling) { throw new Error('Function calling is not enabled for this personality'); } try { logger.info('Generating content with specific tools', { tools, prompt: prompt.substring(0, 100) }); // Filter to only requested tools const availableTools = (this.personality.tools || []) .filter(tool => tool.enabled && tools.includes(tool.name)) .map(tool => ({ name: tool.name, description: tool.description, input_schema: tool.parameters })); const response = await this.client.messages.create({ model: 'claude-3-sonnet-20240229', max_tokens: this.personality.communicationStyle.maxTokens, temperature: this.personality.communicationStyle.temperature, system: this.personality.systemPrompt, messages: [ { role: 'user', content: prompt, }, ], tools: availableTools, tool_choice: { type: 'auto' } }); let finalContent = ''; const toolResults = []; // Handle tool calls if (response.content.some(content => content.type === 'tool_use')) { const results = await this.handleToolCalls(response.content); toolResults.push(...results); // Get final response after tool execution const messages = [ { role: 'user', content: prompt, }, { role: 'assistant', content: response.content } ]; for (const toolResult of results) { messages.push({ role: 'user', content: [{ type: 'text', text: `Tool result for ${toolResult.id}: ${JSON.stringify(toolResult.result)}` }] }); } const finalResponse = await this.client.messages.create({ model: 'claude-3-sonnet-20240229', max_tokens: this.personality.communicationStyle.maxTokens, temperature: this.personality.communicationStyle.temperature, system: this.personality.systemPrompt, messages: messages }); const textContent = finalResponse.content.find(c => c.type === 'text'); if (textContent) { finalContent = textContent.text; } } else { // No tools used, just return text const textContent = response.content.find(c => c.type === 'text'); if (textContent) { finalContent = textContent.text; } } return { content: finalContent, toolsUsed: toolResults, timestamp: new Date().toISOString() }; } catch (error) { logger.error('Failed to generate content with tools', { error, tools }); throw error; } } // Get tool usage statistics getToolUsageStats() { return this.toolRegistry.getAllStats(); } // Enable/disable specific tools configureTool(toolName, enabled) { if (enabled) { this.toolRegistry.enableTool(toolName); } else { this.toolRegistry.disableTool(toolName); } // Update personality config const toolConfig = this.personality.tools?.find(t => t.name === toolName); if (toolConfig) { toolConfig.enabled = enabled; } } }