Convex Agent Component

repository·main·Indexed 43 days ago

https://github.com/get-convex/agent

A framework for building AI agents on Convex featuring persistent chat threads, tool calling, streaming, and RAG. It provides abstractions for managing agent workflows, conversation context with hybrid vector/text search, and built-in usage tracking and rate limiting. Version 0.7.3.

Tokens
46.9K
Snippets
128
Records
166
Agent score
55%

What's inside Convex Agent

  1. Overview of Agent Playground

    main

    The Agent Playground is a tool designed for experimenting with @convex-dev/agent. It provides a visual interface to interact with agent threads and test various agent behaviors.

    Key capabilities include:

    • Thread Management: List and browse threads associated with a specific user.
    • Message Inspection: View messages within a thread, including tool call details and message metadata.
    • Context Experimentation: Test contextual message lookup by adjusting different context options.
    • Message Interaction: Send messages to threads with configurable saving options.
    • Secure Communication: Uses API keys to communicate securely with the backend.
  2. Core concepts of the Convex Agent component

    main

    The Convex Agent component provides a framework for building AI agents with persistent state and reliable workflows. Key abstractions include:

    • Agents: An abstraction for using LLMs to represent specific use-cases. They include associated models, prompts, Tool Calls, and defined behaviors relative to other agents, functions, or APIs.
    • Threads: Persistent containers for messages that can be shared across multiple users and agents (including human agents).
    • Workflows: Multi-step operations that allow agents and users to interact durably and reliably over time.
    • Streaming: Efficient text and object streaming using deltas over websockets, allowing async functions to stream updates to all clients without relying on HTTP streaming.
    • Conversation Context: Automatically managed context for LLM calls, featuring built-in hybrid vector/text search for messages within a thread and optional search for messages from other threads belonging to the same user.
    • RAG (Retrieval-Augmented Generation): Support for prompt augmentation via tool calls or upfront prompt augmentation. It integrates with the Convex RAG Component or can be implemented manually.
    • Files: Support for files within thread history, featuring automatic saving to Convex file storage and automatic ref-counting.
    • Usage Tracking: Built-in support for attributing usage per-provider, per-model, per-user, and per-agent, which is useful for billing and monitoring.
    • Rate Limiting: Integration with the Convex Rate Limiter Component to control user interaction rates and prevent exceeding LLM provider limits.
  3. What is the RAG Component?

    main

    The RAG (Retrieval-Augmented Generation) component is a Convex component that enables LLMs to search through custom knowledge bases. It automates the process of breaking data into chunks and generating embeddings for vector-based semantic search.

    Key features include:

    • Namespaces: Isolate search domains for specific users or teams.
    • Semantic Search: Vector-based search using configurable embedding models.
    • Custom Filtering: Efficiently filter vector search results.
    • Chunk Context: Retrieve surrounding chunks for improved context.
    • Importance Weighting: Assign a value from 0 to 1 to documents to influence search results.
    • Chunking Flexibility: Use default chunking or provide your own logic.
  4. Using tools and handling tool calls

    main

    The Agent component automatically manages the tool call lifecycle. When a tool is called, the call and its result are stored as messages in the thread associated with the source message.

    To ensure the Agent automatically processes the tool result and continues generating (re-generating) if necessary, pass stopWhen: stepCountIs(num) to generateText or streamText, where num > 1.

  5. Handle multiple tool calls and automatic denials

    main

    When a model calls multiple tools in a single step, every pending approval must be resolved (either approved or denied) before generation can continue.

    If a new generation attempt is started while approvals are still unresolved, the system will automatically deny the unresolved approvals with the reason "auto-denied: new generation started". This ensures message history integrity by preventing tool calls from existing without results.

  6. Manage message ordering and placement

    main

    Messages in a thread use order and stepOrder (incrementing integers) to maintain sequence.

    • User Messages: Added at the next available order with stepOrder: 0.
    • Assistant/Tool Responses: Appended to the current order with incrementing stepOrder values.
    • Manual Placement: To start a standalone message at the next available order, pass order: "next" to saveMessage. To place it at a specific position, pass a non-negative integer.
    • Context Linking: Use promptMessageId to associate a response with a specific previous message. Note that if the promptMessageId is not the latest message, the LLM context will only include messages up to that ID.
  7. How the `Thread` object works for continuing conversations

    main

    When working within an action, you can use agent.continueThread to obtain a Thread object. This object acts as a specialized wrapper for a specific thread, allowing you to call generation methods without repeatedly passing the threadId in the options.

    Available methods on a Thread object:

    • thread.getMetadata(): Retrieves userId, title, summary, etc.
    • thread.updateMetadata({ patch: { title, summary, userId } }): Updates thread metadata.
    • thread.generateText({ prompt, ... }): Equivalent to agent.generateText(ctx, { threadId }, { prompt, ... }).
    • thread.streamText({ prompt, ... }): Equivalent to agent.streamText(ctx, { threadId }, { prompt, ... }).
    • thread.generateObject({ prompt, ... }): Equivalent to agent.generateObject(ctx, { threadId }, { prompt, ... }).
    • thread.streamObject({ prompt, ... }): Equivalent to agent.streamObject(ctx, { threadId }, { prompt, ... }).
    const { thread } = await agent.continueThread(ctx, { threadId });
    const result = await thread.generateText({ prompt });
  8. Understand the `UIMessage` type

    main

    The Agent component extends the standard AI SDK UIMessage type with additional metadata optimized for rendering agentic workflows.

    Standard AI SDK fields:

    • parts: Array of parts (e.g., text, file, image, toolCall, toolResult).
    • content: String of the message content.
    • role: The role (e.g., user, assistant, system).

    Agent-specific helper fields:

    • key: Unique identifier for the message.
    • order: The message's order in the thread.
    • stepOrder: The step order within a specific message order.
    • status: The message status (e.g., streaming).
    • agentName: The name of the agent that generated the message.
    • text: The text content of the message.
    • _creationTime: Timestamp of the message.

    To use this type, import UIMessage from @convex-dev/agent.

  9. Strategies for deciding between LLM and human responses

    main

    When building agentic workflows, you need a mechanism to decide if a thread should be handled by an LLM or escalated to a human. Common patterns include:

    1. Explicit State: Storing a flag in your database indicating whether the user or LLM is currently assigned to the thread.
    2. LLM Routing: Using a fast, cheap LLM call to classify if a user question requires human intervention.
    3. Vector Search: Using embeddings to compare the user's question against a corpus of questions known to require human handling.
    4. Structured LLM Output: Having the primary LLM generate an object that includes a field indicating if human assistance is needed.
    5. Tool-based Escalation: Providing the LLM with a tool that, when called, signals the need for human input.
  10. Defining tools in the Agent component

    main

    Tools allow an LLM to interact with external services, databases, or APIs. You can provide tools at multiple layers of the Agent lifecycle. Specifying tools at a more specific layer will overwrite defaults following this precedence: args.tools ?? thread.tools ?? agent.options.tools.

    Available injection points:

    • Agent constructor: new Agent(components.agent, { tools: {...} })
    • Creating a thread: createThread(ctx, { tools: {...} })
    • Continuing a thread: continueThread(ctx, { tools: {...} })
    • On thread functions: thread.generateText({ tools: {...} })
    • Outside of a thread: supportAgent.generateText(ctx, {}, { tools: {...} })
  11. Implement Tool-based RAG

    main

    Tool-based RAG allows the LLM to intelligently decide when it needs to search for context or add new information by providing it with a search tool. This is ideal for dynamic knowledge management where the agent needs to be adaptive.

    In this pattern, the message history includes the original prompt, the tool call, and the tool response, allowing the LLM to reference the retrieved information during the conversation.

    searchContext: createTool({
      description: "Search for context related to this user prompt",
      args: z.object({ query: z.string().describe("Describe the context you're looking for") }),
      handler: async (ctx, { query }) => {
        const context = await rag.search(ctx, { namespace: userId, query });
        return context.text;
      },
    }),
  12. Migrate @convex-dev/agent from v0.6.x to v0.7.0

    main

    When upgrading to @convex-dev/agent v0.7, you are moving to AI SDK 7 at runtime. This requires updating Node.js, core dependencies, and provider packages simultaneously to avoid type incompatibilities. Existing messages saved by v0.6 remain readable, but new streams will use the AI SDK 7 UIMessageChunk format.

    Prerequisites

    • Node.js: Version 22 or newer is required.
    • Dependency Management: Avoid using --force during installation, as mixed AI SDK major versions can cause incompatible model, tool, and message types.

    Step 1: Update Dependencies

    Update @convex-dev/agent and the core AI SDK packages:

    pnpm add @convex-dev/agent@^0.7.0 ai@^7.0.0 \
      @ai-sdk/provider@^4.0.0 @ai-sdk/provider-utils@^5.0.0

    Update your official provider packages to v4:

    pnpm add @ai-sdk/openai@^4.0.0 @ai-sdk/anthropic@^4.0.0 \
      @ai-sdk/google@^4.0.0 @ai-sdk/groq@^4.0.0

    Note for RAG users: If using @convex-dev/rag with Agent, ensure you use an AI SDK 7-compatible RAG release (>= 0.8.0.alpha.0).

    Step 2: Rename system to instructions

    While system is temporarily accepted, it is deprecated. Use instructions for top-level model guidance in the Agent constructor and in generateText calls.

    const agent = new Agent(components.agent, {
      languageModel: openai("gpt-5"),
      instructions: "You are a helpful assistant.",
    });
    
    await agent.generateText(ctx, { threadId }, {
      prompt: "Hello",
      instructions: "Answer concisely.",
    });

    Note: Stored system messages are still supported. If you pass system messages within an assembled history, you must set allowSystemInMessages: true.

    Step 3: Update AI SDK Types and Callbacks

    Agent's generation, streaming, tool, usage, warning, and provider types now use AI SDK 7. You must update custom callbacks or wrappers that previously relied on AI SDK 6 types.

    Key changes to implement:

    • Use mediaType for AI SDK image and file parts.
    • Update custom tools and provider options to their AI SDK 7 forms.
    • Review code consuming usage, warnings, response metadata, or step results, as these now follow AI SDK 7 shapes.
    • maxSteps is still supported, and stopWhen remains the preferred AI SDK API.

    Step 4: Verify the Migration

    Run the TypeScript compiler and tests to ensure no type mismatches exist:

    pnpm exec tsc --noEmit
    pnpm test