@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
JavaScript
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;
}
}
}