{"id":"convex-agents","name":"convex-agents","summary":"Convex Agentコンポーネントを用いたAIエージェントの構築(スレッド管理、ツール統合、ストリーミングレスポンス、RAGパターン、ワークフローオーケストレーションなど)","body":"# Convex Agents\n\nBuild persistent, stateful AI agents with Convex including thread management, tool integration, streaming responses, RAG patterns, and workflow orchestration.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https://docs.convex.dev/ai\n- Convex Agent Component: https://www.npmjs.com/package/@convex-dev/agent\n- For broader context: https://docs.convex.dev/llms.txt\n\n## Instructions\n\n### Why Convex for AI Agents\n\n- **Persistent State** - Conversation history survives restarts\n- **Real-time Updates** - Stream responses to clients automatically\n- **Tool Execution** - Run Convex functions as agent tools\n- **Durable Workflows** - Long-running agent tasks with reliability\n- **Built-in RAG** - Vector search for knowledge retrieval\n\n### Setting Up Convex Agent\n\n```bash\nnpm install @convex-dev/agent ai openai\n```\n\n```typescript\n// convex/agent.ts\nimport { Agent } from \"@convex-dev/agent\";\nimport { components } from \"./_generated/api\";\nimport { OpenAI } from \"openai\";\n\nconst openai = new OpenAI();\n\nexport const agent = new Agent(components.agent, {\n  chat: openai.chat,\n  textEmbedding: openai.embeddings,\n});\n```\n\n### Thread Management\n\n```typescript\n// convex/threads.ts\nimport { mutation, query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { agent } from \"./agent\";\n\n// Create a new conversation thread\nexport const createThread = mutation({\n  args: {\n    userId: v.id(\"users\"),\n    title: v.optional(v.string()),\n  },\n  returns: v.id(\"threads\"),\n  handler: async (ctx, args) => {\n    const threadId = await agent.createThread(ctx, {\n      userId: args.userId,\n      metadata: {\n        title: args.title ?? \"New Conversation\",\n        createdAt: Date.now(),\n      },\n    });\n    return threadId;\n  },\n});\n\n// List user's threads\nexport const listThreads = query({\n  args: { userId: v.id(\"users\") },\n  returns: v.array(v.object({\n    _id: v.id(\"threads\"),\n    title: v.string(),\n    lastMessageAt: v.optional(v.number()),\n  })),\n  handler: async (ctx, args) => {\n    return await agent.listThreads(ctx, {\n      userId: args.userId,\n    });\n  },\n});\n\n// Get thread messages\nexport const getMessages = query({\n  args: { threadId: v.id(\"threads\") },\n  returns: v.array(v.object({\n    role: v.string(),\n    content: v.string(),\n    createdAt: v.number(),\n  })),\n  handler: async (ctx, args) => {\n    return await agent.getMessages(ctx, {\n      threadId: args.threadId,\n    });\n  },\n});\n```\n\n### Sending Messages and Streaming Responses\n\n```typescript\n// convex/chat.ts\nimport { action } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { agent } from \"./agent\";\nimport { internal } from \"./_generated/api\";\n\nexport const sendMessage = action({\n  args: {\n    threadId: v.id(\"threads\"),\n    message: v.string(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    // Add user message to thread\n    await ctx.runMutation(internal.chat.addUserMessage, {\n      threadId: args.threadId,\n      content: args.message,\n    });\n\n    // Generate AI response with streaming\n    const response = await agent.chat(ctx, {\n      threadId: args.threadId,\n      messages: [{ role: \"user\", content: args.message }],\n      stream: true,\n      onToken: async (token) => {\n        // Stream tokens to client via mutation\n        await ctx.runMutation(internal.chat.appendToken, {\n          threadId: args.threadId,\n          token,\n        });\n      },\n    });\n\n    // Save complete response\n    await ctx.runMutation(internal.chat.saveResponse, {\n      threadId: args.threadId,\n      content: response.content,\n    });\n\n    return null;\n  },\n});\n```\n\n### Tool Integration\n\nDefine tools that agents can use:\n\n```typescript\n// convex/tools.ts\nimport { tool } from \"@convex-dev/agent\";\nimport { v } from \"convex/values\";\nimport { api } from \"./_generated/api\";\n\n// Tool to search knowledge base\nexport const searchKnowledge = tool({\n  name: \"search_knowledge\",\n  description: \"Search the knowledge base for relevant information\",\n  parameters: v.object({\n    query: v.string(),\n    limit: v.optional(v.number()),\n  }),\n  handler: async (ctx, args) => {\n    const results = await ctx.runQuery(api.knowledge.search, {\n      query: args.query,\n      limit: args.limit ?? 5,\n    });\n    return results;\n  },\n});\n\n// Tool to create a task\nexport const createTask = tool({\n  name: \"create_task\",\n  description: \"Create a new task for the user\",\n  parameters: v.object({\n    title: v.string(),\n    description: v.optional(v.string()),\n    dueDate: v.optional(v.string()),\n  }),\n  handler: async (ctx, args) => {\n    const taskId = await ctx.runMutation(api.tasks.create, {\n      title: args.title,\n      description: args.description,\n      dueDate: args.dueDate ? new Date(args.dueDate).getTime() : undefined,\n    });\n    return { success: true, taskId };\n  },\n});\n\n// Tool to get weather\nexport const getWeather = tool({\n  name: \"get_weather\",\n  description: \"Get current weather for a location\",\n  parameters: v.object({\n    location: v.string(),\n  }),\n  handler: async (ctx, args) => {\n    const response = await fetch(\n      `https://api.weather.com/current?location=${encodeURIComponent(args.location)}`\n    );\n    return await response.json();\n  },\n});\n```\n\n### Agent with Tools\n\n```typescript\n// convex/assistant.ts\nimport { action } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { agent } from \"./agent\";\nimport { searchKnowledge, createTask, getWeather } from \"./tools\";\n\nexport const chat = action({\n  args: {\n    threadId: v.id(\"threads\"),\n    message: v.string(),\n  },\n  returns: v.string(),\n  handler: async (ctx, args) => {\n    const response = await agent.chat(ctx, {\n      threadId: args.threadId,\n      messages: [{ role: \"user\", content: args.message }],\n      tools: [searchKnowledge, createTask, getWeather],\n      systemPrompt: `You are a helpful assistant. You have access to tools to:\n        - Search the knowledge base for information\n        - Create tasks for the user\n        - Get weather information\n        Use these tools when appropriate to help the user.`,\n    });\n\n    return response.content;\n  },\n});\n```\n\n### RAG (Retrieval Augmented Generation)\n\n```typescript\n// convex/knowledge.ts\nimport { mutation, query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { agent } from \"./agent\";\n\n// Add document to knowledge base\nexport const addDocument = mutation({\n  args: {\n    title: v.string(),\n    content: v.string(),\n    metadata: v.optional(v.object({\n      source: v.optional(v.string()),\n      category: v.optional(v.string()),\n    })),\n  },\n  returns: v.id(\"documents\"),\n  handler: async (ctx, args) => {\n    // Generate embedding\n    const embedding = await agent.embed(ctx, args.content);\n\n    return await ctx.db.insert(\"documents\", {\n      title: args.title,\n      content: args.content,\n      embedding,\n      metadata: args.metadata ?? {},\n      createdAt: Date.now(),\n    });\n  },\n});\n\n// Search knowledge base\nexport const search = query({\n  args: {\n    query: v.string(),\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(v.object({\n    _id: v.id(\"documents\"),\n    title: v.string(),\n    content: v.string(),\n    score: v.number(),\n  })),\n  handler: async (ctx, args) => {\n    const results = await agent.search(ctx, {\n      query: args.query,\n      table: \"documents\",\n      limit: args.limit ?? 5,\n    });\n\n    return results.map((r) => ({\n      _id: r._id,\n      title: r.title,\n      content: r.content,\n      score: r._score,\n    }));\n  },\n});\n```\n\n### Workflow Orchestration\n\n```typescript\n// convex/workflows.ts\nimport { action, internalMutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { agent } from \"./agent\";\nimport { internal } from \"./_generated/api\";\n\n// Multi-step research workflow\nexport const researchTopic = action({\n  args: {\n    topic: v.string(),\n    userId: v.id(\"users\"),\n  },\n  returns: v.id(\"research\"),\n  handler: async (ctx, args) => {\n    // Create research record\n    const researchId = await ctx.runMutation(internal.workflows.createResearch, {\n      topic: args.topic,\n      userId: args.userId,\n      status: \"searching\",\n    });\n\n    // Step 1: Search for relevant documents\n    const searchResults = await agent.search(ctx, {\n      query: args.topic,\n      table: \"documents\",\n      limit: 10,\n    });\n\n    await ctx.runMutation(internal.workflows.updateStatus, {\n      researchId,\n      status: \"analyzing\",\n    });\n\n    // Step 2: Analyze and synthesize\n    const analysis = await agent.chat(ctx, {\n      messages: [{\n        role: \"user\",\n        content: `Analyze these sources about \"${args.topic}\" and provide a comprehensive summary:\\n\\n${\n          searchResults.map((r) => r.content).join(\"\\n\\n---\\n\\n\")\n        }`,\n      }],\n      systemPrompt: \"You are a research assistant. Provide thorough, well-cited analysis.\",\n    });\n\n    // Step 3: Generate key insights\n    await ctx.runMutation(internal.workflows.updateStatus, {\n      researchId,\n      status: \"summarizing\",\n    });\n\n    const insights = await agent.chat(ctx, {\n      messages: [{\n        role: \"user\",\n        content: `Based on this analysis, list 5 key insights:\\n\\n${analysis.content}`,\n      }],\n    });\n\n    // Save final results\n    await ctx.runMutation(internal.workflows.completeResearch, {\n      researchId,\n      analysis: analysis.content,\n      insights: insights.content,\n      sources: searchResults.map((r) => r._id),\n    });\n\n    return researchId;\n  },\n});\n```\n\n## Examples\n\n### Complete Chat Application Schema\n\n```typescript\n// convex/schema.ts\nimport { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nexport default defineSchema({\n  threads: defineTable({\n    userId: v.id(\"users\"),\n    title: v.string(),\n    lastMessageAt: v.optional(v.number()),\n    metadata: v.optional(v.any()),\n  }).index(\"by_user\", [\"userId\"]),\n\n  messages: defineTable({\n    threadId: v.id(\"threads\"),\n    role: v.union(v.literal(\"user\"), v.literal(\"assistant\"), v.literal(\"system\")),\n    content: v.string(),\n    toolCalls: v.optional(v.array(v.object({\n      name: v.string(),\n      arguments: v.any(),\n      result: v.optional(v.any()),\n    }))),\n    createdAt: v.number(),\n  }).index(\"by_thread\", [\"threadId\"]),\n\n  documents: defineTable({\n    title: v.string(),\n    content: v.string(),\n    embedding: v.array(v.float64()),\n    metadata: v.object({\n      source: v.optional(v.string()),\n      category: v.optional(v.string()),\n    }),\n    createdAt: v.number(),\n  }).vectorIndex(\"by_embedding\", {\n    vectorField: \"embedding\",\n    dimensions: 1536,\n  }),\n});\n```\n\n### React Chat Component\n\n```typescript\nimport { useQuery, useMutation, useAction } from \"convex/react\";\nimport { api } from \"../convex/_generated/api\";\nimport { useState, useRef, useEffect } from \"react\";\n\nfunction ChatInterface({ threadId }: { threadId: Id<\"threads\"> }) {\n  const messages = useQuery(api.threads.getMessages, { threadId });\n  const sendMessage = useAction(api.chat.sendMessage);\n  const [input, setInput] = useState(\"\");\n  const [sending, setSending] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSend = async (e: React.FormEvent) => {\n    e.preventDefault();\n    if (!input.trim() || sending) return;\n\n    const message = input.trim();\n    setInput(\"\");\n    setSending(true);\n\n    try {\n      await sendMessage({ threadId, message });\n    } finally {\n      setSending(false);\n    }\n  };\n\n  return (\n    <div className=\"chat-container\">\n      <div className=\"messages\">\n        {messages?.map((msg, i) => (\n          <div key={i} className={`message ${msg.role}`}>\n            <strong>{msg.role === \"user\" ? \"You\" : \"Assistant\"}:</strong>\n            <p>{msg.content}</p>\n          </div>\n        ))}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form onSubmit={handleSend} className=\"input-form\">\n        <input\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          disabled={sending}\n        />\n        <button type=\"submit\" disabled={sending || !input.trim()}>\n          {sending ? \"Sending...\" : \"Send\"}\n        </button>\n      </form>\n    </div>\n  );\n}\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Store conversation history in Convex for persistence\n- Use streaming for better user experience with long responses\n- Implement proper error handling for tool failures\n- Use vector indexes for efficient RAG retrieval\n- Rate limit agent interactions to control costs\n- Log tool usage for debugging and analytics\n\n## Common Pitfalls\n\n1. **Not persisting threads** - Conversations lost on refresh\n2. **Blocking on long responses** - Use streaming instead\n3. **Tool errors crashing agents** - Add proper error handling\n4. **Large context windows** - Summarize old messages\n5. **Missing embeddings for RAG** - Generate embeddings on insert\n\n## References\n\n- Convex Documentation: https://docs.convex.dev/\n- Convex LLMs.txt: https://docs.convex.dev/llms.txt\n- Convex AI: https://docs.convex.dev/ai\n- Agent Component: https://www.npmjs.com/package/@convex-dev/agent","author":"@waynesutton","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/waynesutton/convexskills/tree/main/skills/convex-agents","license":"Apache-2.0","category":"document","lang":"en","tokens":3230,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"agents/openai.yaml","size":91,"sha256":"bb57e6929f0916464111ae4a5e0a2ec5d301653b5a3b818a81dc2c0a7deb21c2"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["api.weather.com","docs.convex.dev"]}}