convex-agents-debugging
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.
When & Why to Use This Skill
Convex Agents Debugging is a specialized toolkit designed to enhance the transparency and reliability of AI agents. It provides developers with essential capabilities to log LLM interactions, inspect database states, and trace tool calls, making it easier to diagnose unexpected behaviors and optimize agent performance through deep visibility into the agent's internal state.
Use Cases
- Troubleshooting unexpected agent responses by logging and analyzing raw LLM request-response pairs in real-time.
- Optimizing LLM context management by inspecting the exact messages, search results, and input history sent to the model.
- Auditing agent tool usage to verify that external functions are being called with the correct arguments and returning expected results.
- Diagnosing data integrity and state issues by querying agent-specific database tables and message threads directly.
- Improving code reliability by identifying and fixing common technical hurdles like circular references or type errors in agent actions.
| name | "Convex Agents Debugging" |
|---|---|
| description | "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." |
Purpose
Debugging tools help understand what's happening inside agents, what the LLM receives, and what's stored. Essential for developing reliable agent applications.
When to Use This Skill
- Agent behavior is unexpected
- LLM responses are off-target
- Investigating why certain context isn't being used
- Understanding message ordering
- Checking file storage and references
- Auditing tool calls and results
- Profiling token usage
Log Raw LLM Requests and Responses
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(),
});
},
});
Log Context Messages
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;
},
});
Inspect Database Tables
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();
},
});
Fetch Context Manually
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,
})),
};
},
});
Trace Tool Calls
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;
},
});
Fix Type Errors
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();
},
});
Analyze Message Structure
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,
}));
},
});
Key Principles
- Log early: Capture data while developing
- Use console for quick checks: Fast iteration
- Save important events: Archive LLM calls for analysis
- Explicit return types: Prevents circular references
- Dashboard inspection: Easiest way to see database state
Next Steps
- See playground for interactive debugging
- See fundamentals for agent setup
- See context for context-aware debugging