Troubleshoots agent behavior, logs LLM interactions, and inspects database state. Use this when responses are unexpected, to understand context the LLM receives, or to diagnose data issues.
Debugging tools help understand what's happening inside agents, what the LLM receives, and what's stored. Essential for developing reliable agent applications.
const myAgent = new Agent(components.agent, {
name: "My Agent",
languageModel: openai.chat("gpt-4o-mini"),
rawRequestResponseHandler: async (ctx, { request, response }) => {
console.log("LLM Request:", JSON.stringify(request, null, 2));
console.log("LLM Response:", JSON.stringify(response, null, 2));
await ctx.runMutation(internal.logging.saveLLMCall, {
request,
response,
timestamp: Date.now(),
});
},
});
See exactly what context the LLM receives:
const myAgent = new Agent(components.agent, {
name: "My Agent",
languageModel: openai.chat("gpt-4o-mini"),
contextHandler: async (ctx, args) => {
console.log("Context Messages:", {
recent: args.recent.length,
search: args.search.length,
input: args.inputMessages.length,
});
args.allMessages.forEach((msg, i) => {
console.log(`Message ${i}:`, {
role: msg.role,
contentLength: typeof msg.content === "string"
? msg.content.length
: JSON.stringify(msg.content).length,
});
});
return args.allMessages;
},
});
Query agent data directly:
export const getThreadMessages = query({
args: { threadId: v.string() },
handler: async (ctx, { threadId }) => {
return await ctx.db
.query(components.agent.tables.messages)
.filter((msg) => msg.threadId === threadId)
.collect();
},
});
Inspect what context would be used:
import { fetchContextWithPrompt } from "@convex-dev/agent";
export const inspectContext = action({
args: { threadId: v.string(), prompt: v.string() },
handler: async (ctx, { threadId, prompt }) => {
const { messages } = await fetchContextWithPrompt(ctx, components.agent, {
threadId,
prompt,
});
return {
contextMessages: messages.length,
messages: messages.map((msg) => ({
role: msg.role,
contentType: typeof msg.content,
})),
};
},
});
Log all tool invocations:
export const myTool = createTool({
description: "My tool",
args: z.object({ query: z.string() }),
handler: async (ctx, { query }): Promise<string> => {
console.log("[TOOL] myTool called with:", query);
const result = await someOperation(query);
console.log("[TOOL] myTool returned:", result);
return result;
},
});
Common circular reference issue:
// WRONG - no return type
export const myFunction = action({
args: { prompt: v.string() },
handler: async (ctx, { prompt }) => {
return await someLogic();
},
});
// CORRECT - explicit return type
export const myFunction = action({
args: { prompt: v.string() },
returns: v.string(),
handler: async (ctx, { prompt }): Promise<string> => {
return await someLogic();
},
});
Debug message ordering:
export const analyzeMessages = query({
args: { threadId: v.string() },
handler: async (ctx, { threadId }) => {
const messages = await listMessages(ctx, components.agent, {
threadId,
paginationOpts: { cursor: null, numItems: 100 },
});
return messages.results.map((msg) => ({
order: msg.order,
stepOrder: msg.stepOrder,
role: msg.message.role,
status: msg.status,
}));
},
});