npm package: @fastn-ai/ucl-sdk
Description: Find the best matching tools from UCL using semantic search. Get top 5 ranked tools with confidence scores and LLM-ready prompts for AI agent integration.
Author: UCL Team
Latest version: 1.0.3

# UCL SDK

Find the best matching tools from UCL using semantic search. Get top 5 ranked tools with confidence scores and LLM-ready prompts for AI agent integration.

## Quick Start

### Step 1: Get Your Credentials

Before using the UCL SDK, you need to obtain your credentials from the UCL platform:

1. **Sign up/Login** to the UCL platform at [app.ucl.dev](https://app.ucl.dev)
2. **Navigate to Settings** → **API Credentials**
3. **Copy your credentials**:
   - **Space ID**: Your unique space identifier (looks like: `0b394afd-e5ba-4b7c-85af-a2d6639e7c04`)
   - **Auth Token**: Your authentication token (looks like: `7c8bc9182a07e19b262495dd1e27fa21bd5029a9`)

**Tip**: Store these credentials securely in your environment variables or configuration files.

### Step 2: Installation

```bash
npm install @fastn-ai/ucl-sdk
```

### Step 3: Basic Setup

Create your first UCL integration:

```typescript
import { UCL, UCLConfig } from '@fastn-ai/ucl-sdk';

// Basic configuration - just your credentials
const config: UCLConfig = {
  authToken: process.env.UCL_AUTH_TOKEN,      // From Step 1
  spaceId: process.env.UCL_SPACE_ID           // From Step 1
  // Everything else uses defaults
};

async function findTools() {
  // Initialize UCL
  const ucl = new UCL(config);
  await ucl.initialize();

  // Find tools for a user message
  const analysis = await ucl.findTools(
    [{ role: "user", content: "Send an email to john@example.com" }]
  );

  // Check if tools were found
  if (analysis.requiresTool && analysis.topMatches) {
    console.log(`Found ${analysis.topMatches.length} matching tools!`);
    
    // Show the top matches
    analysis.topMatches.forEach((match, index) => {
      const emoji = match.confidence === 'high' ? '🟢' : 
                    match.confidence === 'medium' ? '🟡' : '🔴';
      console.log(`${index + 1}. ${emoji} ${match.tool.function.name} (${match.confidence})`);
    });

    // Best tool recommendation
    console.log(`\nBest match: ${analysis.tool?.function.name}`);
  } else {
    console.log("No matching tools found");
  }
}

findTools();
```

## LLM Integration

The UCL SDK automatically generates LLM-ready prompts for your found tools, making AI agent integration simple:

```typescript
const analysis = await ucl.findTools(messages);

if (analysis.requiresTool && analysis.llmPrompt) {
  // Ready-to-use prompt for your LLM
  console.log("LLM Prompt:", analysis.llmPrompt);
  
  // Send to your favorite LLM (OpenAI, Claude, etc.)
  const llmResponse = await your_llm_client.chat({
    messages: [{ role: "user", content: analysis.llmPrompt }]
  });
  
  // The LLM will return structured tool selection and parameters
  const toolSelection = JSON.parse(llmResponse);
  console.log("Selected tool:", toolSelection.selectedTool);
  console.log("Parameters:", toolSelection.parameters);
  
  // Execute the selected tool
  const result = await ucl.executeTool(
    toolSelection.selectedTool,
    toolSelection.parameters
  );
  
  if (result.success) {
    console.log("Tool executed successfully:", result.data);
  } else {
    console.log("Tool execution failed:", result.error);
  }
}
```

### What You Get with LLM Integration:

1. **Tool Selection**: LLM chooses the best tool from top 5 matches
2. **Parameter Extraction**: Automatically extracts parameters from conversation
3. **Missing Parameter Detection**: Identifies what additional info is needed
4. **Tool Execution**: Execute tools directly with extracted parameters

## Complete Example: Conversation Agent

We've created a complete conversation agent example that shows how to:
- Handle user conversations naturally
- Ask follow-up questions for missing parameters
- Execute tools when ready
- Maintain conversation history
- Provide user-friendly responses

```javascript
// See https://github.com/fastnai/ucl-sdk/tree/main/examples for the full implementation
const { UCL } = require('@fastn-ai/ucl-sdk');

// Simple conversation agent that executes tools
class ConversationAgent {
  constructor() {
    this.ucl = new UCL({
      authToken: process.env.UCL_AUTH_TOKEN,
      spaceId: process.env.UCL_SPACE_ID
    });
  }
  
  async processMessage(userMessage) {
    const analysis = await this.ucl.findTools([...conversationHistory]);
    
    if (analysis.requiresTool && analysis.tool) {
      // Check for missing parameters and ask follow-up questions
      // Execute tool when all parameters are available
      const result = await this.ucl.executeTool(
        analysis.tool.actionId, 
        extractedParameters
      );
      return result.success ? `Done! ${result.data}` : `Error: ${result.error}`;
    }
    
    return "How can I help you today?";
  }
}
```

**Try it yourself:**
```bash
# Clone and run the example
git clone https://github.com/fastnai/ucl-sdk.git
cd ucl-sdk/examples
node conversation-agent.js
```

## Configuration Guide

### Basic Configuration (Recommended)
```typescript
const config: UCLConfig = {
  authToken: process.env.UCL_AUTH_TOKEN,
  spaceId: process.env.UCL_SPACE_ID
  // Uses defaults:
  // - environment: "DRAFT" (for testing)
  // - embedding: Local Xenova model (no external API calls)
};
```

### Advanced Configuration
```typescript
const config: UCLConfig = {
  authToken: process.env.UCL_AUTH_TOKEN,
  spaceId: process.env.UCL_SPACE_ID,
  environment: "LIVE",                    // Use "DRAFT" for testing
  baseUrl: "https://custom-api.com",      // Custom API endpoint
  
  // Advanced embedding configuration
  embeddingConfig: {
    provider: "openai",                   // "xenova" (local) or "openai" (cloud)
    openaiApiKey: process.env.OPENAI_API_KEY,               // Required for OpenAI
    modelSize: "large",                   // "small" | "base" | "large" (xenova only)
    preloadModel: true,                   // Faster startup
    maxBatchSize: 32                      // Batch processing size
  }
};
```

## Usage Patterns

### Tool Execution

Execute tools after finding them:

```typescript
const analysis = await ucl.findTools([
  { role: "user", content: "Send email to john@example.com with subject 'Meeting Tomorrow'" }
]);

if (analysis.requiresTool && analysis.tool) {
  // Extract parameters from the conversation
  const parameters = {
    to: "john@example.com",
    subject: "Meeting Tomorrow",
    body: "Hi John, let's meet tomorrow at 2pm."
  };
  
  // Execute the tool
  const result = await ucl.executeTool(
    analysis.tool.actionId,
    parameters,
    'user-tenant-id' // optional
  );
  
  if (result.success) {
    console.log('Email sent successfully:', result.data);
  } else {
    console.log('Failed to send email:', result.error);
  }
}
```

### Pattern 1: Simple Tool Discovery
```typescript
async function simpleDiscovery(userMessage: string) {
  const analysis = await ucl.findTools(
    [{ role: "user", content: userMessage }],
    "tenant-123"
  );
  
  return analysis.requiresTool ? analysis.tool : null;
}
```

### Pattern 2: Conversation-Aware Discovery
```typescript
async function contextAwareDiscovery(messages: Message[]) {
  const analysis = await ucl.findTools(messages);
  
  // Filter by confidence level
  const highConfidenceTools = analysis.topMatches?.filter(
    match => match.confidence === 'high'
  );
  
  return highConfidenceTools?.length > 0 
    ? highConfidenceTools[0] 
    : analysis.topMatches?.[0];
}
```

### Pattern 3: Multi-Step Tool Selection
```typescript
async function toolSelection(messages: Message[]) {
  const analysis = await ucl.findTools(messages);
  
  if (!analysis.requiresTool) {
    return { action: "continue_conversation" };
  }
  
  if (analysis.topMatches?.length === 1) {
    return { action: "auto_select", tool: analysis.tool };
  }
  
  // Multiple options - use LLM to decide
  if (analysis.llmPrompt) {
    const llmDecision = await callYourLLM(analysis.llmPrompt);
    return { action: "llm_selected", decision: llmDecision };
  }
  
  return { action: "ask_user", options: analysis.topMatches };
}
```

## Understanding Results

### ToolAnalysisResult Structure
```typescript
{
  requiresTool: true,                     // Tools were found
  tool: {                                 // Best matching tool
    function: {
      name: "send_email",
      description: "Send an email message",
      parameters: { /* tool parameters */ }
    }
  },
  topMatches: [                          // Top 5 ranked tools
    {
      tool: { /* tool object */ },
      score: 0.89,                       // Similarity score (0-1)
      confidence: "high",                // "high" | "medium" | "low"
      rank: 1                           // Ranking position
    }
  ],
  llmPrompt: "Based on the following...", // Ready-to-use LLM prompt
  toolSelectionContext: "User Intent: Send email\nTop Tools: send_email (high), send_message (medium)",
  analysisMetrics: {
    processingTime: 45,                  // Time taken (ms)
    totalToolsAnalyzed: 127,            // Number of tools analyzed
    similarityThreshold: 0.3             // Threshold used
  }
}
```

### Confidence Levels

- **High**: Score > 0.7, likely correct tool
- **Medium**: Score 0.3-0.7, probably correct but verify
- **Low**: Score < 0.3, uncertain match, consider alternatives

## Environment Setup

### Using Environment Variables (.env)
```bash
# .env file
UCL_AUTH_TOKEN=your-auth-token-here
UCL_SPACE_ID=your-space-id-here
UCL_ENVIRONMENT=DRAFT
```

```typescript
// In your code
import dotenv from 'dotenv';
dotenv.config();

const config: UCLConfig = {
  authToken: process.env.UCL_AUTH_TOKEN!,
  spaceId: process.env.UCL_SPACE_ID!,
  environment: process.env.UCL_ENVIRONMENT as 'DRAFT' | 'LIVE'
};
```

### TypeScript Configuration
Add to your `tsconfig.json`:
```json
{
  "compilerOptions": {
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  }
}
```

## Production Deployment

### Performance Optimization
```typescript
const productionConfig: UCLConfig = {
  authToken: process.env.UCL_AUTH_TOKEN!,
  spaceId: process.env.UCL_SPACE_ID!,
  environment: "LIVE",
  embeddingConfig: {
    provider: "xenova",
    modelSize: "base",        // Balance speed vs accuracy
    preloadModel: true,       // Faster responses
    maxBatchSize: 64         // Higher throughput
  }
};

// Initialize once at app startup
const ucl = new UCL(productionConfig);
await ucl.initialize();

// Clear cache periodically to manage memory
setInterval(() => {
  ucl.clearCache();
}, 30 * 60 * 1000); // Every 30 minutes
```

### Error Handling
```typescript
import { UCL, ConfigurationError } from '@fastn-ai/ucl-sdk';

async function toolDiscovery(messages: Message[]) {
  try {
    const analysis = await ucl.findTools(messages);
    return analysis;
    
  } catch (error) {
    if (error instanceof ConfigurationError) {
      // Configuration issues (invalid credentials, etc.)
      console.error("UCL Configuration Error:", error.message);
      throw new Error("Service configuration error");
      
    } else {
      // Network, API, or other errors
      console.error("UCL Service Error:", error.message);
      
      // Fallback behavior
      return {
        requiresTool: false,
        message: "Tool discovery temporarily unavailable"
      };
    }
  }
}
```

## API Reference

### Core Methods

| Method | Description | Returns |
|--------|-------------|---------|
| `initialize()` | Load tools and connectors | `Promise<void>` |
| `findTools(messages)` | Find matching tools with LLM prompts | `Promise<ToolAnalysisResult>` |
| `executeTool(actionId, parameters, tenantId?)` | Execute a tool with parameters | `Promise<ExecuteToolResponse>` |
| `getAvailableTools()` | Get all tools | `Tool[]` |
| `getAvailableConnectors()` | Get all connectors | `Connector[]` |
| `refreshTools()` | Reload tools from API | `Promise<void>` |
| `clearCache()` | Clear analysis cache | `void` |
| `isInitialized()` | Check initialization status | `boolean` |

### Type Definitions

```typescript
interface Message {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

interface ToolAnalysisResult {
  requiresTool: boolean;
  tool?: Tool;
  topMatches?: ToolMatch[];
  llmPrompt?: string;              // NEW: Ready-to-use LLM prompt
  toolSelectionContext?: string;   // NEW: Context for LLM
  requiresConnection?: boolean;
  connector?: Connector;
  message?: string;
  analysisMetrics?: {
    processingTime: number;
    totalToolsAnalyzed: number;
    similarityThreshold: number;
  };
}

interface ToolMatch {
  tool: Tool;
  score: number;                   // 0-1 similarity score
  confidence: 'high' | 'medium' | 'low';
  rank: number;
  relevanceMetrics: {
    nameScore: number;
    descriptionScore: number;
    combinedScore: number;
  };
}
```

## Common Questions

**Q: Which embedding provider should I use?**
A: Use `xenova` (local) for privacy and no API costs, or `openai` for better accuracy with API costs.

**Q: What's the difference between DRAFT and LIVE environments?**
A: DRAFT is for testing/development with sandbox data. LIVE is for production with real connectors and tools.

**Q: How many tools can I analyze at once?**
A: The SDK can handle hundreds of tools efficiently. Performance scales with your embedding model choice.

**Q: Can I customize the similarity threshold?**
A: The SDK automatically adjusts thresholds for good results, but you can filter results by confidence level.

**Q: How do I handle rate limits?**
A: The SDK includes retry logic. For high-volume usage, consider implementing your own queueing system.

## License

MIT License - see the [LICENSE](./LICENSE) file for details.

## Support

- Website: [ucl.dev](https://ucl.dev)
- Platform: [app.ucl.dev](https://app.ucl.dev)
- Documentation: [docs.fastn.ai/ucl-unified-context-layer/about-ucl](https://docs.fastn.ai/ucl-unified-context-layer/about-ucl)
- Issues: [GitHub Issues](https://github.com/fastnai/ucl-sdk/issues)