mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-10 11:41:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b460ded5e0 | ||
|
|
a8967ed200 |
No files matched your search
@@ -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]"
|
||||
|
||||
|
||||
@@ -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]" \
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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 @@
|
||||
__version__ = "2.6.4"
|
||||
__version__ = "2.6.5"
|
||||
Reference in new issue
Block a user