> ## Documentation Index
> Fetch the complete documentation index at: https://docs.case.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Document Intelligence Agent

> Build a RAG-powered agent that can store and retrieve information from your documents

## What You'll Build

An intelligent agent that can:

* **Store documents** in an encrypted vault with automatic OCR and embedding generation
* **Answer questions** by retrieving relevant document chunks and synthesizing responses
* **Add knowledge** dynamically as users provide new information
* **Cite sources** with page numbers and document references

## Why RAG?

Large Language Models are powerful, but they can only reason on their training data. RAG solves this by:

1. **Embedding** your documents into a searchable vector space
2. **Retrieving** relevant chunks when a user asks a question
3. **Augmenting** the LLM's context with those chunks
4. **Generating** an accurate, grounded response

With Case.dev, you don't need to manage embeddings, vector databases, or chunking strategies — Vaults handle all of this automatically.

## Architecture

```mermaid theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
flowchart LR
    A["User Query"] --> B["Vault Search<br/>(Semantic)"]
    B --> C["LLM Gateway<br/>+ Context"]
    C --> H["Grounded Response"]
    D["New Document"] --> E["Vault Upload"]
    E --> F["Auto-embed"]
    F --> G["Ready to query"]
    G -.indexed chunks.-> B
```

## Prerequisites

* Case.dev API key ([get one here](https://console.case.dev))
* Node.js 18+ or Python 3.9+
* Vercel AI SDK (optional, for streaming UI)

## Project Setup

## Step 1: Install dependencies

<CodeGroup>
  ```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  # No installation needed — just set your API key
  export CASEDEV_API_KEY="sk_case_YOUR_API_KEY"
  ```

  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  brew tap CaseMark/casedev && brew install casedev
  ```

  ```bash title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  npm install casedev ai zod
  ```

  ```bash title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  pip install casedev
  ```

  ```bash title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  go get github.com/CaseMark/casedev-go
  ```
</CodeGroup>

## Step 2: Set up environment variables

```bash title="Environment" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
CASEDEV_API_KEY=sk_case_your_api_key
```

## Step 3: Create a vault for your knowledge base

<CodeGroup>
  ```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  curl -X POST https://api.case.dev/vault \
    -H "Authorization: Bearer $CASEDEV_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Knowledge Base",
      "description": "Document intelligence agent knowledge store"
    }'
  ```

  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault create \
    --name "Knowledge Base" \
    --description "Document intelligence agent knowledge store"
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import Casedev from 'casedev';

  const client = new Casedev({ apiKey: process.env.CASEDEV_API_KEY });

  // Create a vault to store your knowledge base
  const vault = await client.vault.create({
    name: 'Knowledge Base',
    description: 'Document intelligence agent knowledge store'
  });

  console.log(`Vault created: ${vault.id}`);
  // Save this ID - you'll need it for queries
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import os
  import casedev

  client = casedev.Casedev(api_key=os.environ['CASEDEV_API_KEY'])

  # Create a vault to store your knowledge base
  vault = client.vault.create(
      name='Knowledge Base',
      description='Document intelligence agent knowledge store'
  )

  print(f'Vault created: {vault.id}')
  # Save this ID - you'll need it for queries
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  vault, _ := client.Vault.New(ctx, casedev.VaultNewParams{
  	Name:        casedev.F("Knowledge Base"),
  	Description: casedev.F("Document intelligence agent knowledge store"),
  })
  fmt.Printf("Vault created: %s\n", vault.ID)
  ```
</CodeGroup>

## Core Functions

### 1. Add Documents to Knowledge Base

When a user uploads a document or provides information, store it in the vault:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault upload \
    --id $VAULT_ID \
    --filename "document.pdf" \
    --content-type "application/pdf"
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import Casedev from 'casedev';

  const client = new Casedev({ apiKey: process.env.CASEDEV_API_KEY });
  const VAULT_ID = process.env.VAULT_ID;

  /**
   * Add a document or text to the knowledge base
   */
  async function addToKnowledgeBase(content: string, metadata?: Record<string, string>) {
    // For text content, create a text file
    const blob = new Blob([content], { type: 'text/plain' });
    const filename = `knowledge-${Date.now()}.txt`;
    
    // Get upload URL
    const upload = await client.vault.upload(VAULT_ID, {
      filename,
      contentType: 'text/plain',
      metadata: {
        source: 'user-input',
        timestamp: new Date().toISOString(),
        ...metadata
      }
    });
    
    // Upload content
    await fetch(upload.uploadUrl, {
      method: 'PUT',
      headers: { 'Content-Type': 'text/plain' },
      body: content
    });
    
    // Trigger ingestion (generates embeddings automatically)
    await client.vault.ingest(VAULT_ID, upload.objectId);
    
    return { objectId: upload.objectId, filename };
  }

  /**
   * Add a file (PDF, Word, image) to the knowledge base
   */
  async function addFileToKnowledgeBase(
    file: Buffer, 
    filename: string, 
    contentType: string
  ) {
    const upload = await client.vault.upload(VAULT_ID, {
      filename,
      contentType,
      metadata: {
        source: 'file-upload',
        timestamp: new Date().toISOString()
      }
    });
    
    await fetch(upload.uploadUrl, {
      method: 'PUT',
      headers: { 'Content-Type': contentType },
      body: file
    });
    
    // Ingestion handles OCR (if needed) and embedding generation
    const job = await client.vault.ingest(VAULT_ID, upload.objectId);
    
    return { objectId: upload.objectId, jobId: job.id };
  }
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import os
  import requests
  from datetime import datetime
  import casedev

  client = casedev.Casedev(api_key=os.environ['CASEDEV_API_KEY'])
  VAULT_ID = os.environ['VAULT_ID']

  def add_to_knowledge_base(content: str, metadata: dict = None):
      """Add text content to the knowledge base"""
      filename = f'knowledge-{int(datetime.now().timestamp())}.txt'
      
      # Get upload URL
      upload = client.vault.upload(VAULT_ID,
          filename=filename,
          content_type='text/plain',
          metadata={
              'source': 'user-input',
              'timestamp': datetime.now().isoformat(),
              **(metadata or {})
          }
      )
      
      # Upload content
      requests.put(upload.upload_url, 
          data=content.encode('utf-8'),
          headers={'Content-Type': 'text/plain'}
      )
      
      # Trigger ingestion (generates embeddings automatically)
      client.vault.ingest(upload.object_id, id=VAULT_ID)
      
      return {'object_id': upload.object_id, 'filename': filename}

  def add_file_to_knowledge_base(file_path: str, content_type: str):
      """Add a file (PDF, Word, image) to the knowledge base"""
      filename = os.path.basename(file_path)
      
      upload = client.vault.upload(VAULT_ID,
          filename=filename,
          content_type=content_type,
          metadata={
              'source': 'file-upload',
              'timestamp': datetime.now().isoformat()
          }
      )
      
      with open(file_path, 'rb') as f:
          requests.put(upload.upload_url, data=f,
              headers={'Content-Type': content_type})
      
      # Ingestion handles OCR (if needed) and embedding generation
      job = client.vault.ingest(upload.object_id, id=VAULT_ID)
      
      return {'object_id': upload.object_id, 'job_id': job.id}
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  upload, _ := client.Vault.Upload(ctx, vaultID, casedev.VaultUploadParams{
  	Filename:    casedev.F("document.pdf"),
  	ContentType: casedev.F("application/pdf"),
  })
  // PUT file to upload.UploadURL via net/http
  fmt.Println(upload.ObjectID)
  ```
</CodeGroup>

### 2. Retrieve Relevant Information

Search the knowledge base for content relevant to a user's question:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault search \
    --id $VAULT_ID \
    --query "search query"
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  /**
   * Find relevant content from the knowledge base
   */
  async function findRelevantContent(query: string, limit: number = 5) {
    const results = await client.vault.search(VAULT_ID, {
      query,
      method: 'hybrid',  // Combines semantic + keyword search
      limit
    });
    
    // Format results for LLM context
    return results.chunks.map(chunk => ({
      content: chunk.text,
      source: chunk.filename,
      page: chunk.page,
      score: chunk.hybridScore
    }));
  }
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  def find_relevant_content(query: str, limit: int = 5):
      """Find relevant content from the knowledge base"""
      results = client.vault.search(VAULT_ID,
          query=query,
          method='hybrid',  # Combines semantic + keyword search
          top_k=limit
      )
      
      # Format results for LLM context
      return [{
          'content': chunk.text,
          'source': chunk.filename,
          'page': chunk.page,
          'score': chunk.hybrid_score
      } for chunk in results.chunks]
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  results, _ := client.Vault.Search(ctx, vaultID, casedev.VaultSearchParams{
  	Query: casedev.F("search query"),
  	Method: casedev.F(casedev.VaultSearchParamsMethodHybrid),
  })
  for _, chunk := range results.Chunks {
  	fmt.Println(chunk.Text)
  }
  ```
</CodeGroup>

### 3. Generate Responses with Context

Use the LLM Gateway to generate responses grounded in your documents:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  # Search knowledge base
  casedev vault search --id $VAULT_ID \
    --query "What are the key terms?" --method hybrid --limit 10

  # Answer with LLM
  casedev llm:v1:chat create-completion \
    --model anthropic/claude-sonnet-4.5 \
    --message '{role: system, content: "Answer using only the provided context. Cite sources."}' \
    --message '{role: user, content: "Context: <search results>\n\nQuestion: What are the key terms?"}' \
    --temperature 0.3 --max-tokens 1000
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  /**
   * Answer a question using the knowledge base
   */
  async function answerQuestion(question: string) {
    // 1. Retrieve relevant context
    const relevantContent = await findRelevantContent(question);
    
    if (relevantContent.length === 0) {
      return {
        answer: "I don't have any information about that in my knowledge base.",
        sources: []
      };
    }
    
    // 2. Format context for the LLM
    const context = relevantContent
      .map((c, i) => `[${i + 1}] ${c.content} (Source: ${c.source}, Page ${c.page})`)
      .join('\n\n');
    
    // 3. Generate response with LLM
    const response = await client.llm.v1.chat.createCompletion({
      model: 'anthropic/claude-sonnet-4.5',
      messages: [
        {
          role: 'system',
          content: `You are a helpful assistant that answers questions based on the provided context.
          
  Rules:
  - Only use information from the provided context
  - Cite sources using [1], [2], etc.
  - If the context doesn't contain relevant information, say so
  - Be concise and accurate`
        },
        {
          role: 'user',
          content: `Context:\n${context}\n\nQuestion: ${question}`
        }
      ],
      temperature: 0.3,
      max_tokens: 1000
    });
    
    return {
      answer: response.choices[0].message.content,
      sources: relevantContent.map(c => ({
        filename: c.source,
        page: c.page,
        excerpt: c.content.substring(0, 200) + '...'
      }))
    };
  }
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  def answer_question(question: str):
      """Answer a question using the knowledge base"""
      # 1. Retrieve relevant context
      relevant_content = find_relevant_content(question)
      
      if not relevant_content:
          return {
              'answer': "I don't have any information about that in my knowledge base.",
              'sources': []
          }
      
      # 2. Format context for the LLM
      context = '\n\n'.join([
          f"[{i+1}] {c['content']} (Source: {c['source']}, Page {c['page']})"
          for i, c in enumerate(relevant_content)
      ])
      
      # 3. Generate response with LLM
      response = client.llm.v1.chat.create_completion(
          model='anthropic/claude-sonnet-4.5',
          messages=[
              {
                  'role': 'system',
                  'content': '''You are a helpful assistant that answers questions based on the provided context.
          
  Rules:
  - Only use information from the provided context
  - Cite sources using [1], [2], etc.
  - If the context doesn't contain relevant information, say so
  - Be concise and accurate'''
              },
              {
                  'role': 'user',
                  'content': f'Context:\n{context}\n\nQuestion: {question}'
              }
          ],
          temperature=0.3,
          max_tokens=1000
      )
      
      return {
          'answer': response.choices[0].message.content,
          'sources': [{
              'filename': c['source'],
              'page': c['page'],
              'excerpt': c['content'][:200] + '...'
          } for c in relevant_content]
      }
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  // 1. Retrieve relevant context from vault
  results, _ := client.Vault.Search(ctx, vaultID, casedev.VaultSearchParams{
  	Query:  casedev.F(question),
  	Method: casedev.F(casedev.VaultSearchParamsMethodHybrid),
  	TopK:   casedev.F(int64(10)),
  })

  // Format context
  var context string
  for i, c := range results.Chunks {
  	context += fmt.Sprintf("[%d] %s (Source: %s)\n\n", i+1, c.Text, c.Filename)
  }

  // 2. Generate answer with LLM
  resp, _ := client.Llm.V1.Chat.NewCompletion(ctx, casedev.LlmV1ChatNewCompletionParams{
  	Model: casedev.F("anthropic/claude-sonnet-4.5"),
  	Messages: casedev.F([]casedev.LlmV1ChatNewCompletionParamsMessage{
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleSystem),
  			Content: casedev.F("You are a helpful assistant. Answer using only the provided context. Cite sources using [1], [2], etc."),
  		},
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
  			Content: casedev.F("Context:\n" + context + "\n\nQuestion: " + question),
  		},
  	}),
  	Temperature: casedev.F(float64(0.3)),
  	MaxTokens:   casedev.F(int64(1000)),
  })
  fmt.Println(resp.Choices[0].Message.Content)
  ```
</CodeGroup>

## Building the Agent with Tools

For a more sophisticated agent that can decide when to search vs. add knowledge, use tool calling:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  # Tool-calling agents require programmatic control flow.
  # Use the Go or TypeScript SDK for agent loops.
  # For simple questions, pipe vault search into LLM:
  casedev vault search --id $VAULT_ID --query "What deadlines are coming up?"

  casedev llm:v1:chat create-completion \
    --model anthropic/claude-sonnet-4.5 \
    --message '{role: system, content: "You are a document intelligence agent."}' \
    --message '{role: user, content: "Based on these documents: <search results>\n\nWhat deadlines are coming up?"}'
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import { z } from 'zod';

  // Define tools for the agent
  const tools = {
    searchKnowledgeBase: {
      description: 'Search the knowledge base for information relevant to a question',
      parameters: z.object({
        query: z.string().describe('The search query')
      }),
      execute: async ({ query }) => {
        const results = await findRelevantContent(query);
        return results.length > 0 
          ? results 
          : 'No relevant information found in knowledge base.';
      }
    },
    
    addToKnowledgeBase: {
      description: 'Add new information to the knowledge base. Use this when the user provides facts or documents.',
      parameters: z.object({
        content: z.string().describe('The content to add'),
        topic: z.string().optional().describe('Topic or category')
      }),
      execute: async ({ content, topic }) => {
        const result = await addToKnowledgeBase(content, { topic });
        return `Added to knowledge base: ${result.filename}`;
      }
    }
  };

  /**
   * Run the agent with tool support
   */
  async function runAgent(userMessage: string, conversationHistory: any[] = []) {
    const messages = [
      {
        role: 'system',
        content: `You are a helpful document intelligence assistant.

  Your capabilities:
  1. Search your knowledge base to answer questions
  2. Add new information when users provide it

  Always search the knowledge base before answering factual questions.
  If you don't find relevant information, say so honestly.
  When adding information, confirm what was added.`
      },
      ...conversationHistory,
      { role: 'user', content: userMessage }
    ];
    
    // First call - may request tool use
    let response = await client.llm.v1.chat.createCompletion({
      model: 'anthropic/claude-sonnet-4.5',
      messages,
      tools: ,
        }
      tool_choice: 'auto'
    });
    
    // Handle tool calls
    while (response.choices[0].message.tool_calls) {
      const toolCalls = response.choices[0].message.tool_calls;
      
      // Execute each tool
      const toolResults = await Promise.all(
        toolCalls.map(async (call) => {
          const tool = tools[call.function.name];
          const args = JSON.parse(call.function.arguments);
          const result = await tool.execute(args);
          return {
            role: 'tool',
            tool_call_id: call.id,
            content: JSON.stringify(result)
          };
        })
      );
      
      // Continue conversation with tool results
      messages.push(response.choices[0].message);
      messages.push(...toolResults);
      
      response = await client.llm.v1.chat.createCompletion({
        model: 'anthropic/claude-sonnet-4.5',
        messages,
        tools: ,
          }
        }))
      });
    }
    
    return response.choices[0].message.content;
  }
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import json

  # Define tools for the agent
  tools = [
      {
          'type': 'function',
          'function': {
              'name': 'search_knowledge_base',
              'description': 'Search the knowledge base for information relevant to a question',
              'parameters': {
                  'type': 'object',
                  'properties': {
                      'query': {
                          'type': 'string',
                          'description': 'The search query'
                      }
                  },
                  'required': ['query']
              }
          }
      },
      {
          'type': 'function',
          'function': {
              'name': 'add_to_knowledge_base',
              'description': 'Add new information to the knowledge base',
              'parameters': {
                  'type': 'object',
                  'properties': {
                      'content': {
                          'type': 'string',
                          'description': 'The content to add'
                      },
                      'topic': {
                          'type': 'string',
                          'description': 'Topic or category'
                      }
                  },
                  'required': ['content']
              }
          }
      }
  ]

  def execute_tool(name: str, args: dict):
      """Execute a tool by name"""
      if name == 'search_knowledge_base':
          results = find_relevant_content(args['query'])
          return results if results else 'No relevant information found.'
      elif name == 'add_to_knowledge_base':
          result = add_to_knowledge_base(args['content'], {'topic': args.get('topic', '')})
          return f"Added to knowledge base: {result['filename']}"

  def run_agent(user_message: str, conversation_history: list = None):
      """Run the agent with tool support"""
      messages = [
          {
              'role': 'system',
              'content': '''You are a helpful document intelligence assistant.

  Your capabilities:
  1. Search your knowledge base to answer questions
  2. Add new information when users provide it

  Always search the knowledge base before answering factual questions.
  If you don't find relevant information, say so honestly.'''
          },
          *(conversation_history or []),
          {'role': 'user', 'content': user_message}
      ]
      
      # First call - may request tool use
      response = client.llm.v1.chat.create_completion(
          model='anthropic/claude-sonnet-4.5',
          messages=messages
      )
      
      # Handle tool calls
      while response.choices[0].message.tool_calls:
          tool_calls = response.choices[0].message.tool_calls
          
          # Add assistant message with tool calls
          messages.append(response.choices[0].message)
          
          # Execute each tool and add results
          for call in tool_calls:
              args = json.loads(call.function.arguments)
              result = execute_tool(call.function.name, args)
              messages.append({
                  'role': 'tool',
                  'tool_call_id': call.id,
                  'content': json.dumps(result)
              })
          
          # Continue conversation
          response = client.llm.v1.chat.create_completion(
              model='anthropic/claude-sonnet-4.5',
              messages=messages
          )
      
      return response.choices[0].message.content
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  // Tool-calling agent loop in Go
  // 1. Initial request with tools
  resp, _ := client.Llm.V1.Chat.NewCompletion(ctx, casedev.LlmV1ChatNewCompletionParams{
  	Model: casedev.F("anthropic/claude-sonnet-4.5"),
  	Messages: casedev.F([]casedev.LlmV1ChatNewCompletionParamsMessage{
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleSystem),
  			Content: casedev.F("You are a document intelligence agent with access to a knowledge base."),
  		},
  		{
  			Role:    casedev.F(casedev.LlmV1ChatNewCompletionParamsMessagesRoleUser),
  			Content: casedev.F(question),
  		},
  	}),
  	// Tools defined as JSON schema (searchKnowledgeBase, addToKnowledgeBase)
  })

  // 2. If tool calls returned, execute them and continue
  // See TypeScript example for full tool-calling loop pattern
  fmt.Println(resp.Choices[0].Message.Content)
  ```
</CodeGroup>

## Integration with Vercel AI SDK

For Next.js applications, integrate with the Vercel AI SDK for streaming responses:

```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
// app/api/chat/route.ts
import { streamText, tool } from 'ai';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { z } from 'zod';
import Casedev from 'casedev';

const client = new Casedev({ apiKey: process.env.CASEDEV_API_KEY });
const VAULT_ID = process.env.VAULT_ID;

// Set up Case.dev as an OpenAI-compatible provider for Vercel AI SDK
const casedev = createOpenAICompatible({
  name: 'casedev',
  baseURL: 'https://api.case.dev/llm/v1',
  headers: { Authorization: `Bearer ${process.env.CASEDEV_API_KEY}` },
});

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: casedev('anthropic/claude-sonnet-4.5'),
    system: `You are a document intelligence assistant. 
Check your knowledge base before answering questions.
Only respond using information from tool calls.
If no relevant information is found, say "I don't have information about that."`,
    messages,
    maxSteps: 5,
    tools: {
      searchDocuments: tool({
        description: 'Search the document knowledge base',
        parameters: z.object({
          query: z.string().describe('The search query')
        }),
        execute: async ({ query }) => {
          const results = await client.vault.search(VAULT_ID, {
            query,
            method: 'hybrid',
            limit: 5
          });
          return results.chunks.map(c => ({
            text: c.text,
            source: c.filename,
            page: c.page
          }));
        }
      }),
      
      addDocument: tool({
        description: 'Add information to the knowledge base',
        parameters: z.object({
          content: z.string().describe('Content to add')
        }),
        execute: async ({ content }) => {
          const upload = await client.vault.upload(VAULT_ID, {
            filename: `note-${Date.now()}.txt`,
            contentType: 'text/plain'
          });
          await fetch(upload.uploadUrl, {
            method: 'PUT',
            body: content
          });
          await client.vault.ingest(VAULT_ID, upload.objectId);
          return 'Added to knowledge base successfully';
        }
      })
    }
  });

  return result.toDataStreamResponse();
}
```

## Example Usage

```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
// Add some knowledge
await addToKnowledgeBase(
  'The Smith v. Jones case was filed on March 15, 2024. The plaintiff alleges negligence in the maintenance of the property.',
  { topic: 'case-facts' }
);

await addToKnowledgeBase(
  'Deposition of John Smith on April 2, 2024: Witness stated he observed water damage on the ceiling two weeks before the incident.',
  { topic: 'depositions' }
);

// Ask questions
const result = await answerQuestion('When was the Smith v. Jones case filed?');
console.log(result.answer);
// "The Smith v. Jones case was filed on March 15, 2024 [1]."

const result2 = await answerQuestion('What did John Smith observe?');
console.log(result2.answer);
// "John Smith observed water damage on the ceiling two weeks before the incident [1]."

// Using the agent
const response = await runAgent('My favorite pizza topping is pepperoni. Remember that.');
console.log(response);
// "I've added that to my knowledge base. Your favorite pizza topping is pepperoni."

const response2 = await runAgent('What is my favorite pizza topping?');
console.log(response2);
// "According to my knowledge base, your favorite pizza topping is pepperoni."
```

## Best Practices

<Info>
  **Chunking is automatic.** Case.dev Vaults automatically chunk documents into semantic segments optimized for retrieval. You don't need to implement chunking yourself.
</Info>

### 1. Use hybrid search

Combine semantic and keyword search for best results:

<CodeGroup>
  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev vault search --id $VAULT_ID \
    --query "liability insurance coverage limits" \
    --method hybrid --limit 10
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  const results = await client.vault.search(VAULT_ID, {
    query: 'liability insurance coverage limits',
    method: 'hybrid',  // Best of both worlds
    limit: 10
  });
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  results = client.vault.search(VAULT_ID,
      query='liability insurance coverage limits',
      method='hybrid',  # Best of both worlds
      top_k=10
  )
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  results, _ := client.Vault.Search(ctx, vaultID, casedev.VaultSearchParams{
      Query:  casedev.F("liability insurance coverage limits"),
      Method: casedev.F(casedev.VaultSearchParamsMethodHybrid),
      TopK:   casedev.F(int64(10)),
  })
  ```
</CodeGroup>

### 2. Set appropriate temperature

Use low temperature for factual retrieval:

<CodeGroup>
  ```bash title="cURL" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  curl -X POST https://api.case.dev/llm/v1/chat/completions \
    -H "Authorization: Bearer $CASEDEV_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{}'
  ```

  ```bash title="CLI" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  casedev llm:v1:chat create-completion \
    --model anthropic/claude-sonnet-4.5 \
    --message '{role: user, content: "..."}' \
    --temperature 0.2
  ```

  ```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  const response = await client.llm.v1.chat.createCompletion({
    model: 'anthropic/claude-sonnet-4.5',
    messages: [...],
    temperature: 0.2  // More deterministic for factual tasks
  });
  ```

  ```python title="Python" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  response = client.llm.v1.chat.create_completion(
      model='anthropic/claude-sonnet-4.5',
      messages=[...],
      temperature=0.2  # More deterministic for factual tasks
  )
  ```

  ```go title="Go" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  resp, _ := client.Llm.V1.Chat.NewCompletion(ctx, casedev.LlmV1ChatNewCompletionParams{
      Model:       casedev.F("anthropic/claude-sonnet-4.5"),
      Messages:    casedev.F(messages),
      Temperature: casedev.F(float64(0.2)),
  })
  ```
</CodeGroup>

### 3. Structure your prompts

Be explicit about using only provided context:

```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
const systemPrompt = `You are a legal research assistant.

Rules:
- ONLY use information from the provided context
- If information is not in the context, say "I don't have that information"
- Always cite sources using [1], [2], etc.
- Never make up or infer facts not explicitly stated`;
```

### 4. Handle no results gracefully

```typescript title="Typescript" theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
const results = await findRelevantContent(query);

if (results.length === 0 || results[0].score < 0.5) {
  return "I couldn't find relevant information in the knowledge base.";
}
```

## Next Steps

* Add [file upload support](/vault/upload) for PDFs and images
* Implement [conversation memory](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot) with Vercel AI SDK
* Add [citations with page links](/vault/search) to your responses
* Scale with [batch processing](/cookbooks/discovery-pipeline) for large document sets
