Building AI Tools on Probe (Node.js SDK & LangChain) ​

This guide explains how to use Probe's Node.js SDK to build custom AI-powered code intelligence tools for your development workflow.

Overview ​

Probe's Node.js SDK provides programmatic access to its powerful code search capabilities, allowing you to build custom tools, integrate with AI frameworks, and create specialized workflows for your development team.

Installing @probelabs/probe as a Dependency ​

Start by adding Probe to your Node.js project:

bash
# Add to your project
npm install @probelabs/probe@latest

# Or install globally
npm install -g @probelabs/probe@latest

During installation, the package will automatically download the appropriate Probe binary for your platform (Windows, macOS, or Linux).

Basic SDK Usage ​

The SDK provides three main functions:

Search: Find Code Patterns ​

javascript
import { search } from '@probelabs/probe';

// Search for code patterns
const results = await search({
  path: '/path/to/your/project',
  query: 'authentication',
  maxResults: 10
});

console.log(results);

Query: Find Specific Code Structures ​

javascript
import { query } from '@probelabs/probe';

// Find JavaScript functions
const functions = await query({
  path: '/path/to/your/project',
  pattern: 'function $NAME($$$PARAMS) $$$BODY',
  language: 'javascript'
});

console.log(functions);

Extract: Get Code from Specific Files ​

javascript
import { extract } from '@probelabs/probe';

// Extract code from specific files
const code = await extract({
  files: [
    '/path/to/your/project/src/auth.js',
    '/path/to/your/project/src/api.js:42'  // Extract from line 42
  ]
});

// Extract with LLM prompt and instructions
const codeWithPrompt = await extract({
  files: ['/path/to/your/project/src/auth.js#authenticate'],
  prompt: 'engineer',  // Use engineer template
  instructions: 'Explain this authentication function'
});

// Extract with custom prompt template
const codeWithCustomPrompt = await extract({
  files: ['/path/to/your/project/src/api.js:42'],
  prompt: '/path/to/custom/prompt.txt',  // Path to custom prompt file
  instructions: 'Refactor this code'
});

console.log(code);

Examples with LangChain ​

LangChain is a popular framework for building AI applications. Here's how to integrate Probe with LangChain:

Creating LangChain Tools ​

javascript
import { ChatOpenAI } from '@langchain/openai';
import { tools } from '@probelabs/probe';

// Create LangChain tools from Probe
const searchTool = tools.createSearchTool();
const queryTool = tools.createQueryTool();
const extractTool = tools.createExtractTool();

// Create a ChatOpenAI instance with tools
const model = new ChatOpenAI({
  modelName: "gpt-4o",
  temperature: 0.7
}).withTools([searchTool, queryTool, extractTool]);

// Use the model with tools
async function chatWithAI(userMessage) {
  const result = await model.invoke([
    { 
      role: "system", 
      content: "You are a code intelligence assistant. Use the provided tools to search and analyze code." 
    },
    { 
      role: "user", 
      content: userMessage 
    }
  ]);
  
  return result.content;
}

// Example usage
const response = await chatWithAI("How is authentication implemented in this project?");
console.log(response);

Building a Code Explanation Chain ​

javascript
import { search } from '@probelabs/probe';
import { ChatOpenAI } from '@langchain/openai';
import { PromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';

// Create a chain for code explanation
async function createCodeExplainer() {
  // Create a chat model
  const model = new ChatOpenAI({
    modelName: "gpt-4o",
    temperature: 0.5
  });
  
  // Create a prompt template
  const promptTemplate = PromptTemplate.fromTemplate(`
    You are a code explanation expert. I'll provide you with a question and some code search results.
    Please analyze the code and explain it in a clear, concise manner.
    
    Question: {question}
    
    Code search results:
    {searchResults}
    
    Your explanation:
  `);
  
  // Create a chain
  const chain = promptTemplate
    .pipe(model)
    .pipe(new StringOutputParser());
  
  // Function to explain code
  async function explainCode(question, codebasePath) {
    // Search for relevant code
    const searchResults = await search({
      path: codebasePath,
      query: question,
      maxResults: 5,
      maxTokens: 8000
    });
    
    // Get the explanation from the AI
    const explanation = await chain.invoke({
      question,
      searchResults
    });
    
    return explanation;
  }
  
  return { explainCode };
}

// Usage
const explainer = await createCodeExplainer();
const explanation = await explainer.explainCode(
  "How does the authentication system work?",
  "/path/to/your/project"
);
console.log(explanation);

Examples with Vercel AI SDK ​

The Vercel AI SDK is another popular framework for building AI applications. The latest version of Probe provides tool generators that allow for better configuration and session isolation:

javascript
import { generateText } from 'ai';
import { searchTool, queryTool, extractTool } from '@probelabs/probe';
import { randomUUID } from 'crypto';

// Generate a session ID for tool isolation
const sessionId = randomUUID();
console.log(`Generated session ID: ${sessionId}`);

// Configure tools with options
const configOptions = {
  sessionId,
  debug: process.env.DEBUG === 'true',
  maxTokens: 30000 // Optional: override default max tokens
};

// Create configured tool instances
const configuredTools = {
  search: searchTool(configOptions),
  query: queryTool(configOptions),
  extract: extractTool(configOptions)
};

// Use the configured tools with Vercel AI SDK
async function chatWithAI(userMessage) {
  const result = await generateText({
    model: provider(modelName),
    messages: [{ role: 'user', content: userMessage }],
    system: "You are a code intelligence assistant. Use the provided tools to search and analyze code.",
    tools: configuredTools,
    maxSteps: 15,
    temperature: 0.7
  });
  
  return result.text;
}

// Example usage
const response = await chatWithAI("Find all API endpoints in this project");
console.log(response);

Benefits of Tool Generators ​

The new tool generator approach provides several advantages:

  1. Session Isolation: Each tool instance can have its own session ID, which is crucial in concurrent environments like web applications
  2. Configurable Options: You can customize tools with options like debug logging and token limits
  3. Better Debugging: Enable debug mode to see detailed logs of tool execution
  4. Backward Compatibility: The package still exports pre-configured tools for backward compatibility

Backward Compatibility ​

If you prefer the previous approach, you can still use the pre-configured tools:

javascript
import { tools } from '@probelabs/probe';

// Use the pre-configured tools
const result = await generateText({
  model: provider(modelName),
  messages: [{ role: 'user', content: userMessage }],
  system: "You are a code intelligence assistant.",
  tools: {
    search: tools.searchTool,
    query: tools.queryTool,
    extract: tools.extractTool
  }
});

Building a Custom Code Search API ​

Create a REST API for code search:

javascript
import express from 'express';
import { search, query, extract } from '@probelabs/probe';

const app = express();
app.use(express.json());

// Search endpoint
app.post('/api/search', async (req, res) => {
  try {
    const { path, query, options } = req.body;
    const results = await search({
      path,
      query,
      ...options
    });
    res.json({ results });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Query endpoint
app.post('/api/query', async (req, res) => {
  try {
    const { path, pattern, language, options } = req.body;
    const results = await query({
      path,
      pattern,
      language,
      ...options
    });
    res.json({ results });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Extract endpoint
app.post('/api/extract', async (req, res) => {
  try {
    const { files, prompt, instructions, options } = req.body;
    const results = await extract({
      files,
      prompt,
      instructions,
      ...options
    });
    res.json({ results });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log('Code search API running on port 3000');
});

Handling Concurrency and Caching ​

For production applications, you'll want to handle concurrency and implement caching:

Implementing a Cache Layer ​

javascript
import { search } from '@probelabs/probe';
import NodeCache from 'node-cache';

// Create a cache with 1 hour TTL
const cache = new NodeCache({ stdTTL: 3600 });

// Cached search function
async function cachedSearch(params) {
  // Create a cache key from the parameters
  const cacheKey = JSON.stringify(params);
  
  // Check if we have a cached result
  const cachedResult = cache.get(cacheKey);
  if (cachedResult) {
    console.log('Cache hit!');
    return cachedResult;
  }
  
  // Perform the search
  console.log('Cache miss, performing search...');
  const results = await search(params);
  
  // Cache the results
  cache.set(cacheKey, results);
  
  return results;
}

// Example usage
const results = await cachedSearch({
  path: '/path/to/your/project',
  query: 'authentication',
  maxResults: 10
});

Managing Concurrency ​

javascript
import { search } from '@probelabs/probe';
import pLimit from 'p-limit';

// Limit concurrency to 5 simultaneous searches
const limit = pLimit(5);

// Function to search multiple repositories
async function searchMultipleRepos(query, repositories) {
  // Map each repository to a limited promise
  const promises = repositories.map(repo => 
    limit(() => search({
      path: repo,
      query,
      maxResults: 10
    }))
  );
  
  // Wait for all searches to complete
  const results = await Promise.all(promises);
  
  // Combine and return results
  return repositories.reduce((acc, repo, index) => {
    acc[repo] = results[index];
    return acc;
  }, {});
}

// Example usage
const results = await searchMultipleRepos(
  'authentication',
  [
    '/path/to/repo1',
    '/path/to/repo2',
    '/path/to/repo3',
    '/path/to/repo4',
    '/path/to/repo5'
  ]
);

Best Practices for Production ​

Error Handling ​

javascript
import { search } from '@probelabs/probe';

async function robustSearch(params) {
  try {
    return await search(params);
  } catch (error) {
    console.error('Search error:', error);
    
    // Handle specific errors
    if (error.message.includes('Binary not found')) {
      // Handle missing binary
      console.error('Probe binary not found. Trying to reinstall...');
      // Implement reinstall logic
    } else if (error.message.includes('Permission denied')) {
      // Handle permission issues
      console.error('Permission denied. Check file permissions.');
    } else if (error.message.includes('timeout')) {
      // Handle timeouts
      console.error('Search timed out. Try with a more specific query.');
    }
    
    // Return a fallback result
    return { error: error.message, fallback: true };
  }
}

Performance Optimization ​

javascript
import { search } from '@probelabs/probe';

// Optimize search for performance
async function optimizedSearch(query, path) {
  // First, do a quick search for files only
  const fileResults = await search({
    path,
    query,
    filesOnly: true,
    maxResults: 50
  });
  
  // If we have too many files, refine the query
  if (fileResults.files && fileResults.files.length > 20) {
    console.log('Too many files, refining query...');
    // Add more specific terms to narrow down results
    return search({
      path,
      query: `${query} AND (function OR class OR method)`,
      maxResults: 10
    });
  }
  
  // Otherwise, search the content of the files we found
  return search({
    path,
    query,
    maxResults: 10
  });
}

Memory Management ​

javascript
import { search } from '@probelabs/probe';

// Search with memory constraints
async function memoryEfficientSearch(query, path) {
  // Split the search into smaller batches
  const directories = [
    `${path}/src`,
    `${path}/lib`,
    `${path}/packages`
  ];
  
  const results = [];
  
  // Process each directory separately
  for (const dir of directories) {
    const dirResults = await search({
      path: dir,
      query,
      maxResults: 5
    });
    
    results.push(dirResults);
    
    // Optional: Force garbage collection if available
    if (global.gc) {
      global.gc();
    }
  }
  
  return results;
}

// Run with --expose-gc flag to enable manual garbage collection
// node --expose-gc your-script.js

Next Steps ​