Compare commits

...
2 Commits
Author SHA1 Message Date
Mervin Praison b460ded5e0 Release v2.6.5 2025-12-28 23:02:27 +00:00
Mervin Praison a8967ed200 Release v1.3.1 - add Router class with simplified keyword/pattern-based routing and agent session management
- Bump praisonai-ts version from 1.3.0 to 1.3.1
- Add Router class with simplified keyword/pattern-based routing API (5-line usage)
- Export Router, RouterAgent, createRouter, routeConditions from agent module
- Add SimpleRouterConfig and SimpleRouteConfig types for simplified configuration
- Support both Agent and EnhancedAgent types in router (AnyAgent union type)
- Add Router.chat() method for direct routing
2025-12-28 22:53:04 +00:00
17 changed files with 955 additions and 37 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.6.4" \
"praisonai>=2.6.5" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.6.4" \
"praisonai>=2.6.5" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.6.4" \
"praisonai>=2.6.5" \
"praisonai[ui]" \
"praisonai[crewai]"
@@ -0,0 +1,157 @@
/**
* Test file for simplified TS SDK APIs
* Run with: npx tsx examples/simplified-api-test.ts
*/
import { Agent, db, Router, PlanningAgent, TaskAgent, AgentTelemetry } from '../src';
async function testSimpleAgent() {
console.log('\n=== Test 1: Simple Agent ===');
const agent = new Agent({
instructions: 'You are a helpful assistant. Be brief.',
verbose: false
});
const response = await agent.chat('What is 2+2? Answer in one word.');
console.log('Response:', response.slice(0, 100));
console.log('✅ Simple Agent works');
}
async function testAgentWithTools() {
console.log('\n=== Test 2: Agent with Tools ===');
const getWeather = (city: string) => `Weather in ${city}: 22°C, sunny`;
const calculate = (expr: string) => {
try {
return String(eval(expr));
} catch {
return 'Error';
}
};
const agent = new Agent({
instructions: 'You help with weather and calculations. Be brief.',
tools: [getWeather, calculate],
verbose: false
});
const response = await agent.chat('What is 5 + 3?');
console.log('Response:', response.slice(0, 100));
console.log('✅ Agent with Tools works');
}
async function testAgentWithPersistence() {
console.log('\n=== Test 3: Agent with Persistence (Memory) ===');
const agent = new Agent({
instructions: 'You remember conversations. Be brief.',
db: db('memory:'), // In-memory for testing
sessionId: 'test-session-123',
verbose: false
});
await agent.chat('My name is Alice.');
const response = await agent.chat('What is my name?');
console.log('Response:', response.slice(0, 100));
console.log('History length:', agent.getHistory().length);
console.log('✅ Agent with Persistence works');
}
async function testRouter() {
console.log('\n=== Test 4: Simplified Router ===');
const mathAgent = new Agent({
instructions: 'You are a math expert. Answer math questions briefly.',
verbose: false
});
const codeAgent = new Agent({
instructions: 'You are a coding expert. Answer coding questions briefly.',
verbose: false
});
const router = new Router({
math: { agent: mathAgent, keywords: ['calculate', 'math', 'sum', '+', '-', '*', '/'] },
code: { agent: codeAgent, keywords: ['code', 'program', 'function', 'javascript'] }
});
const response = await router.chat('Calculate 10 + 5');
console.log('Response:', response.slice(0, 100));
console.log('✅ Simplified Router works');
}
async function testPlanningAgent() {
console.log('\n=== Test 5: Planning Agent ===');
const agent = new PlanningAgent({
verbose: false,
maxSteps: 3 // Limit steps for testing
});
const plan = await agent.createPlan('Make a cup of tea');
console.log('Plan created with', plan.steps.length, 'steps');
console.log('First step:', plan.steps[0]?.description || 'N/A');
console.log('✅ Planning Agent works');
}
async function testTaskAgent() {
console.log('\n=== Test 6: Task Agent ===');
const agent = new TaskAgent({ verbose: false });
agent.addTask('Fix critical bug', 'high');
agent.addTask('Write documentation', 'medium');
agent.addTask('Review PR', 'low');
console.log('Pending tasks:', agent.getPendingTasks().length);
agent.completeTask('bug');
console.log('After completing bug task:', agent.getPendingTasks().length);
console.log('Progress:', agent.getProgress().percentage + '%');
console.log('✅ Task Agent works');
}
async function testAgentTelemetry() {
console.log('\n=== Test 7: Agent Telemetry ===');
const agent = new Agent({
instructions: 'Be brief.',
verbose: false
});
const telemetry = new AgentTelemetry('TestAgent');
await telemetry.trackChat(async () => {
return await agent.chat('Hello!');
});
await telemetry.trackChat(async () => {
return await agent.chat('How are you?');
});
const stats = telemetry.getStats();
console.log('Total chats:', stats.totalChats);
console.log('Success rate:', telemetry.getSuccessRate() + '%');
console.log('✅ Agent Telemetry works');
}
async function main() {
console.log('🚀 Testing Simplified TS SDK APIs\n');
try {
await testSimpleAgent();
await testAgentWithTools();
await testAgentWithPersistence();
await testRouter();
await testPlanningAgent();
await testTaskAgent();
await testAgentTelemetry();
console.log('\n✅ All tests passed!');
} catch (error) {
console.error('\n❌ Test failed:', error);
process.exit(1);
}
}
main();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "praisonai",
"version": "1.3.0",
"version": "1.3.2",
"description": "PraisonAI TypeScript AI Agents Framework - Node.js, npm, and Javascript AI Agents Framework",
"main": "dist/index.js",
"types": "dist/index.d.ts",
+5
View File
@@ -4,6 +4,7 @@
* The primary exports are:
* - Agent: Single agent with instructions, tools, and optional persistence
* - Agents: Multi-agent orchestration (alias for PraisonAIAgents)
* - Router: Simplified keyword/pattern-based routing
* - Workflow: Step-based workflow execution (from workflows module)
*/
@@ -11,6 +12,10 @@
export { Agent, PraisonAIAgents, Agents } from './simple';
export type { SimpleAgentConfig, PraisonAIAgentsConfig } from './simple';
// Router exports
export { Router, RouterAgent, createRouter, routeConditions } from './router';
export type { RouterConfig, RouteConfig, RouteContext, SimpleRouterConfig, SimpleRouteConfig } from './router';
// Task support (for advanced use cases)
export { Task } from './types';
export type { TaskConfig, AgentConfig as TaskAgentConfig } from './types';
+128 -7
View File
@@ -1,15 +1,38 @@
/**
* Router Agent - Route requests to specialized agents
*
* @example Simple usage (5 lines)
* ```typescript
* import { Agent, Router } from 'praisonai';
*
* const router = new Router({
* math: { agent: new Agent({ instructions: 'Math expert' }), keywords: ['calculate', 'math'] },
* code: { agent: new Agent({ instructions: 'Code expert' }), keywords: ['code', 'program'] }
* });
* await router.chat('Calculate 2+2'); // Routes to math agent
* ```
*/
import type { EnhancedAgent } from './enhanced';
import { Agent } from './simple';
// Support both Agent and EnhancedAgent
type AnyAgent = Agent | EnhancedAgent;
export interface RouteConfig {
agent: EnhancedAgent;
agent: AnyAgent;
condition: (input: string, context?: RouteContext) => boolean | Promise<boolean>;
priority?: number;
}
/** Simplified route definition */
export interface SimpleRouteConfig {
agent: AnyAgent;
keywords?: string[];
pattern?: RegExp;
priority?: number;
}
export interface RouteContext {
history?: string[];
metadata?: Record<string, any>;
@@ -18,17 +41,20 @@ export interface RouteContext {
export interface RouterConfig {
name?: string;
routes: RouteConfig[];
defaultAgent?: EnhancedAgent;
defaultAgent?: AnyAgent;
verbose?: boolean;
}
/** Simplified router config - just a map of route name to config */
export type SimpleRouterConfig = Record<string, SimpleRouteConfig>;
/**
* Router Agent - Routes requests to the most appropriate agent
*/
export class RouterAgent {
readonly name: string;
private routes: RouteConfig[];
private defaultAgent?: EnhancedAgent;
private defaultAgent?: AnyAgent;
private verbose: boolean;
constructor(config: RouterConfig) {
@@ -41,7 +67,7 @@ export class RouterAgent {
/**
* Route a request to the appropriate agent
*/
async route(input: string, context?: RouteContext): Promise<{ agent: EnhancedAgent; response: string } | null> {
async route(input: string, context?: RouteContext): Promise<{ agent: AnyAgent; response: string } | null> {
for (const route of this.routes) {
const matches = await route.condition(input, context);
if (matches) {
@@ -49,7 +75,9 @@ export class RouterAgent {
console.log(`[Router] Routing to: ${route.agent.name}`);
}
const response = await route.agent.chat(input);
return { agent: route.agent, response: response.text };
// Handle both Agent (returns string) and EnhancedAgent (returns ChatResult)
const responseText = typeof response === 'string' ? response : response.text;
return { agent: route.agent, response: responseText };
}
}
@@ -58,12 +86,24 @@ export class RouterAgent {
console.log(`[Router] Using default agent: ${this.defaultAgent.name}`);
}
const response = await this.defaultAgent.chat(input);
return { agent: this.defaultAgent, response: response.text };
const responseText = typeof response === 'string' ? response : response.text;
return { agent: this.defaultAgent, response: responseText };
}
return null;
}
/**
* Simplified chat method - routes and returns just the response
*/
async chat(input: string, context?: RouteContext): Promise<string> {
const result = await this.route(input, context);
if (!result) {
throw new Error('No matching route found and no default agent configured');
}
return result.response;
}
/**
* Add a route
*/
@@ -139,8 +179,89 @@ export const routeConditions = {
};
/**
* Create a router agent
* Create a router agent (legacy API)
*/
export function createRouter(config: RouterConfig): RouterAgent {
return new RouterAgent(config);
}
/**
* Simplified Router class - uses keyword/pattern-based routing
*
* @example Simple usage (5 lines)
* ```typescript
* import { Agent, Router } from 'praisonai';
*
* const router = new Router({
* math: { agent: new Agent({ instructions: 'Math expert' }), keywords: ['calculate', 'math'] },
* code: { agent: new Agent({ instructions: 'Code expert' }), keywords: ['code', 'program'] }
* });
* await router.chat('Calculate 2+2'); // Routes to math agent
* ```
*/
export class Router {
private routerAgent: RouterAgent;
private agentMap: Map<string, AnyAgent> = new Map();
constructor(config: SimpleRouterConfig, options?: { default?: string; verbose?: boolean }) {
const routes: RouteConfig[] = [];
let defaultAgent: AnyAgent | undefined;
for (const [name, routeConfig] of Object.entries(config)) {
this.agentMap.set(name, routeConfig.agent);
// Build condition from keywords or pattern
let condition: (input: string) => boolean;
if (routeConfig.keywords) {
condition = routeConditions.keywords(routeConfig.keywords);
} else if (routeConfig.pattern) {
condition = routeConditions.pattern(routeConfig.pattern);
} else {
condition = routeConditions.always();
}
routes.push({
agent: routeConfig.agent,
condition,
priority: routeConfig.priority
});
// Set default agent
if (options?.default === name) {
defaultAgent = routeConfig.agent;
}
}
// If no default specified, use first agent
if (!defaultAgent && routes.length > 0) {
defaultAgent = routes[0].agent;
}
this.routerAgent = new RouterAgent({
routes,
defaultAgent,
verbose: options?.verbose
});
}
/**
* Route and get response
*/
async chat(input: string): Promise<string> {
return this.routerAgent.chat(input);
}
/**
* Route and get full result with agent info
*/
async route(input: string): Promise<{ agent: AnyAgent; response: string } | null> {
return this.routerAgent.route(input);
}
/**
* Get agent by name
*/
getAgent(name: string): AnyAgent | undefined {
return this.agentMap.get(name);
}
}
+158 -16
View File
@@ -67,10 +67,22 @@ export interface SimpleAgentConfig {
toolFunctions?: Record<string, Function>;
/** Database adapter for persistence */
db?: DbAdapter;
/** Session ID for conversation persistence */
/** Session ID for conversation persistence (auto-generated if not provided) */
sessionId?: string;
/** Run ID for tracing (auto-generated if not provided) */
runId?: string;
/** Max messages to restore from history (default: 100) */
historyLimit?: number;
/** Auto-restore conversation history from db (default: true) */
autoRestore?: boolean;
/** Auto-persist messages to db (default: true) */
autoPersist?: boolean;
/** Enable caching of responses */
cache?: boolean;
/** Cache TTL in seconds (default: 3600) */
cacheTTL?: number;
/** Enable telemetry tracking (default: false, opt-in) */
telemetry?: boolean;
// Advanced mode (role/goal/backstory) - for compatibility
/** Agent role (advanced mode) */
@@ -96,6 +108,14 @@ export class Agent {
private sessionId: string;
private runId: string;
private messages: Array<{ role: string; content: string | null; tool_calls?: any[]; tool_call_id?: string }> = [];
private dbInitialized: boolean = false;
private historyLimit: number;
private autoRestore: boolean;
private autoPersist: boolean;
private cache: boolean;
private cacheTTL: number;
private responseCache: Map<string, { response: string; timestamp: number }> = new Map();
private telemetryEnabled: boolean;
constructor(config: SimpleAgentConfig) {
// Build instructions from either simple or advanced mode
@@ -120,8 +140,14 @@ export class Agent {
this.stream = config.stream ?? true;
this.tools = config.tools;
this.dbAdapter = config.db;
this.sessionId = config.sessionId || randomUUID();
this.sessionId = config.sessionId || this.generateSessionId();
this.runId = config.runId || randomUUID();
this.historyLimit = config.historyLimit ?? 100;
this.autoRestore = config.autoRestore ?? true;
this.autoPersist = config.autoPersist ?? true;
this.cache = config.cache ?? false;
this.cacheTTL = config.cacheTTL ?? 3600;
this.telemetryEnabled = config.telemetry ?? false;
this.llmService = new OpenAIService(this.llm);
// Configure logging
@@ -180,6 +206,70 @@ export class Agent {
}
}
/**
* Generate a session ID based on current hour and agent name (like Python SDK)
*/
private generateSessionId(): string {
const now = new Date();
const hourStr = now.toISOString().slice(0, 13).replace(/[-T:]/g, '');
const hash = this.name ? this.name.slice(0, 6) : 'agent';
return `${hourStr}-${hash}`;
}
/**
* Initialize DB session - restore history on first chat (lazy)
*/
private async initDbSession(): Promise<void> {
if (this.dbInitialized || !this.dbAdapter || !this.autoRestore) return;
try {
// Restore previous messages from DB
const history = await this.dbAdapter.getMessages(this.sessionId, this.historyLimit);
if (history.length > 0) {
this.messages = history.map(m => ({
role: m.role,
content: m.content
}));
Logger.debug(`Restored ${history.length} messages from session ${this.sessionId}`);
}
} catch (error) {
Logger.warn('Failed to initialize DB session:', error);
}
this.dbInitialized = true;
}
/**
* Get cached response if available and not expired
*/
private getCachedResponse(prompt: string): string | null {
if (!this.cache) return null;
const cacheKey = `${this.sessionId}:${prompt}`;
const cached = this.responseCache.get(cacheKey);
if (cached) {
const age = (Date.now() - cached.timestamp) / 1000;
if (age < this.cacheTTL) {
Logger.debug('Cache hit for prompt');
return cached.response;
}
// Expired, remove it
this.responseCache.delete(cacheKey);
}
return null;
}
/**
* Cache a response
*/
private cacheResponse(prompt: string, response: string): void {
if (!this.cache) return;
const cacheKey = `${this.sessionId}:${prompt}`;
this.responseCache.set(cacheKey, { response, timestamp: Date.now() });
}
private createSystemPrompt(): string {
let prompt = this.instructions;
if (this.markdown) {
@@ -325,27 +415,32 @@ export class Agent {
prompt = prompt.replace('{{previous}}', previousResult);
}
// Initialize messages array
// Initialize messages array with system prompt and conversation history
const messages: Array<any> = [
{ role: 'system', content: this.createSystemPrompt() },
{ role: 'user', content: prompt }
{ role: 'system', content: this.createSystemPrompt() }
];
// Add conversation history (excluding the current prompt which will be added below)
for (const msg of this.messages) {
if (msg.role && msg.content) {
messages.push({ role: msg.role, content: msg.content });
}
}
// Add current user prompt
messages.push({ role: 'user', content: prompt });
let finalResponse = '';
if (this.stream && !this.tools) {
// Use streaming without tools
let fullResponse = '';
await this.llmService.streamText(
prompt,
this.createSystemPrompt(),
// Use streaming with full conversation history
finalResponse = await this.llmService.streamChat(
messages,
0.7,
(token: string) => {
process.stdout.write(token);
fullResponse += token;
}
);
finalResponse = fullResponse;
} else if (this.tools) {
// Use tools (non-streaming for now to simplify implementation)
let continueConversation = true;
@@ -402,18 +497,36 @@ export class Agent {
}
async chat(prompt: string, previousResult?: string): Promise<string> {
// Persist user message if db is configured
if (this.dbAdapter) {
// Lazy init: restore history on first chat (like Python SDK)
await this.initDbSession();
// Check cache first
const cached = this.getCachedResponse(prompt);
if (cached) {
return cached;
}
// Add user message to conversation history
this.messages.push({ role: 'user', content: prompt });
// Persist user message if db is configured and autoPersist is enabled
if (this.dbAdapter && this.autoPersist) {
await this.persistMessage('user', prompt);
}
const response = await this.start(prompt, previousResult);
// Persist assistant response if db is configured
if (this.dbAdapter) {
// Add assistant response to history
this.messages.push({ role: 'assistant', content: response });
// Persist assistant response if db is configured and autoPersist is enabled
if (this.dbAdapter && this.autoPersist) {
await this.persistMessage('assistant', response);
}
// Cache the response
this.cacheResponse(prompt, response);
return response;
}
@@ -464,6 +577,35 @@ export class Agent {
getInstructions(): string {
return this.instructions;
}
/**
* Get conversation history
*/
getHistory(): Array<{ role: string; content: string | null }> {
return [...this.messages];
}
/**
* Clear conversation history (in memory and optionally in DB)
*/
async clearHistory(clearDb: boolean = true): Promise<void> {
this.messages = [];
if (clearDb && this.dbAdapter) {
try {
await this.dbAdapter.deleteMessages(this.sessionId);
Logger.debug(`Cleared history for session ${this.sessionId}`);
} catch (error) {
Logger.warn('Failed to clear DB history:', error);
}
}
}
/**
* Clear response cache
*/
clearCache(): void {
this.responseCache.clear();
}
}
/**
+16 -6
View File
@@ -42,8 +42,8 @@
// ============================================================================
// Agent - Single agent with instructions, tools, and optional persistence
export { Agent, Agents, PraisonAIAgents } from './agent';
export type { SimpleAgentConfig, PraisonAIAgentsConfig } from './agent';
export { Agent, Agents, PraisonAIAgents, Router } from './agent';
export type { SimpleAgentConfig, PraisonAIAgentsConfig, SimpleRouterConfig, SimpleRouteConfig } from './agent';
// Workflow - Step-based workflow execution
export { Workflow, parallel, route, loop, repeat } from './workflows';
@@ -124,8 +124,12 @@ export {
type VectorStoreAdapter as AutoMemoryVectorStore, type KnowledgeBaseAdapter as AutoMemoryKnowledgeBase
} from './memory/auto-memory';
// Export Telemetry
export { TelemetryCollector, getTelemetry, enableTelemetry, disableTelemetry, cleanupTelemetry, type TelemetryEvent, type TelemetryConfig } from './telemetry';
// Export Telemetry (Agent-focused)
export {
TelemetryCollector, AgentTelemetry,
getTelemetry, enableTelemetry, disableTelemetry, cleanupTelemetry, createAgentTelemetry,
type TelemetryEvent, type TelemetryConfig, type AgentStats
} from './telemetry';
// Export AutoAgents
export { AutoAgents, createAutoAgents, type AgentConfig, type TaskConfig, type TeamStructure, type AutoAgentsConfig } from './auto';
@@ -146,8 +150,14 @@ export { PromptExpanderAgent, createPromptExpanderAgent, type PromptExpanderConf
// Export LLMGuardrail
export { LLMGuardrail, createLLMGuardrail, type LLMGuardrailConfig, type LLMGuardrailResult } from './guardrails/llm-guardrail';
// Export Planning
export { Plan, PlanStep, TodoList, TodoItem, PlanStorage, createPlan, createTodoList, createPlanStorage, type PlanConfig, type PlanStepConfig, type TodoItemConfig, type PlanStatus, type TodoStatus } from './planning';
// Export Planning (simplified API)
export {
Plan, PlanStep, TodoList, TodoItem, PlanStorage,
PlanningAgent, TaskAgent,
createPlan, createTodoList, createPlanStorage, createPlanningAgent, createTaskAgent,
type PlanConfig, type PlanStepConfig, type TodoItemConfig, type PlanStatus, type TodoStatus,
type PlanningAgentConfig, type PlanResult
} from './planning';
// Export Cache
export { BaseCache, MemoryCache, FileCache, createMemoryCache, createFileCache, type CacheConfig, type CacheEntry } from './cache';
+41
View File
@@ -322,6 +322,47 @@ export class OpenAIService {
}
}
async streamChat(
messages: ChatMessage[],
temperature: number = 0.7,
onToken: (token: string) => void
): Promise<string> {
await Logger.debug('Starting chat stream with messages...', {
model: this.model,
messageCount: messages.length
});
try {
const openAIMessages = messages.map(convertToOpenAIMessage);
const stream = await this.getClient().then(client =>
client.chat.completions.create({
model: this.model,
temperature,
messages: openAIMessages,
stream: true
})
);
let fullResponse = '';
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.content) {
const token = delta.content;
fullResponse += token;
onToken(token);
}
}
await Logger.debug('Chat stream completed');
return fullResponse;
} catch (error) {
await Logger.error('Error in chat stream', error);
throw error;
}
}
async chatCompletion(
messages: ChatMessage[],
temperature: number = 0.7,
+275
View File
@@ -305,3 +305,278 @@ export function createTodoList(name?: string): TodoList {
export function createPlanStorage(): PlanStorage {
return new PlanStorage();
}
/**
* PlanningAgent - Agent with built-in planning capabilities
*
* @example Simple usage (4 lines)
* ```typescript
* import { PlanningAgent } from 'praisonai';
*
* const agent = new PlanningAgent({ instructions: 'You break down tasks into steps' });
* const result = await agent.planAndExecute('Build a web scraper');
* console.log(result.plan); // The plan that was created
* console.log(result.results); // Results from each step
* ```
*/
export interface PlanningAgentConfig {
instructions?: string;
name?: string;
llm?: string;
verbose?: boolean;
maxSteps?: number;
}
export interface PlanResult {
plan: Plan;
results: string[];
success: boolean;
}
// Import Agent dynamically to avoid circular dependency
let AgentClass: any = null;
async function getAgentClass() {
if (!AgentClass) {
const { Agent } = await import('../agent/simple');
AgentClass = Agent;
}
return AgentClass;
}
export class PlanningAgent {
private config: PlanningAgentConfig;
private agent: any = null;
private currentPlan: Plan | null = null;
constructor(config: PlanningAgentConfig = {}) {
this.config = {
instructions: config.instructions || 'You are a planning agent that breaks down complex tasks into steps.',
name: config.name || 'PlanningAgent',
llm: config.llm,
verbose: config.verbose ?? true,
maxSteps: config.maxSteps ?? 10
};
}
private async getAgent() {
if (!this.agent) {
const Agent = await getAgentClass();
this.agent = new Agent({
name: this.config.name,
instructions: this.config.instructions,
llm: this.config.llm,
verbose: this.config.verbose
});
}
return this.agent;
}
/**
* Create a plan for a task
*/
async createPlan(task: string): Promise<Plan> {
const agent = await this.getAgent();
const planPrompt = `Create a step-by-step plan for: ${task}
Return ONLY a numbered list of steps, one per line. Example:
1. First step
2. Second step
3. Third step`;
const response = await agent.chat(planPrompt);
// Parse the response into steps
const plan = new Plan({ name: task });
const lines = response.split('\n').filter((l: string) => l.trim());
for (const line of lines) {
// Extract step description (remove numbering)
const match = line.match(/^\d+[\.\)]\s*(.+)/);
if (match) {
plan.addStep(new PlanStep({ description: match[1].trim() }));
}
}
this.currentPlan = plan;
return plan;
}
/**
* Execute a single step
*/
async executeStep(step: PlanStep): Promise<string> {
const agent = await this.getAgent();
step.start();
try {
const response = await agent.chat(`Execute this step: ${step.description}`);
step.complete();
return response;
} catch (error) {
step.fail();
throw error;
}
}
/**
* Plan and execute a task in one call
*/
async planAndExecute(task: string): Promise<PlanResult> {
const plan = await this.createPlan(task);
const results: string[] = [];
let success = true;
plan.start();
for (const step of plan.steps.slice(0, this.config.maxSteps)) {
try {
if (this.config.verbose) {
console.log(`[${plan.getProgress().percentage}%] Executing: ${step.description}`);
}
const result = await this.executeStep(step);
results.push(result);
} catch (error) {
success = false;
results.push(`Error: ${error}`);
break;
}
}
if (success) {
plan.complete();
}
return { plan, results, success };
}
/**
* Get the current plan
*/
getPlan(): Plan | null {
return this.currentPlan;
}
/**
* Simple chat (without planning)
*/
async chat(message: string): Promise<string> {
const agent = await this.getAgent();
return agent.chat(message);
}
}
/**
* Create a planning agent
*/
export function createPlanningAgent(config?: PlanningAgentConfig): PlanningAgent {
return new PlanningAgent(config);
}
/**
* TaskAgent - Agent with built-in todo list management
*
* @example Simple usage (4 lines)
* ```typescript
* import { TaskAgent } from 'praisonai';
*
* const agent = new TaskAgent();
* await agent.addTask('Fix critical bug', 'high');
* await agent.addTask('Write docs', 'medium');
* console.log(agent.getPendingTasks());
* ```
*/
export class TaskAgent {
private todos: TodoList;
private agent: any = null;
private config: { name?: string; llm?: string; verbose?: boolean };
constructor(config?: { name?: string; llm?: string; verbose?: boolean }) {
this.todos = new TodoList('Agent Tasks');
this.config = config || {};
}
private async getAgent() {
if (!this.agent) {
const Agent = await getAgentClass();
this.agent = new Agent({
name: this.config.name || 'TaskAgent',
instructions: 'You are a task management assistant.',
llm: this.config.llm,
verbose: this.config.verbose ?? false
});
}
return this.agent;
}
/**
* Add a task
*/
addTask(content: string, priority: 'low' | 'medium' | 'high' = 'medium'): TodoItem {
const item = new TodoItem({ content, priority });
this.todos.add(item);
return item;
}
/**
* Complete a task by content (partial match)
*/
completeTask(contentMatch: string): boolean {
const item = this.todos.items.find(t =>
t.content.toLowerCase().includes(contentMatch.toLowerCase()) && t.status !== 'completed'
);
if (item) {
item.complete();
return true;
}
return false;
}
/**
* Get pending tasks
*/
getPendingTasks(): TodoItem[] {
return this.todos.getPending();
}
/**
* Get all tasks
*/
getAllTasks(): TodoItem[] {
return [...this.todos.items];
}
/**
* Get progress
*/
getProgress(): { completed: number; total: number; percentage: number } {
return this.todos.getProgress();
}
/**
* Clear all tasks
*/
clearTasks(): void {
this.todos.clear();
}
/**
* Chat with AI about tasks
*/
async chat(message: string): Promise<string> {
const agent = await this.getAgent();
const context = `Current tasks:\n${this.todos.items.map(t =>
`- [${t.status}] ${t.content} (${t.priority})`
).join('\n') || 'No tasks'}`;
return agent.chat(`${context}\n\nUser: ${message}`);
}
}
/**
* Create a task agent
*/
export function createTaskAgent(config?: { name?: string; llm?: string; verbose?: boolean }): TaskAgent {
return new TaskAgent(config);
}
+151
View File
@@ -225,3 +225,154 @@ export function cleanupTelemetry(): void {
globalTelemetry.cleanup();
}
}
/**
* AgentTelemetry - Agent-focused telemetry wrapper
*
* @example Simple usage (3 lines)
* ```typescript
* import { Agent } from 'praisonai';
*
* // Enable telemetry on agent
* const agent = new Agent({
* instructions: 'You are helpful',
* telemetry: true // Opt-in telemetry
* });
* await agent.chat('Hello!'); // Automatically tracked
* ```
*
* @example Manual tracking
* ```typescript
* import { AgentTelemetry } from 'praisonai';
*
* const telemetry = new AgentTelemetry('MyAgent');
* const result = await telemetry.trackChat(async () => {
* return await agent.chat('Hello!');
* });
* console.log(telemetry.getStats());
* ```
*/
export interface AgentStats {
totalChats: number;
successfulChats: number;
failedChats: number;
totalDuration: number;
avgDuration: number;
totalTokens: number;
toolCalls: number;
}
export class AgentTelemetry {
private agentName: string;
private collector: TelemetryCollector;
private stats: AgentStats = {
totalChats: 0,
successfulChats: 0,
failedChats: 0,
totalDuration: 0,
avgDuration: 0,
totalTokens: 0,
toolCalls: 0
};
constructor(agentName: string, config?: TelemetryConfig) {
this.agentName = agentName;
this.collector = config ? new TelemetryCollector(config) : getTelemetry();
}
/**
* Track a chat execution
*/
async trackChat<T>(fn: () => Promise<T>): Promise<T> {
const startTime = Date.now();
this.stats.totalChats++;
try {
const result = await fn();
const duration = Date.now() - startTime;
this.stats.successfulChats++;
this.stats.totalDuration += duration;
this.stats.avgDuration = this.stats.totalDuration / this.stats.totalChats;
this.collector.trackAgentExecution(this.agentName, duration, true);
return result;
} catch (error) {
const duration = Date.now() - startTime;
this.stats.failedChats++;
this.stats.totalDuration += duration;
this.stats.avgDuration = this.stats.totalDuration / this.stats.totalChats;
this.collector.trackAgentExecution(this.agentName, duration, false);
this.collector.trackError(String(error), { agent: this.agentName });
throw error;
}
}
/**
* Track a tool call
*/
trackToolCall(toolName: string, duration: number, success: boolean): void {
this.stats.toolCalls++;
this.collector.trackToolCall(toolName, duration, success);
}
/**
* Track token usage
*/
trackTokens(tokens: number): void {
this.stats.totalTokens += tokens;
}
/**
* Get agent statistics
*/
getStats(): AgentStats {
return { ...this.stats };
}
/**
* Reset statistics
*/
resetStats(): void {
this.stats = {
totalChats: 0,
successfulChats: 0,
failedChats: 0,
totalDuration: 0,
avgDuration: 0,
totalTokens: 0,
toolCalls: 0
};
}
/**
* Get success rate
*/
getSuccessRate(): number {
if (this.stats.totalChats === 0) return 0;
return (this.stats.successfulChats / this.stats.totalChats) * 100;
}
/**
* Print summary
*/
printSummary(): void {
console.log(`\n📊 Agent Telemetry: ${this.agentName}`);
console.log(` Total chats: ${this.stats.totalChats}`);
console.log(` Success rate: ${this.getSuccessRate().toFixed(1)}%`);
console.log(` Avg duration: ${this.stats.avgDuration.toFixed(0)}ms`);
console.log(` Tool calls: ${this.stats.toolCalls}`);
console.log(` Total tokens: ${this.stats.totalTokens}`);
}
}
/**
* Create agent telemetry
*/
export function createAgentTelemetry(agentName: string, config?: TelemetryConfig): AgentTelemetry {
return new AgentTelemetry(agentName, config);
}
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.6.4.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.6.4.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.6.5.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.6.5.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
@@ -401,6 +401,11 @@ Built-in tools include: internet_search, calculator, file operations, etc.
else:
i += 1
from pathlib import Path
# Check if tools.py exists in current directory
cwd_tools_py = Path.cwd() / "tools.py"
sources = {
"built_in": "praisonaiagents.tools.TOOL_MAPPINGS",
"package_discovery": ["praisonai_tools"],
@@ -408,6 +413,7 @@ Built-in tools include: internet_search, calculator, file operations, etc.
"~/.praison/tools",
"~/.config/praison/tools",
],
"cwd_tools_py": str(cwd_tools_py) if cwd_tools_py.exists() else "(not found: ./tools.py)",
}
if template_name:
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.6.4 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.6.5 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
@@ -283,6 +283,7 @@ def create_tool_registry_with_overrides(
2. Override directories (explicit CLI --tools-dir)
3. Template tools_sources (from TEMPLATE.yaml)
4. Template-local tools.py
4.5. Current working directory tools.py (./tools.py)
5. Default custom dirs (~/.praison/tools, etc.)
6. Package discovery (praisonai-tools if installed)
7. Built-in tools
@@ -329,6 +330,15 @@ def create_tool_registry_with_overrides(
except Exception:
pass
# 4.5. Current working directory tools.py (if exists)
cwd_tools_py = Path.cwd() / "tools.py"
if cwd_tools_py.exists():
try:
tools = loader.load_from_file(str(cwd_tools_py))
registry.update(tools)
except Exception:
pass
# 4. Template-local tools.py
if template_dir:
tools_py = Path(template_dir) / "tools.py"
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.6.4"
__version__ = "2.6.5"