diff --git a/.changeset/rude-ducks-invent.md b/.changeset/rude-ducks-invent.md new file mode 100644 index 0000000000000000000000000000000000000000..5dc1712990814f1677e8268b9043998fa5703bc6 --- /dev/null +++ b/.changeset/rude-ducks-invent.md @@ -0,0 +1,5 @@ +--- +"llamaindex": patch +--- + +feat: add verbose mode to Agent diff --git a/examples/.env.example b/examples/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..a86a908b7a24ca70e892348095fb4dad1d4b5147 --- /dev/null +++ b/examples/.env.example @@ -0,0 +1 @@ +DEBUG=llamaindex diff --git a/examples/agent/openai.ts b/examples/agent/openai.ts index 857008054ebb31b60fa0426d6e5abd7c00a5c33d..67d0d4b556f5a5b95b6c0b8d10d2a35c1075c846 100644 --- a/examples/agent/openai.ts +++ b/examples/agent/openai.ts @@ -53,7 +53,7 @@ async function main() { message: "How much is 5 + 5? then divide by 2", }); - console.log(String(response)); + console.log(response.response.message); } void main().then(() => { diff --git a/packages/core/src/Settings.ts b/packages/core/src/Settings.ts index 7e9f8eb90659dee2e3544bbf7d173371c78f3e6c..6852fffcc0d4bc4af7fc63e2de43056b6851fa5a 100644 --- a/packages/core/src/Settings.ts +++ b/packages/core/src/Settings.ts @@ -55,9 +55,9 @@ class GlobalSettings implements Config { get debug() { const debug = getEnv("DEBUG"); return ( - getEnv("NODE_ENV") === "development" && - Boolean(debug) && - debug?.includes("llamaindex") + (Boolean(debug) && debug?.includes("llamaindex")) || + debug === "*" || + debug === "true" ); } diff --git a/packages/core/src/agent/anthropic.ts b/packages/core/src/agent/anthropic.ts index accc87fcaf55279d1d2c8398d8f973ed13b6f90a..3ab55521ec511190abaa7b24362db5fc0a96bee0 100644 --- a/packages/core/src/agent/anthropic.ts +++ b/packages/core/src/agent/anthropic.ts @@ -49,6 +49,7 @@ export class AnthropicAgent extends AgentRunner<Anthropic> { "tools" in params ? params.tools : params.toolRetriever.retrieve.bind(params.toolRetriever), + verbose: params.verbose ?? false, }); } @@ -94,7 +95,11 @@ export class AnthropicAgent extends AgentRunner<Anthropic> { const targetTool = tools.find( (tool) => tool.metadata.name === toolCall.name, ); - const toolOutput = await callTool(targetTool, toolCall); + const toolOutput = await callTool( + targetTool, + toolCall, + step.context.logger, + ); step.context.store.toolOutputs.push(toolOutput); step.context.store.messages = [ ...step.context.store.messages, diff --git a/packages/core/src/agent/base.ts b/packages/core/src/agent/base.ts index e0e14f4f5ee8de60918c1b30ff3b1ca3de2309a5..aa0a192cb35f94d0416c5725b10701ecda1cacad 100644 --- a/packages/core/src/agent/base.ts +++ b/packages/core/src/agent/base.ts @@ -4,12 +4,14 @@ import { pipeline, randomUUID, } from "@llamaindex/env"; +import { Settings } from "../Settings.js"; import { type ChatEngine, type ChatEngineParamsNonStreaming, type ChatEngineParamsStreaming, } from "../engines/chat/index.js"; import { wrapEventCaller } from "../internal/context/EventCaller.js"; +import { consoleLogger, emptyLogger } from "../internal/logger.js"; import { getCallbackManager } from "../internal/settings/CallbackManager.js"; import { isAsyncIterable } from "../internal/utils.js"; import type { @@ -66,6 +68,7 @@ export function createTaskOutputStream< const enqueueOutput = ( output: TaskStepOutput<Model, Store, AdditionalMessageOptions>, ) => { + context.logger.log("Enqueueing output for step(id, %s).", step.id); taskOutputs.push(output); controller.enqueue(output); }; @@ -75,7 +78,9 @@ export function createTaskOutputStream< }, }); + context.logger.log("Starting step(id, %s).", step.id); await handler(step, enqueueOutput); + context.logger.log("Finished step(id, %s).", step.id); // fixme: support multi-thread when there are multiple outputs // todo: for now we pretend there is only one task output const { isLast, taskStep } = taskOutputs[0]; @@ -87,6 +92,10 @@ export function createTaskOutputStream< toolCallCount: 1, }; if (isLast) { + context.logger.log( + "Final step(id, %s) reached, closing task.", + step.id, + ); getCallbackManager().dispatchEvent("agent-end", { payload: { endStep: step, @@ -125,6 +134,7 @@ export type AgentRunnerParams< tools: | BaseToolWithCall[] | ((query: MessageContent) => Promise<BaseToolWithCall[]>); + verbose: boolean; }; export type AgentParamsBase< @@ -139,6 +149,7 @@ export type AgentParamsBase< llm?: AI; chatHistory?: ChatMessage<AdditionalMessageOptions>[]; systemPrompt?: MessageContent; + verbose?: boolean; }; /** @@ -218,6 +229,7 @@ export abstract class AgentRunner< readonly #systemPrompt: MessageContent | null = null; #chatHistory: ChatMessage<AdditionalMessageOptions>[]; readonly #runner: AgentWorker<AI, Store, AdditionalMessageOptions>; + readonly #verbose: boolean; // create extra store abstract createStore(): Store; @@ -229,14 +241,15 @@ export abstract class AgentRunner< protected constructor( params: AgentRunnerParams<AI, Store, AdditionalMessageOptions>, ) { - const { llm, chatHistory, runner, tools } = params; + const { llm, chatHistory, systemPrompt, runner, tools, verbose } = params; this.#llm = llm; this.#chatHistory = chatHistory; this.#runner = runner; - if (params.systemPrompt) { - this.#systemPrompt = params.systemPrompt; + if (systemPrompt) { + this.#systemPrompt = systemPrompt; } this.#tools = tools; + this.#verbose = verbose; } get llm() { @@ -247,6 +260,10 @@ export abstract class AgentRunner< return this.#chatHistory; } + get verbose(): boolean { + return Settings.debug || this.#verbose; + } + public reset(): void { this.#chatHistory = []; } @@ -270,8 +287,11 @@ export abstract class AgentRunner< return task.context.toolCallCount < MAX_TOOL_CALLS; } - // fixme: this shouldn't be async - async createTask(message: MessageContent, stream: boolean = false) { + createTask( + message: MessageContent, + stream: boolean = false, + verbose: boolean | undefined = undefined, + ) { const initialMessages = [...this.#chatHistory]; if (this.#systemPrompt !== null) { const systemPrompt = this.#systemPrompt; @@ -296,6 +316,13 @@ export abstract class AgentRunner< toolOutputs: [] as ToolOutput[], }, shouldContinue: AgentRunner.shouldContinue, + logger: + // disable verbose if explicitly set to false + verbose === false + ? emptyLogger + : verbose || this.verbose + ? consoleLogger + : emptyLogger, }); } @@ -312,7 +339,7 @@ export abstract class AgentRunner< | AgentChatResponse<AdditionalMessageOptions> | ReadableStream<AgentStreamChatResponse<AdditionalMessageOptions>> > { - const task = await this.createTask(params.message, !!params.stream); + const task = this.createTask(params.message, !!params.stream); const stepOutput = await pipeline( task, async ( diff --git a/packages/core/src/agent/openai.ts b/packages/core/src/agent/openai.ts index f7146665d11e0e5110473467ea919e5dc642267b..fec65b7dd4b23eaa3f00a12466294a46a36c3302 100644 --- a/packages/core/src/agent/openai.ts +++ b/packages/core/src/agent/openai.ts @@ -46,6 +46,7 @@ export class OpenAIAgent extends AgentRunner<OpenAI> { "tools" in params ? params.tools : params.toolRetriever.retrieve.bind(params.toolRetriever), + verbose: params.verbose ?? false, }); } @@ -77,7 +78,11 @@ export class OpenAIAgent extends AgentRunner<OpenAI> { const targetTool = tools.find( (tool) => tool.metadata.name === toolCall.name, ); - const toolOutput = await callTool(targetTool, toolCall); + const toolOutput = await callTool( + targetTool, + toolCall, + step.context.logger, + ); step.context.store.toolOutputs.push(toolOutput); step.context.store.messages = [ ...step.context.store.messages, @@ -154,7 +159,11 @@ export class OpenAIAgent extends AgentRunner<OpenAI> { }, }, ]; - const toolOutput = await callTool(targetTool, toolCall); + const toolOutput = await callTool( + targetTool, + toolCall, + step.context.logger, + ); step.context.store.messages = [ ...step.context.store.messages, { diff --git a/packages/core/src/agent/react.ts b/packages/core/src/agent/react.ts index ebed34fb1e604cd72ab4b36c7273f6f3356b07d4..f1e895d9403c6ec629f520cbd7748cb7a59fcd28 100644 --- a/packages/core/src/agent/react.ts +++ b/packages/core/src/agent/react.ts @@ -354,6 +354,7 @@ export class ReActAgent extends AgentRunner<LLM, ReACTAgentStore> { "tools" in params ? params.tools : params.toolRetriever.retrieve.bind(params.toolRetriever), + verbose: params.verbose ?? false, }); } @@ -387,14 +388,19 @@ export class ReActAgent extends AgentRunner<LLM, ReACTAgentStore> { isLast: type !== "action", }); }); + step.context.logger.log("current reason: %O", reason); step.context.store.reasons = [...step.context.store.reasons, reason]; if (reason.type === "action") { const tool = tools.find((tool) => tool.metadata.name === reason.action); - const toolOutput = await callTool(tool, { - id: randomUUID(), - input: reason.input, - name: reason.action, - }); + const toolOutput = await callTool( + tool, + { + id: randomUUID(), + input: reason.input, + name: reason.action, + }, + step.context.logger, + ); step.context.store.reasons = [ ...step.context.store.reasons, { diff --git a/packages/core/src/agent/types.ts b/packages/core/src/agent/types.ts index b3d48374ccd6f3fb78844d92e4c774c394986500..22d562c1a88a599cb0d05f44b67b7509cf6f7903 100644 --- a/packages/core/src/agent/types.ts +++ b/packages/core/src/agent/types.ts @@ -1,4 +1,5 @@ import { ReadableStream } from "@llamaindex/env"; +import type { Logger } from "../internal/logger.js"; import type { BaseEvent } from "../internal/type.js"; import type { ChatMessage, @@ -32,6 +33,7 @@ export type AgentTaskContext< toolOutputs: ToolOutput[]; messages: ChatMessage<AdditionalMessageOptions>[]; } & Store; + logger: Readonly<Logger>; }; export type TaskStep< diff --git a/packages/core/src/agent/utils.ts b/packages/core/src/agent/utils.ts index df8f6b8cd71f215b870db4f51bd8d00e145cf105..d385016d80aa0be46c311fa7a5b87955fbf5604d 100644 --- a/packages/core/src/agent/utils.ts +++ b/packages/core/src/agent/utils.ts @@ -1,4 +1,5 @@ import { ReadableStream } from "@llamaindex/env"; +import type { Logger } from "../internal/logger.js"; import { getCallbackManager } from "../internal/settings/CallbackManager.js"; import { isAsyncIterable, prettifyError } from "../internal/utils.js"; import type { @@ -13,12 +14,14 @@ import type { BaseTool, JSONObject, JSONValue, ToolOutput } from "../types.js"; export async function callTool( tool: BaseTool | undefined, toolCall: ToolCall | PartialToolCall, + logger: Logger, ): Promise<ToolOutput> { const input: JSONObject = typeof toolCall.input === "string" ? JSON.parse(toolCall.input) : toolCall.input; if (!tool) { + logger.error(`Tool ${toolCall.name} does not exist.`); const output = `Tool ${toolCall.name} does not exist.`; return { tool, @@ -30,6 +33,9 @@ export async function callTool( const call = tool.call; let output: JSONValue; if (!call) { + logger.error( + `Tool ${tool.metadata.name} (remote:${toolCall.name}) does not have a implementation.`, + ); output = `Tool ${tool.metadata.name} (remote:${toolCall.name}) does not have a implementation.`; return { tool, @@ -45,6 +51,10 @@ export async function callTool( }, }); output = await call.call(tool, input); + logger.log( + `Tool ${tool.metadata.name} (remote:${toolCall.name}) succeeded.`, + ); + logger.log(`Output: ${JSON.stringify(output)}`); const toolOutput: ToolOutput = { tool, input, @@ -60,6 +70,9 @@ export async function callTool( return toolOutput; } catch (e) { output = prettifyError(e); + logger.error( + `Tool ${tool.metadata.name} (remote:${toolCall.name}) failed: ${output}`, + ); } return { tool, diff --git a/packages/core/src/engines/chat/types.ts b/packages/core/src/engines/chat/types.ts index d81e0035ab552464ee95f778b9fc86da0ce65831..0b00f1d1dbfa47e33f7c15d26448df8ff26a8535 100644 --- a/packages/core/src/engines/chat/types.ts +++ b/packages/core/src/engines/chat/types.ts @@ -13,6 +13,11 @@ export interface ChatEngineParamsBase { * Optional chat history if you want to customize the chat history. */ chatHistory?: ChatMessage[] | ChatHistory; + /** + * Optional flag to enable verbose mode. + * @default false + */ + verbose?: boolean; } export interface ChatEngineParamsStreaming extends ChatEngineParamsBase { diff --git a/packages/core/src/internal/logger.ts b/packages/core/src/internal/logger.ts new file mode 100644 index 0000000000000000000000000000000000000000..686d7e170f2751c5ef0a022a41e6bd719d3e8de1 --- /dev/null +++ b/packages/core/src/internal/logger.ts @@ -0,0 +1,17 @@ +export type Logger = { + log: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; +}; + +export const emptyLogger: Logger = Object.freeze({ + log: () => {}, + error: () => {}, + warn: () => {}, +}); + +export const consoleLogger: Logger = Object.freeze({ + log: console.log.bind(console), + error: console.error.bind(console), + warn: console.warn.bind(console), +});