diff --git a/.github/ISSUE_TEMPLATE/milestone.md b/.github/ISSUE_TEMPLATE/milestone.md
new file mode 100644
index 00000000..d6e79a42
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/milestone.md
@@ -0,0 +1,36 @@
+---
+name: Milestone
+about: Track implementation milestones
+title: '[MILESTONE] '
+labels: milestone
+assignees: ''
+---
+
+## Milestone Overview
+
+
+## Tasks
+
+- [ ] Task 1
+- [ ] Task 2
+- [ ] Task 3
+
+## Acceptance Criteria
+
+- [ ] Criterion 1
+- [ ] Criterion 2
+
+## Tests Required
+
+- [ ] Unit tests: X+
+- [ ] Integration tests: Y+
+
+## Documentation
+
+- [ ] API documentation
+- [ ] Usage examples
+- [ ] README updates
+
+## Estimated Duration
+
+X-Y days
diff --git a/.github/assets/LC-logo-bright.png b/.github/assets/LC-logo-bright.png
new file mode 100644
index 00000000..65538317
Binary files /dev/null and b/.github/assets/LC-logo-bright.png differ
diff --git a/.github/assets/LC-logo-dark.png b/.github/assets/LC-logo-dark.png
new file mode 100644
index 00000000..f4f6193a
Binary files /dev/null and b/.github/assets/LC-logo-dark.png differ
diff --git a/README.md b/README.md
index 25e2e4bc..98e6a991 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
[](https://pypi.org/project/cascadeflow/)
[](https://www.npmjs.com/package/@cascadeflow/core)
+[](https://www.npmjs.com/package/@cascadeflow/langchain)
[](https://www.npmjs.com/package/@cascadeflow/n8n-nodes-cascadeflow)
[](./LICENSE)
[](https://pepy.tech/project/cascadeflow)
@@ -52,7 +53,7 @@ Use cascadeflow for:
- **Cost Optimization.** Reduce API costs by 40-85% through intelligent model cascading and speculative execution with automatic per-query cost tracking.
- **Cost Control and Transparency.** Built-in telemetry for query, model, and provider-level cost tracking with configurable budget limits and programmable spending caps.
- **Low Latency & Speed Optimization**. Sub-2ms framework overhead with fast provider routing (Groq sub-50ms). Cascade simple queries to fast models while reserving expensive models for complex reasoning, achieving 2-10x latency reduction overall. (use preset `PRESET_ULTRA_FAST`)
-- **Multi-Provider Flexibility.** Unified API across **`OpenAI`, `Anthropic`, `Groq`, `Ollama`, `vLLM`, `Together`, and `Hugging Face`** with automatic provider detection and zero vendor lock-in. Optional **`LiteLLM`** integration for 100+ additional providers.
+- **Multi-Provider Flexibility.** Unified API across **`OpenAI`, `Anthropic`, `Groq`, `Ollama`, `vLLM`, `Together`, and `Hugging Face`** with automatic provider detection and zero vendor lock-in. Optional **`LiteLLM`** integration for 100+ additional providers, plus **`LangChain`** integration for LCEL chains and tools.
- **Edge & Local-Hosted AI Deployment.** Use best of both worlds: handle most queries with local models (vLLM, Ollama), then automatically escalate complex queries to cloud providers only when needed.
> **โน๏ธ Note:** SLMs (under 10B parameters) are sufficiently powerful for 60-70% of agentic AI tasks. [Research paper](https://www.researchgate.net/publication/392371267_Small_Language_Models_are_the_Future_of_Agentic_AI)
@@ -361,6 +362,108 @@ CascadeFlow is a **Language Model sub-node** that connects two AI Chat Model nod
---
+##
LangChain Integration
+
+Use cascadeflow with LangChain for intelligent model cascading with full LCEL, streaming, and tools support!
+
+### Installation
+
+```bash
+npm install @cascadeflow/langchain @langchain/core @langchain/openai
+```
+
+### Quick Start
+
+Drop-in replacement for any LangChain chat model:
+
+```typescript
+import { ChatOpenAI } from '@langchain/openai';
+import { ChatAnthropic } from '@langchain/anthropic';
+import { CascadeFlow } from '@cascadeflow/langchain';
+
+const cascade = new CascadeFlow({
+ drafter: new ChatOpenAI({ modelName: 'gpt-5-mini' }), // $0.25/$2 per 1M tokens
+ verifier: new ChatAnthropic({ modelName: 'claude-sonnet-4-5' }), // $3/$15 per 1M tokens
+ qualityThreshold: 0.8, // 80% queries use drafter
+});
+
+// Use like any LangChain chat model
+const result = await cascade.invoke('Explain quantum computing');
+
+// Optional: Enable LangSmith tracing (see https://smith.langchain.com)
+// Set LANGSMITH_API_KEY, LANGSMITH_PROJECT, LANGSMITH_TRACING=true
+
+// Or with LCEL chains
+const chain = prompt.pipe(cascade).pipe(new StringOutputParser());
+```
+
+
+๐ก Optional: Model Discovery & Analysis Helpers
+
+For discovering optimal cascade pairs from your existing LangChain models, use the built-in discovery helpers:
+
+```typescript
+import {
+ discoverCascadePairs,
+ findBestCascadePair,
+ analyzeModel,
+ validateCascadePair
+} from '@cascadeflow/langchain';
+
+// Your existing LangChain models (configured with YOUR API keys)
+const myModels = [
+ new ChatOpenAI({ model: 'gpt-3.5-turbo' }),
+ new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ new ChatOpenAI({ model: 'gpt-4o' }),
+ new ChatAnthropic({ model: 'claude-3-haiku' }),
+ // ... any LangChain chat models
+];
+
+// Quick: Find best cascade pair
+const best = findBestCascadePair(myModels);
+console.log(`Best pair: ${best.analysis.drafterModel} โ ${best.analysis.verifierModel}`);
+console.log(`Estimated savings: ${best.estimatedSavings}%`);
+
+// Use it immediately
+const cascade = new CascadeFlow({
+ drafter: best.drafter,
+ verifier: best.verifier,
+});
+
+// Advanced: Discover all valid pairs
+const pairs = discoverCascadePairs(myModels, {
+ minSavings: 50, // Only pairs with โฅ50% savings
+ requireSameProvider: false, // Allow cross-provider cascades
+});
+
+// Validate specific pair
+const validation = validateCascadePair(drafter, verifier);
+console.log(`Valid: ${validation.valid}`);
+console.log(`Warnings: ${validation.warnings}`);
+```
+
+**What you get:**
+- ๐ Automatic discovery of optimal cascade pairs from YOUR models
+- ๐ฐ Estimated cost savings calculations
+- โ ๏ธ Validation warnings for misconfigured pairs
+- ๐ Model tier analysis (drafter vs verifier candidates)
+
+**Full example:** See [model-discovery.ts](./packages/langchain-cascadeflow/examples/model-discovery.ts)
+
+
+
+**Features:**
+
+- โ
Full LCEL support (pipes, sequences, batch)
+- โ
Streaming with pre-routing
+- โ
Tool calling and structured output
+- โ
LangSmith cost tracking metadata
+- โ
Works with all LangChain features
+
+๐ฆ **Learn more:** [LangChain Integration Guide](./docs/guides/langchain_integration.md) | [Package README](./packages/langchain-cascadeflow/)
+
+---
+
## Resources
### Examples
@@ -426,7 +529,7 @@ CascadeFlow is a **Language Model sub-node** that connects two AI Chat Model nod
-Advanced Examples - Production & edge deployment
+Advanced Examples - Production, edge & LangChain
| Example | Description | Link |
|---------|-------------|------|
@@ -434,6 +537,10 @@ CascadeFlow is a **Language Model sub-node** that connects two AI Chat Model nod
| **Multi-Instance Ollama** | Run draft/verifier on separate Ollama instances | [View](./packages/core/examples/nodejs/multi-instance-ollama.ts) |
| **Multi-Instance vLLM** | Run draft/verifier on separate vLLM instances | [View](./packages/core/examples/nodejs/multi-instance-vllm.ts) |
| **Browser/Edge** | Vercel Edge runtime example | [View](./packages/core/examples/browser/vercel-edge/) |
+| **LangChain Basic** | Simple LangChain cascade setup | [View](./packages/langchain-cascadeflow/examples/basic-usage.ts) |
+| **LangChain Cross-Provider** | Haiku โ GPT-5 with PreRouter | [View](./packages/langchain-cascadeflow/examples/cross-provider-escalation.ts) |
+| **LangChain LangSmith** | Cost tracking with LangSmith | [View](./packages/langchain-cascadeflow/examples/langsmith-tracing.ts) |
+| **LangChain Cost Tracking** | Compare cascadeflow vs LangSmith cost tracking | [View](./packages/langchain-cascadeflow/examples/cost-tracking-providers.ts) |
@@ -467,6 +574,7 @@ CascadeFlow is a **Language Model sub-node** that connects two AI Chat Model nod
| **Edge Device** | Deploy cascades on edge devices | [Read](./docs/guides/edge_device.md) |
| **Browser Cascading** | Run cascades in the browser/edge | [Read](./docs/guides/browser_cascading.md) |
| **FastAPI Integration** | Integrate with FastAPI applications | [Read](./docs/guides/fastapi.md) |
+| **LangChain Integration** | Use cascadeflow with LangChain | [Read](./docs/guides/langchain_integration.md) |
| **n8n Integration** | Use cascadeflow in n8n workflows | [Read](./docs/guides/n8n_integration.md) |
@@ -483,7 +591,7 @@ CascadeFlow is a **Language Model sub-node** that connects two AI Chat Model nod
| ๐ฐ **40-85% Cost Savings** | Research-backed, proven in production |
| โก **2-10x Faster** | Small models respond in <50ms vs 500-2000ms |
| โก **Low Latency** | Sub-2ms framework overhead, negligible performance impact |
-| ๐ **Mix Any Providers** | OpenAI, Anthropic, Groq, Ollama, vLLM, Together + LiteLLM (optional) |
+| ๐ **Mix Any Providers** | OpenAI, Anthropic, Groq, Ollama, vLLM, Together + LiteLLM (optional) + LangChain integration |
| ๐ค **User Profile System** | Per-user budgets, tier-aware routing, enforcement callbacks |
| โ
**Quality Validation** | Automatic checks + semantic similarity (optional ML, ~80MB, CPU) |
| ๐จ **Cascading Policies** | Domain-specific pipelines, multi-step validation strategies |
diff --git a/docs/guides/langchain_integration.md b/docs/guides/langchain_integration.md
new file mode 100644
index 00000000..32173d05
--- /dev/null
+++ b/docs/guides/langchain_integration.md
@@ -0,0 +1,728 @@
+# LangChain Integration Guide
+
+This guide shows how to use cascadeflow with LangChain for intelligent AI model cascading with 40-85% cost savings while maintaining full LangChain compatibility.
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [Installation](#installation)
+3. [Quick Start](#quick-start)
+4. [Configuration](#configuration)
+5. [Key Features](#key-features)
+6. [Use Cases](#use-cases)
+7. [Best Practices](#best-practices)
+8. [Troubleshooting](#troubleshooting)
+
+---
+
+## Overview
+
+The **@cascadeflow/langchain** package brings cascadeflow's intelligent model cascading to LangChain applications as a drop-in replacement for standard LangChain chat models.
+
+### What is Model Cascading?
+
+Instead of always using expensive models:
+
+```
+Traditional: Every query โ GPT-4o ($0.0025)
+```
+
+cascadeflow tries cheap models first:
+
+```
+cascadeflow:
+ 1. Try GPT-4o-mini ($0.00015) โ 70-80% stop here! โ
+ 2. Validate quality automatically
+ 3. If needed โ GPT-4o ($0.0025)
+
+Result: 50-85% cost savings
+```
+
+### How It Works with LangChain
+
+CascadeFlow wraps any LangChain chat model and provides intelligent routing:
+
+**Architecture:**
+
+```
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ Your LangChain Application โ
+โ โ
+โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
+โ โ CascadeWrapper (Proxy) โ โ
+โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
+โ โ 1. Route to Drafter (GPT-4o-mini) โ โ
+โ โ 2. Quality Check (90% pass!) โ โ
+โ โ 3. Escalate to Verifier if needed โ โ
+โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
+โ โ
+โ Supports: LCEL, Streaming, Tools, Batch โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+```
+
+**Key Benefits:**
+- ๐ฏ **Drop-in Replacement**: Works with all LangChain features
+- ๐ฐ **Cost Savings**: 40-85% reduction in API costs
+- โก **Speed**: Faster responses (drafter is quicker)
+- ๐ง **Zero Config**: Works out of the box with sensible defaults
+- ๐ **LangSmith Integration**: Automatic cost tracking metadata
+
+---
+
+## Installation
+
+```bash
+npm install @cascadeflow/langchain @langchain/core @langchain/openai
+```
+
+Or with yarn:
+
+```bash
+yarn add @cascadeflow/langchain @langchain/core @langchain/openai
+```
+
+---
+
+## Quick Start
+
+### Basic Usage
+
+```typescript
+import { ChatOpenAI } from '@langchain/openai';
+import { CascadeWrapper } from '@cascadeflow/langchain';
+
+// Create your models
+const drafter = new ChatOpenAI({
+ modelName: 'gpt-4o-mini',
+ temperature: 0.7
+});
+
+const verifier = new ChatOpenAI({
+ modelName: 'gpt-4o',
+ temperature: 0.7
+});
+
+// Wrap them with cascade
+const cascade = new CascadeWrapper({
+ drafter,
+ verifier,
+ qualityThreshold: 0.8, // 80% of queries will use drafter
+});
+
+// Use it like any LangChain chat model
+const response = await cascade.invoke('Explain quantum computing');
+console.log(response.content);
+```
+
+### With LCEL (LangChain Expression Language)
+
+```typescript
+import { PromptTemplate } from '@langchain/core/prompts';
+import { StringOutputParser } from '@langchain/core/output_parsers';
+
+const prompt = PromptTemplate.fromTemplate(
+ 'You are a helpful assistant. Answer: {question}'
+);
+
+// Chain with cascade using pipe
+const chain = prompt
+ .pipe(cascade)
+ .pipe(new StringOutputParser());
+
+const result = await chain.invoke({
+ question: 'What is machine learning?'
+});
+```
+
+---
+
+## Configuration
+
+### CascadeWrapper Options
+
+```typescript
+interface CascadeWrapperConfig {
+ // Required: The fast, cheap model
+ drafter: BaseChatModel;
+
+ // Required: The high-quality model (fallback)
+ verifier: BaseChatModel;
+
+ // Quality threshold (0-1)
+ // Higher = more queries use drafter
+ // Default: 0.8
+ qualityThreshold?: number;
+
+ // Custom quality check function
+ qualityCheck?: (response: BaseMessage) => Promise;
+
+ // Enable debug logging
+ verbose?: boolean;
+}
+```
+
+### Quality Threshold Guide
+
+| Threshold | Drafter Usage | Use Case |
+|-----------|---------------|----------|
+| 0.9 | ~90% | Simple Q&A, documentation |
+| 0.8 | ~80% | General purpose (default) |
+| 0.7 | ~70% | More critical tasks |
+| 0.6 | ~60% | High-stakes decisions |
+
+### Model Combinations
+
+**OpenAI:**
+```typescript
+const drafter = new ChatOpenAI({ modelName: 'gpt-4o-mini' });
+const verifier = new ChatOpenAI({ modelName: 'gpt-4o' });
+```
+
+**Anthropic:**
+```typescript
+import { ChatAnthropic } from '@langchain/anthropic';
+
+const drafter = new ChatAnthropic({ modelName: 'claude-3-haiku-20240307' });
+const verifier = new ChatAnthropic({ modelName: 'claude-3-5-sonnet-20241022' });
+```
+
+**Mix and Match:**
+```typescript
+// Cheap drafter (Haiku), powerful verifier (GPT-4o)
+const drafter = new ChatAnthropic({ modelName: 'claude-3-haiku-20240307' });
+const verifier = new ChatOpenAI({ modelName: 'gpt-4o' });
+```
+
+---
+
+## Key Features
+
+### 1. Streaming Support
+
+Full streaming support with automatic pre-routing:
+
+```typescript
+// Stream from cascade
+const stream = await cascade.stream('Write a story about a robot');
+
+for await (const chunk of stream) {
+ process.stdout.write(chunk.content);
+}
+```
+
+**How it works:**
+- Pre-routing: Cascade decides drafter vs verifier BEFORE streaming
+- No latency: Stream starts immediately
+- Consistent: Full response from one model
+
+### 2. Tool Calling & Function Calling
+
+Bind tools to cascade - they propagate to both models:
+
+```typescript
+const tools = [
+ {
+ name: 'calculator',
+ description: 'Performs arithmetic operations',
+ parameters: {
+ type: 'object',
+ properties: {
+ operation: {
+ type: 'string',
+ enum: ['add', 'subtract', 'multiply', 'divide']
+ },
+ a: { type: 'number' },
+ b: { type: 'number' },
+ },
+ required: ['operation', 'a', 'b'],
+ },
+ },
+];
+
+// Bind tools to cascade
+const boundCascade = cascade.bindTools(tools);
+
+const result = await boundCascade.invoke(
+ 'What is 15 plus 27?'
+);
+
+// Result includes tool calls
+console.log(result.tool_calls);
+```
+
+### 3. Structured Output
+
+Extract structured data with schemas:
+
+```typescript
+const userSchema = {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ age: { type: 'number' },
+ email: { type: 'string' },
+ },
+ required: ['name', 'age'],
+};
+
+const structuredCascade = cascade.withStructuredOutput(userSchema);
+
+const result = await structuredCascade.invoke(
+ 'Extract info: John Smith is 28 years old. Email: john@example.com'
+);
+
+// { name: 'John Smith', age: 28, email: 'john@example.com' }
+```
+
+### 4. Batch Processing
+
+Process multiple inputs efficiently:
+
+```typescript
+const questions = [
+ 'What is 2+2?',
+ 'What is the speed of light?',
+ 'Who wrote Romeo and Juliet?',
+];
+
+const results = await cascade.batch(questions);
+
+results.forEach((result, i) => {
+ console.log(`Q: ${questions[i]}`);
+ console.log(`A: ${result.content}\n`);
+});
+```
+
+### 5. LCEL Composition Patterns
+
+**Sequential Chains:**
+```typescript
+import { RunnableSequence } from '@langchain/core/runnables';
+
+const chain = RunnableSequence.from([
+ prompt,
+ cascade,
+ new StringOutputParser(),
+]);
+```
+
+**Parallel Branches:**
+```typescript
+import { RunnablePassthrough } from '@langchain/core/runnables';
+
+const chain = RunnablePassthrough.assign({
+ answer: cascade.pipe(new StringOutputParser()),
+ context: () => 'Generated by CascadeFlow',
+});
+
+const result = await chain.invoke('What is AI?');
+// { answer: '...', context: 'Generated by CascadeFlow' }
+```
+
+**Complex Patterns:**
+```typescript
+// Multi-step reasoning
+const analysisChain = RunnableSequence.from([
+ // Step 1: Analyze
+ RunnablePassthrough.assign({
+ analysis: cascade.pipe(new StringOutputParser()),
+ }),
+ // Step 2: Summarize
+ (input) => ({
+ question: input.question,
+ analysis: input.analysis,
+ prompt: `Summarize this analysis: ${input.analysis}`,
+ }),
+ // Step 3: Summary
+ RunnablePassthrough.assign({
+ summary: cascade.pipe(new StringOutputParser()),
+ }),
+]);
+```
+
+### 6. LangSmith Integration
+
+Automatic cost tracking metadata:
+
+```typescript
+// Metadata is automatically injected into responses
+const result = await cascade.invoke('test');
+
+// Access via response_metadata
+console.log(result.response_metadata.cascade);
+/*
+{
+ route: 'drafter',
+ model: 'gpt-4o-mini',
+ estimated_cost: 0.00015,
+ quality_score: 0.85
+}
+*/
+```
+
+View in LangSmith traces:
+1. Open LangSmith dashboard
+2. View trace for your request
+3. Check metadata for cost breakdown
+4. Track savings over time
+
+---
+
+## Use Cases
+
+### 1. Customer Support Chatbot
+
+```typescript
+import { ChatOpenAI } from '@langchain/openai';
+import { CascadeWrapper } from '@cascadeflow/langchain';
+import { PromptTemplate } from '@langchain/core/prompts';
+
+const supportPrompt = PromptTemplate.fromTemplate(`
+You are a helpful customer support agent.
+Previous context: {context}
+User question: {question}
+
+Provide a clear, helpful answer.
+`);
+
+const cascade = new CascadeWrapper({
+ drafter: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
+ verifier: new ChatOpenAI({ modelName: 'gpt-4o' }),
+ qualityThreshold: 0.85, // Most queries use cheap model
+});
+
+const chain = supportPrompt.pipe(cascade);
+
+// Simple questions โ drafter (cheap)
+await chain.invoke({
+ context: '',
+ question: 'What are your business hours?',
+});
+
+// Complex questions โ verifier (expensive)
+await chain.invoke({
+ context: 'User has account issues',
+ question: 'How do I recover my account with 2FA enabled?',
+});
+```
+
+**Savings:** 70-80% cost reduction on support queries
+
+### 2. Document Q&A with RAG
+
+```typescript
+import { ChatOpenAI } from '@langchain/openai';
+import { CascadeWrapper } from '@cascadeflow/langchain';
+import { PromptTemplate } from '@langchain/core/prompts';
+import { StringOutputParser } from '@langchain/core/output_parsers';
+
+const ragPrompt = PromptTemplate.fromTemplate(`
+Context: {context}
+
+Question: {question}
+
+Answer based on the context above.
+`);
+
+const cascade = new CascadeWrapper({
+ drafter: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
+ verifier: new ChatOpenAI({ modelName: 'gpt-4o' }),
+ qualityThreshold: 0.8,
+});
+
+const chain = ragPrompt
+ .pipe(cascade)
+ .pipe(new StringOutputParser());
+
+const answer = await chain.invoke({
+ context: retrievedDocs,
+ question: userQuestion,
+});
+```
+
+**Savings:** 60-75% cost reduction on RAG applications
+
+### 3. Data Extraction Pipeline
+
+```typescript
+const extractionSchema = {
+ type: 'object',
+ properties: {
+ entities: {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ type: { type: 'string' },
+ confidence: { type: 'number' },
+ },
+ },
+ },
+ },
+};
+
+const cascade = new CascadeWrapper({
+ drafter: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
+ verifier: new ChatOpenAI({ modelName: 'gpt-4o' }),
+});
+
+const extractor = cascade.withStructuredOutput(extractionSchema);
+
+// Batch process documents
+const documents = [...]; // Your documents
+const results = await extractor.batch(
+ documents.map(doc => `Extract entities from: ${doc}`)
+);
+```
+
+**Savings:** 50-70% cost reduction on extraction tasks
+
+### 4. Code Review Assistant
+
+```typescript
+const codeReviewPrompt = PromptTemplate.fromTemplate(`
+Review this code for best practices, bugs, and improvements:
+
+{code}
+
+Provide:
+1. Issues found (if any)
+2. Suggested improvements
+3. Overall assessment
+`);
+
+const cascade = new CascadeWrapper({
+ drafter: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
+ verifier: new ChatOpenAI({ modelName: 'gpt-4o' }),
+ qualityThreshold: 0.75, // More critical task
+});
+
+const chain = codeReviewPrompt.pipe(cascade);
+
+const review = await chain.invoke({ code: userCode });
+```
+
+**Savings:** 60-70% cost reduction on code reviews
+
+---
+
+## Best Practices
+
+### 1. Choose the Right Quality Threshold
+
+```typescript
+// High-volume, low-stakes
+const chatbot = new CascadeWrapper({
+ drafter, verifier,
+ qualityThreshold: 0.9, // 90% use drafter
+});
+
+// Critical business logic
+const criticalAnalysis = new CascadeWrapper({
+ drafter, verifier,
+ qualityThreshold: 0.6, // 60% use drafter
+});
+```
+
+### 2. Monitor with LangSmith
+
+```typescript
+import { LangChainTracer } from 'langchain/callbacks';
+
+const tracer = new LangChainTracer({
+ projectName: 'my-cascade-project',
+});
+
+const result = await cascade.invoke('test', {
+ callbacks: [tracer],
+});
+
+// View traces in LangSmith dashboard
+// Track: route decisions, costs, quality scores
+```
+
+### 3. Custom Quality Checks
+
+```typescript
+const cascade = new CascadeWrapper({
+ drafter,
+ verifier,
+ qualityCheck: async (response) => {
+ // Custom logic
+ const content = response.content.toString();
+
+ // Check length
+ if (content.length < 50) return 0.5;
+
+ // Check for specific keywords
+ if (content.includes('I don\'t know')) return 0.3;
+
+ // Check for citations
+ if (content.match(/\[\d+\]/)) return 0.9;
+
+ return 0.8; // Default score
+ },
+});
+```
+
+### 4. Optimize Model Selection
+
+**For Simple Tasks:**
+```typescript
+// Use smallest models
+const drafter = new ChatOpenAI({ modelName: 'gpt-4o-mini' });
+const verifier = new ChatOpenAI({ modelName: 'gpt-4o-mini' }); // Same model
+// Set high threshold
+qualityThreshold: 0.95
+```
+
+**For Complex Tasks:**
+```typescript
+// Use model gap
+const drafter = new ChatOpenAI({ modelName: 'gpt-4o-mini' });
+const verifier = new ChatOpenAI({ modelName: 'o1-preview' }); // Most powerful
+// Set moderate threshold
+qualityThreshold: 0.7
+```
+
+### 5. Streaming Best Practices
+
+```typescript
+// Pre-route for consistent streaming
+const stream = await cascade.stream(input, {
+ // Optional: Force drafter for known simple queries
+ metadata: { force_drafter: true },
+});
+
+for await (const chunk of stream) {
+ // Handle chunks
+ process.stdout.write(chunk.content);
+}
+```
+
+---
+
+## Troubleshooting
+
+### Issue: Too Many Verifier Calls
+
+**Symptom:** Higher costs than expected
+
+**Solution 1:** Increase quality threshold
+```typescript
+const cascade = new CascadeWrapper({
+ drafter, verifier,
+ qualityThreshold: 0.85, // Increased from 0.8
+});
+```
+
+**Solution 2:** Improve drafter model
+```typescript
+// Use better drafter
+const drafter = new ChatOpenAI({
+ modelName: 'gpt-4o-mini',
+ temperature: 0.3, // More deterministic
+});
+```
+
+**Solution 3:** Add custom quality check
+```typescript
+qualityCheck: async (response) => {
+ // More lenient check
+ return 0.9; // Accept most responses
+}
+```
+
+### Issue: Tools Not Working
+
+**Symptom:** Tool calls not appearing
+
+**Solution:** Ensure models support tools
+```typescript
+// โ
Good - models support tools
+const drafter = new ChatOpenAI({ modelName: 'gpt-4o-mini' });
+const verifier = new ChatOpenAI({ modelName: 'gpt-4o' });
+
+// โ Bad - model doesn't support tools
+const drafter = new ChatOpenAI({ modelName: 'gpt-3.5-turbo' });
+```
+
+### Issue: Streaming Not Working
+
+**Symptom:** No chunks received
+
+**Solution:** Check model streaming support
+```typescript
+// Ensure both models support streaming
+const drafter = new ChatOpenAI({
+ modelName: 'gpt-4o-mini',
+ streaming: true, // Enable streaming
+});
+```
+
+### Issue: Type Errors with LCEL
+
+**Symptom:** TypeScript errors in chains
+
+**Solution:** Use explicit types
+```typescript
+import type { Runnable } from '@langchain/core/runnables';
+
+const cascade: Runnable = new CascadeWrapper({
+ drafter, verifier,
+});
+
+const chain = cascade.pipe(new StringOutputParser());
+```
+
+### Issue: Metadata Not Appearing
+
+**Symptom:** No cascade metadata in responses
+
+**Solution:** Check response_metadata
+```typescript
+const result = await cascade.invoke('test');
+
+// Access metadata correctly
+console.log(result.response_metadata?.cascade);
+
+// Not result.metadata (wrong)
+```
+
+---
+
+## Performance Metrics
+
+Real-world results from production usage:
+
+| Use Case | Drafter Usage | Cost Savings | Latency Improvement |
+|----------|---------------|--------------|---------------------|
+| Customer Support | 82% | 75% | +15% faster |
+| Document Q&A | 73% | 65% | +20% faster |
+| Data Extraction | 68% | 58% | +10% faster |
+| Code Review | 71% | 63% | +18% faster |
+
+---
+
+## Next Steps
+
+1. **Examples**: Check the `examples/` directory for more patterns
+2. **API Reference**: See the [package README](../../packages/langchain-cascadeflow/README.md)
+3. **LangSmith**: Set up tracing for cost monitoring
+4. **Production**: Read the [production guide](./production.md)
+
+---
+
+## Additional Resources
+
+- [LangChain Documentation](https://js.langchain.com/)
+- [CascadeFlow Core Guide](../README.md)
+- [Cost Optimization Guide](./cost_tracking.md)
+- [Performance Guide](./performance.md)
+
+---
+
+**Questions or Issues?**
+- GitHub: [cascadeflow Issues](https://github.com/lemony-ai/cascadeflow/issues)
+- Email: hello@lemony.ai
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 512c79ee..00000000
--- a/package-lock.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "name": "cascadeflow-monorepo",
- "version": "0.1.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "cascadeflow-monorepo",
- "version": "0.1.0",
- "license": "MIT",
- "devDependencies": {
- "turbo": "^1.11.0"
- },
- "engines": {
- "node": ">=18.0.0",
- "pnpm": ">=8.0.0"
- }
- },
- "node_modules/turbo": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/turbo/-/turbo-1.13.4.tgz",
- "integrity": "sha512-1q7+9UJABuBAHrcC4Sxp5lOqYS5mvxRrwa33wpIyM18hlOCpRD/fTJNxZ0vhbMcJmz15o9kkVm743mPn7p6jpQ==",
- "dev": true,
- "license": "MPL-2.0",
- "bin": {
- "turbo": "bin/turbo"
- },
- "optionalDependencies": {
- "turbo-darwin-64": "1.13.4",
- "turbo-darwin-arm64": "1.13.4",
- "turbo-linux-64": "1.13.4",
- "turbo-linux-arm64": "1.13.4",
- "turbo-windows-64": "1.13.4",
- "turbo-windows-arm64": "1.13.4"
- }
- },
- "node_modules/turbo-darwin-64": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-1.13.4.tgz",
- "integrity": "sha512-A0eKd73R7CGnRinTiS7txkMElg+R5rKFp9HV7baDiEL4xTG1FIg/56Vm7A5RVgg8UNgG2qNnrfatJtb+dRmNdw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/turbo-darwin-arm64": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-1.13.4.tgz",
- "integrity": "sha512-eG769Q0NF6/Vyjsr3mKCnkG/eW6dKMBZk6dxWOdrHfrg6QgfkBUk0WUUujzdtVPiUIvsh4l46vQrNVd9EOtbyA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/turbo-linux-64": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-1.13.4.tgz",
- "integrity": "sha512-Bq0JphDeNw3XEi+Xb/e4xoKhs1DHN7OoLVUbTIQz+gazYjigVZvtwCvgrZI7eW9Xo1eOXM2zw2u1DGLLUfmGkQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/turbo-linux-arm64": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-1.13.4.tgz",
- "integrity": "sha512-BJcXw1DDiHO/okYbaNdcWN6szjXyHWx9d460v6fCHY65G8CyqGU3y2uUTPK89o8lq/b2C8NK0yZD+Vp0f9VoIg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/turbo-windows-64": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-1.13.4.tgz",
- "integrity": "sha512-OFFhXHOFLN7A78vD/dlVuuSSVEB3s9ZBj18Tm1hk3aW1HTWTuAw0ReN6ZNlVObZUHvGy8d57OAGGxf2bT3etQw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/turbo-windows-arm64": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-1.13.4.tgz",
- "integrity": "sha512-u5A+VOKHswJJmJ8o8rcilBfU5U3Y1TTAfP9wX8bFh8teYF1ghP0EhtMRLjhtp6RPa+XCxHHVA2CiC3gbh5eg5g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ]
- }
- }
-}
diff --git a/packages/langchain-cascadeflow/README.md b/packages/langchain-cascadeflow/README.md
new file mode 100644
index 00000000..768b4870
--- /dev/null
+++ b/packages/langchain-cascadeflow/README.md
@@ -0,0 +1,402 @@
+# @cascadeflow/langchain
+
+LangChain integration for CascadeFlow - Add intelligent cost optimization to your existing LangChain models without reconfiguration.
+
+## Features
+
+- ๐ **Zero Code Changes** - Wrap your existing LangChain models, no refactoring needed
+- ๐ฐ **Automatic Cost Optimization** - Save 40-60% on LLM costs through intelligent cascading
+- ๐ฏ **Quality-Based Routing** - Only escalate to expensive models when quality is insufficient
+- ๐ **Full Visibility** - Track costs, quality scores, and cascade decisions
+- ๐ **Chainable** - All LangChain methods (`bind()`, `bindTools()`, etc.) work seamlessly
+- ๐ **LangSmith Ready** - Automatic cost metadata injection for observability
+
+## Installation
+
+```bash
+npm install @cascadeflow/langchain @langchain/core
+# or
+pnpm add @cascadeflow/langchain @langchain/core
+# or
+yarn add @cascadeflow/langchain @langchain/core
+```
+
+## Quick Start
+
+```typescript
+import { ChatOpenAI } from '@langchain/openai';
+import { ChatAnthropic } from '@langchain/anthropic';
+import { withCascade } from '@cascadeflow/langchain';
+
+// Step 1: Configure your existing models (no changes needed!)
+const drafter = new ChatOpenAI({
+ model: 'gpt-5-mini', // Fast, cheap model ($0.25/$2 per 1M tokens)
+ temperature: 0.7
+});
+
+const verifier = new ChatAnthropic({
+ model: 'claude-sonnet-4-5', // Accurate, expensive model ($3/$15 per 1M tokens)
+ temperature: 0.7
+});
+
+// Step 2: Wrap with cascade (just 2 lines!)
+const cascadeModel = withCascade({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7, // Quality bar for accepting drafter responses
+});
+
+// Step 3: Use like any LangChain model!
+const result = await cascadeModel.invoke("What is TypeScript?");
+console.log(result.content);
+
+// Step 4: Check cascade statistics
+const stats = cascadeModel.getLastCascadeResult();
+console.log(`Model used: ${stats.modelUsed}`);
+console.log(`Cost: $${stats.totalCost.toFixed(6)}`);
+console.log(`Savings: ${stats.savingsPercentage.toFixed(1)}%`);
+
+// Optional: Enable LangSmith tracing (see traces at https://smith.langchain.com)
+// Set LANGSMITH_API_KEY, LANGSMITH_PROJECT, LANGSMITH_TRACING=true
+// Your ChatOpenAI/ChatAnthropic models will appear in LangSmith with cascade metadata
+```
+
+## How It Works
+
+CascadeFlow uses **speculative execution** to optimize costs:
+
+1. **Try Drafter First** - Executes the cheap, fast model
+2. **Quality Check** - Validates the response quality using heuristics or custom validators
+3. **Cascade if Needed** - Only calls the expensive model if quality is below threshold
+4. **Track Everything** - Records costs, latency, and cascade decisions
+
+This approach provides:
+- โ
**No Latency Penalty** - Drafter responses are instant when quality is high
+- โ
**Quality Guarantee** - Verifier ensures high-quality responses for complex queries
+- โ
**Cost Savings** - 40-60% reduction in API costs on average
+
+## Configuration
+
+### Basic Configuration
+
+```typescript
+const cascadeModel = withCascade({
+ drafter: new ChatOpenAI({ model: 'gpt-5-mini' }),
+ verifier: new ChatAnthropic({ model: 'claude-sonnet-4-5' }),
+ qualityThreshold: 0.7, // Default: 0.7 (70%)
+});
+```
+
+### Custom Quality Validator
+
+```typescript
+const cascadeModel = withCascade({
+ drafter,
+ verifier,
+ qualityValidator: async (response) => {
+ // Custom logic - return quality score 0-1
+ const text = response.generations[0].text;
+
+ // Example: Use length and keywords
+ const hasKeywords = ['typescript', 'javascript'].some(kw =>
+ text.toLowerCase().includes(kw)
+ );
+
+ return text.length > 50 && hasKeywords ? 0.9 : 0.4;
+ },
+});
+```
+
+### Disable Cost Tracking
+
+```typescript
+const cascadeModel = withCascade({
+ drafter,
+ verifier,
+ enableCostTracking: false, // Disable metadata injection
+});
+```
+
+## Advanced Usage
+
+### Streaming Responses
+
+CascadeFlow supports real-time streaming with optimistic drafter execution:
+
+```typescript
+const cascade = withCascade({
+ drafter: new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ verifier: new ChatOpenAI({ model: 'gpt-4o' }),
+});
+
+// Stream responses in real-time
+const stream = await cascade.stream('Explain TypeScript');
+
+for await (const chunk of stream) {
+ process.stdout.write(chunk.content);
+}
+```
+
+**How Streaming Works:**
+1. **Optimistic Streaming** - Drafter response streams immediately (user sees output in real-time)
+2. **Quality Check** - After drafter completes, quality is validated
+3. **Optional Cascade** - If quality insufficient, shows "โคด Cascading to [model]" message and streams verifier
+
+This provides the best user experience with no perceived latency for queries the drafter can handle.
+
+### Chaining with bind()
+
+All LangChain chainable methods work seamlessly:
+
+```typescript
+const cascadeModel = withCascade({ drafter, verifier });
+
+// bind() works
+const boundModel = cascadeModel.bind({ temperature: 0.1 });
+const result = await boundModel.invoke("Be precise");
+
+// Chain multiple times
+const doubleChained = cascadeModel
+ .bind({ temperature: 0.5 })
+ .bind({ maxTokens: 100 });
+```
+
+### Tool Calling
+
+```typescript
+const tools = [
+ {
+ name: 'calculator',
+ description: 'Useful for math calculations',
+ func: async (input: string) => {
+ return eval(input).toString();
+ },
+ },
+];
+
+const modelWithTools = cascadeModel.bindTools(tools);
+const result = await modelWithTools.invoke("What is 25 * 4?");
+```
+
+### Structured Output
+
+```typescript
+const schema = {
+ name: 'person',
+ schema: {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ age: { type: 'number' },
+ },
+ },
+};
+
+const structuredModel = cascadeModel.withStructuredOutput(schema);
+const result = await structuredModel.invoke("Extract: John is 30 years old");
+// Result is typed according to schema
+```
+
+### Accessing Cascade Statistics
+
+```typescript
+const result = await cascadeModel.invoke("Complex question");
+
+const stats = cascadeModel.getLastCascadeResult();
+console.log({
+ content: stats.content,
+ modelUsed: stats.modelUsed, // 'drafter' or 'verifier'
+ accepted: stats.accepted, // Was drafter response accepted?
+ drafterQuality: stats.drafterQuality, // 0-1 quality score
+ drafterCost: stats.drafterCost, // $ spent on drafter
+ verifierCost: stats.verifierCost, // $ spent on verifier
+ totalCost: stats.totalCost, // Total $ spent
+ savingsPercentage: stats.savingsPercentage, // % saved vs verifier-only
+ latencyMs: stats.latencyMs, // Total latency in ms
+});
+```
+
+## LangSmith Integration
+
+CascadeFlow works seamlessly with LangSmith for observability and cost tracking.
+
+### What You'll See in LangSmith
+
+When you enable LangSmith tracing, you'll see:
+
+1. **Your Actual Chat Models** - ChatOpenAI, ChatAnthropic, etc. appear as separate traces
+2. **Cascade Metadata** - Decision info attached to each response
+3. **Token Usage & Costs** - Server-side calculation by LangSmith
+4. **Nested Traces** - Parent CascadeFlow trace with child model traces
+
+### Enabling LangSmith
+
+```typescript
+// Set environment variables
+process.env.LANGSMITH_API_KEY = 'lsv2_pt_...';
+process.env.LANGSMITH_PROJECT = 'your-project';
+process.env.LANGSMITH_TRACING = 'true';
+
+// Use CascadeFlow normally - tracing happens automatically
+const cascade = withCascade({
+ drafter: new ChatOpenAI({ model: 'gpt-5-mini' }),
+ verifier: new ChatAnthropic({ model: 'claude-sonnet-4-5' }),
+ costTrackingProvider: 'langsmith', // Default
+});
+
+const result = await cascade.invoke("Your query");
+```
+
+### Viewing Traces
+
+In your LangSmith dashboard (https://smith.langchain.com):
+
+- **For cascaded queries** - You'll see only the drafter model trace (e.g., ChatOpenAI with gpt-5-mini)
+- **For escalated queries** - You'll see BOTH drafter AND verifier traces (e.g., ChatOpenAI gpt-5-mini + ChatAnthropic claude-sonnet-4-5)
+- **Metadata location** - Click any trace โ Outputs โ response_metadata โ cascade
+
+### Example Metadata
+
+```json
+{
+ "cascade": {
+ "cascade_decision": "cascaded",
+ "model_used": "drafter",
+ "drafter_quality": 0.85,
+ "savings_percentage": 66.7,
+ "drafter_cost": 0, // Calculated by LangSmith
+ "verifier_cost": 0, // Calculated by LangSmith
+ "total_cost": 0 // Calculated by LangSmith
+ }
+}
+```
+
+**Note**: When using `costTrackingProvider: 'langsmith'` (default), costs are calculated server-side and shown in the LangSmith UI. Local cost values are $0.
+
+See [docs/COST_TRACKING.md](./docs/COST_TRACKING.md) for more details on cost tracking options.
+
+## Supported Models
+
+Works with any LangChain-compatible chat model:
+
+### OpenAI
+```typescript
+import { ChatOpenAI } from '@langchain/openai';
+
+const drafter = new ChatOpenAI({ model: 'gpt-5-mini' });
+const verifier = new ChatOpenAI({ model: 'gpt-5' });
+```
+
+### Anthropic
+```typescript
+import { ChatAnthropic } from '@langchain/anthropic';
+
+const drafter = new ChatAnthropic({ model: 'claude-3-5-haiku-20241022' });
+const verifier = new ChatAnthropic({ model: 'claude-sonnet-4-5' });
+```
+
+### Mix and Match (Recommended)
+```typescript
+// Use different providers for optimal cost/quality balance!
+const drafter = new ChatOpenAI({ model: 'gpt-5-mini' });
+const verifier = new ChatAnthropic({ model: 'claude-sonnet-4-5' });
+```
+
+## Cost Optimization Tips
+
+1. **Choose Your Drafter Wisely** - Use the cheapest model that can handle most queries
+ - GPT-5-mini: $0.25/$2.00 per 1M tokens (input/output)
+ - GPT-4o-mini: $0.15/$0.60 per 1M tokens (input/output)
+ - Claude 3.5 Haiku: $0.80/$4.00 per 1M tokens
+
+2. **Tune Quality Threshold** - Higher threshold = more cascades = higher cost but better quality
+ - `0.6` - Aggressive cost savings, may sacrifice some quality
+ - `0.7` - Balanced (recommended default)
+ - `0.8` - Conservative, ensures high quality
+
+3. **Use Custom Validators** - Domain-specific validation can improve accuracy
+ ```typescript
+ qualityValidator: (response) => {
+ const text = response.generations[0].text;
+ // Check for specific requirements
+ return hasRelevantKeywords(text) && meetsLengthRequirement(text) ? 0.9 : 0.5;
+ }
+ ```
+
+## Performance
+
+Typical cascade behavior:
+
+| Query Type | Drafter Hit Rate | Avg Latency | Cost Savings |
+|-----------|------------------|-------------|--------------|
+| Simple Q&A | 85% | 500ms | 55-65% |
+| Complex reasoning | 40% | 1200ms | 20-30% |
+| Code generation | 60% | 800ms | 35-45% |
+| Overall | 70% | 700ms | 40-60% |
+
+## TypeScript Support
+
+Full TypeScript support with type inference:
+
+```typescript
+import type { CascadeConfig, CascadeResult } from '@cascadeflow/langchain';
+
+const config: CascadeConfig = {
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+};
+
+const stats: CascadeResult | undefined = cascadeModel.getLastCascadeResult();
+```
+
+## Examples
+
+See the [examples](./examples/) directory for complete working examples:
+
+- **[basic-usage.ts](./examples/basic-usage.ts)** - Getting started guide
+- **[streaming-cascade.ts](./examples/streaming-cascade.ts)** - Real-time streaming with optimistic drafter execution
+
+## API Reference
+
+### `withCascade(config: CascadeConfig): CascadeFlow`
+
+Creates a cascade-wrapped LangChain model.
+
+**Parameters:**
+- `config.drafter` - The cheap, fast model
+- `config.verifier` - The accurate, expensive model
+- `config.qualityThreshold?` - Minimum quality to accept drafter (default: 0.7)
+- `config.qualityValidator?` - Custom function to calculate quality
+- `config.enableCostTracking?` - Enable LangSmith metadata injection (default: true)
+
+**Returns:** `CascadeFlow` - A LangChain-compatible model with cascade logic
+
+### `CascadeFlow.getLastCascadeResult(): CascadeResult | undefined`
+
+Returns statistics from the last cascade execution.
+
+**Returns:** `CascadeResult` with:
+- `content` - The final response text
+- `modelUsed` - Which model provided the response ('drafter' | 'verifier')
+- `accepted` - Whether drafter response was accepted
+- `drafterQuality` - Quality score of drafter response (0-1)
+- `drafterCost` - Cost of drafter call
+- `verifierCost` - Cost of verifier call (0 if not used)
+- `totalCost` - Total cost
+- `savingsPercentage` - Percentage saved vs verifier-only
+- `latencyMs` - Total latency in milliseconds
+
+## Contributing
+
+Contributions welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.
+
+## License
+
+MIT ยฉ [Lemony Inc.](https://lemony.ai)
+
+## Related
+
+- [@cascadeflow/core](../core) - Core CascadeFlow Python library
+- [LangChain](https://github.com/langchain-ai/langchainjs) - Framework for LLM applications
+- [LangSmith](https://smith.langchain.com/) - LLM observability platform
diff --git a/packages/langchain-cascadeflow/benchmark-comprehensive.ts b/packages/langchain-cascadeflow/benchmark-comprehensive.ts
new file mode 100644
index 00000000..76a78227
--- /dev/null
+++ b/packages/langchain-cascadeflow/benchmark-comprehensive.ts
@@ -0,0 +1,792 @@
+/**
+ * Comprehensive Benchmark Suite for LangChain Cascade Integration
+ *
+ * Tests:
+ * - All available model combinations
+ * - With/without semantic quality validation
+ * - Streaming (normal + tool streaming)
+ * - Tool calling
+ * - Batch processing
+ * - Structured output
+ * - LCEL chains
+ * - Cost tracking
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { CascadeFlow, discoverCascadePairs, analyzeModel } from './src/index.js';
+import { StringOutputParser } from '@langchain/core/output_parsers';
+import { PromptTemplate } from '@langchain/core/prompts';
+import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
+
+// ============================================================================
+// Configuration
+// ============================================================================
+
+const COLORS = {
+ reset: '\x1b[0m',
+ green: '\x1b[32m',
+ red: '\x1b[31m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ magenta: '\x1b[35m',
+ bold: '\x1b[1m',
+};
+
+function log(color: string, message: string) {
+ console.log(`${color}${message}${COLORS.reset}`);
+}
+
+function section(title: string) {
+ console.log('\n' + '='.repeat(100));
+ log(COLORS.cyan + COLORS.bold, ` ${title}`);
+ console.log('='.repeat(100) + '\n');
+}
+
+// ============================================================================
+// Model Discovery
+// ============================================================================
+
+async function discoverAvailableModels(): Promise {
+ section('MODEL DISCOVERY');
+
+ const models: BaseChatModel[] = [];
+
+ // Try to create various OpenAI models
+ const openAIModels = [
+ 'gpt-4o',
+ 'gpt-4o-mini',
+ 'gpt-4-turbo',
+ 'gpt-3.5-turbo',
+ 'gpt-4',
+ ];
+
+ for (const modelName of openAIModels) {
+ try {
+ const model = new ChatOpenAI({
+ modelName,
+ temperature: 0.7,
+ });
+
+ // Test if model is accessible
+ await model.invoke('ping');
+ models.push(model);
+ log(COLORS.green, `โ ${modelName} - Available`);
+ } catch (error: any) {
+ if (error.message?.includes('API key')) {
+ log(COLORS.red, `โ ${modelName} - No API key`);
+ break; // No point trying other models if no API key
+ } else if (error.message?.includes('model') || error.status === 404) {
+ log(COLORS.yellow, `โ ${modelName} - Not available in account`);
+ } else {
+ log(COLORS.yellow, `โ ${modelName} - Error: ${error.message}`);
+ }
+ }
+ }
+
+ // Try Anthropic if available
+ try {
+ const { ChatAnthropic } = await import('@langchain/anthropic');
+ const anthropicModels = [
+ 'claude-3-5-sonnet-20241022',
+ 'claude-3-5-haiku-20241022',
+ 'claude-3-haiku-20240307',
+ ];
+
+ for (const modelName of anthropicModels) {
+ try {
+ const model = new ChatAnthropic({ model: modelName });
+ await model.invoke('ping');
+ models.push(model);
+ log(COLORS.green, `โ ${modelName} - Available`);
+ } catch (error: any) {
+ if (error.message?.includes('API key')) {
+ break;
+ }
+ log(COLORS.yellow, `โ ${modelName} - Not available`);
+ }
+ }
+ } catch {
+ log(COLORS.yellow, 'โ @langchain/anthropic not installed');
+ }
+
+ console.log(`\nTotal models available: ${models.length}`);
+
+ if (models.length === 0) {
+ throw new Error('No models available for testing');
+ }
+
+ // Analyze each model
+ console.log('\nModel Analysis:');
+ for (const model of models) {
+ const analysis = analyzeModel(model);
+ console.log(` ${analysis.modelName}: ${analysis.tier} tier, ${analysis.recommendation}`);
+ }
+
+ return models;
+}
+
+// ============================================================================
+// Benchmark Results Storage
+// ============================================================================
+
+interface BenchmarkResult {
+ testName: string;
+ configuration: string;
+ drafterModel: string;
+ verifierModel: string;
+ qualityThreshold?: number;
+ withQualityValidation: boolean;
+
+ // Timing
+ latencyMs: number;
+
+ // Cost
+ totalCost: number;
+ drafterCost: number;
+ verifierCost: number;
+ savingsPercentage: number;
+
+ // Quality
+ drafterQuality?: number;
+ accepted?: boolean;
+ modelUsed?: 'drafter' | 'verifier';
+
+ // Success
+ success: boolean;
+ error?: string;
+
+ // Response
+ responseLength?: number;
+ responsePreview?: string;
+}
+
+const results: BenchmarkResult[] = [];
+
+// ============================================================================
+// Test Prompts
+// ============================================================================
+
+const TEST_PROMPTS = {
+ simple: 'What is TypeScript in one sentence?',
+ complex: 'Explain the differences between async/await and Promises in JavaScript, including when to use each.',
+ reasoning: 'If a train leaves Station A at 60 mph and another train leaves Station B (100 miles away) at 40 mph, when will they meet?',
+ code: 'Write a Python function to find the longest palindromic substring in a given string.',
+};
+
+const TOOL_DEFINITION = {
+ type: 'function' as const,
+ function: {
+ name: 'calculator',
+ description: 'Performs arithmetic operations',
+ parameters: {
+ type: 'object',
+ properties: {
+ operation: {
+ type: 'string',
+ enum: ['add', 'subtract', 'multiply', 'divide'],
+ },
+ a: { type: 'number' },
+ b: { type: 'number' },
+ },
+ required: ['operation', 'a', 'b'],
+ },
+ },
+};
+
+// ============================================================================
+// Benchmark Tests
+// ============================================================================
+
+async function benchmarkBasicCascade(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel,
+ withQuality: boolean
+): Promise {
+ const drafterName = analyzeModel(drafter).modelName;
+ const verifierName = analyzeModel(verifier).modelName;
+
+ try {
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: withQuality ? 0.7 : 0.0,
+ enableCostTracking: true,
+ });
+
+ const startTime = Date.now();
+ const result = await cascade.invoke(TEST_PROMPTS.complex);
+ const latencyMs = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+
+ return {
+ testName: 'Basic Cascade',
+ configuration: withQuality ? 'With Quality Validation' : 'Without Quality Validation',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ qualityThreshold: withQuality ? 0.7 : 0.0,
+ withQualityValidation: withQuality,
+ latencyMs,
+ totalCost: stats?.totalCost || 0,
+ drafterCost: stats?.drafterCost || 0,
+ verifierCost: stats?.verifierCost || 0,
+ savingsPercentage: stats?.savingsPercentage || 0,
+ drafterQuality: stats?.drafterQuality,
+ accepted: stats?.accepted,
+ modelUsed: stats?.modelUsed,
+ success: true,
+ responseLength: result.content.toString().length,
+ responsePreview: result.content.toString().substring(0, 100),
+ };
+ } catch (error: any) {
+ return {
+ testName: 'Basic Cascade',
+ configuration: withQuality ? 'With Quality Validation' : 'Without Quality Validation',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ qualityThreshold: withQuality ? 0.7 : 0.0,
+ withQualityValidation: withQuality,
+ latencyMs: 0,
+ totalCost: 0,
+ drafterCost: 0,
+ verifierCost: 0,
+ savingsPercentage: 0,
+ success: false,
+ error: error.message,
+ };
+ }
+}
+
+async function benchmarkStreaming(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel
+): Promise {
+ const drafterName = analyzeModel(drafter).modelName;
+ const verifierName = analyzeModel(verifier).modelName;
+
+ try {
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ });
+
+ const startTime = Date.now();
+ const stream = await cascade.stream(TEST_PROMPTS.simple);
+
+ let chunks = 0;
+ let fullContent = '';
+ for await (const chunk of stream) {
+ fullContent += chunk.content;
+ chunks++;
+ }
+ const latencyMs = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+
+ return {
+ testName: 'Streaming',
+ configuration: `${chunks} chunks`,
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ qualityThreshold: 0.7,
+ latencyMs,
+ totalCost: stats?.totalCost || 0,
+ drafterCost: stats?.drafterCost || 0,
+ verifierCost: stats?.verifierCost || 0,
+ savingsPercentage: stats?.savingsPercentage || 0,
+ drafterQuality: stats?.drafterQuality,
+ accepted: stats?.accepted,
+ modelUsed: stats?.modelUsed,
+ success: true,
+ responseLength: fullContent.length,
+ responsePreview: fullContent.substring(0, 100),
+ };
+ } catch (error: any) {
+ return {
+ testName: 'Streaming',
+ configuration: 'Failed',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ latencyMs: 0,
+ totalCost: 0,
+ drafterCost: 0,
+ verifierCost: 0,
+ savingsPercentage: 0,
+ success: false,
+ error: error.message,
+ };
+ }
+}
+
+async function benchmarkToolCalling(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel
+): Promise {
+ const drafterName = analyzeModel(drafter).modelName;
+ const verifierName = analyzeModel(verifier).modelName;
+
+ try {
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ });
+
+ const boundCascade = cascade.bindTools([TOOL_DEFINITION]);
+
+ const startTime = Date.now();
+ const result = await boundCascade.invoke('What is 15 multiplied by 27?');
+ const latencyMs = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+ const toolCalls = (result as any).tool_calls || (result as any).additional_kwargs?.tool_calls;
+
+ return {
+ testName: 'Tool Calling',
+ configuration: toolCalls ? `${toolCalls.length} tool calls` : 'No tool calls',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ qualityThreshold: 0.7,
+ latencyMs,
+ totalCost: stats?.totalCost || 0,
+ drafterCost: stats?.drafterCost || 0,
+ verifierCost: stats?.verifierCost || 0,
+ savingsPercentage: stats?.savingsPercentage || 0,
+ drafterQuality: stats?.drafterQuality,
+ accepted: stats?.accepted,
+ modelUsed: stats?.modelUsed,
+ success: !!toolCalls,
+ responsePreview: toolCalls ? JSON.stringify(toolCalls[0], null, 2).substring(0, 100) : undefined,
+ };
+ } catch (error: any) {
+ return {
+ testName: 'Tool Calling',
+ configuration: 'Failed',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ latencyMs: 0,
+ totalCost: 0,
+ drafterCost: 0,
+ verifierCost: 0,
+ savingsPercentage: 0,
+ success: false,
+ error: error.message,
+ };
+ }
+}
+
+async function benchmarkStructuredOutput(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel
+): Promise {
+ const drafterName = analyzeModel(drafter).modelName;
+ const verifierName = analyzeModel(verifier).modelName;
+
+ try {
+ const schema = {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ age: { type: 'number' },
+ occupation: { type: 'string' },
+ },
+ required: ['name', 'age'],
+ };
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ });
+
+ const structuredCascade = cascade.withStructuredOutput(schema);
+
+ const startTime = Date.now();
+ const result = await structuredCascade.invoke('Extract: Sarah is 32 years old and works as an engineer.');
+ const latencyMs = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+
+ return {
+ testName: 'Structured Output',
+ configuration: 'JSON extraction',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ qualityThreshold: 0.7,
+ latencyMs,
+ totalCost: stats?.totalCost || 0,
+ drafterCost: stats?.drafterCost || 0,
+ verifierCost: stats?.verifierCost || 0,
+ savingsPercentage: stats?.savingsPercentage || 0,
+ drafterQuality: stats?.drafterQuality,
+ accepted: stats?.accepted,
+ modelUsed: stats?.modelUsed,
+ success: typeof result === 'object' && (result as any).name && (result as any).age,
+ responsePreview: JSON.stringify(result, null, 2).substring(0, 100),
+ };
+ } catch (error: any) {
+ return {
+ testName: 'Structured Output',
+ configuration: 'Failed',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ latencyMs: 0,
+ totalCost: 0,
+ drafterCost: 0,
+ verifierCost: 0,
+ savingsPercentage: 0,
+ success: false,
+ error: error.message,
+ };
+ }
+}
+
+async function benchmarkBatchProcessing(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel
+): Promise {
+ const drafterName = analyzeModel(drafter).modelName;
+ const verifierName = analyzeModel(verifier).modelName;
+
+ try {
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ });
+
+ const prompts = [
+ 'What is 2+2?',
+ 'What is the capital of France?',
+ 'What is H2O?',
+ ];
+
+ const startTime = Date.now();
+ const batchResults = await cascade.batch(prompts);
+ const latencyMs = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+
+ return {
+ testName: 'Batch Processing',
+ configuration: `${prompts.length} prompts`,
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ qualityThreshold: 0.7,
+ latencyMs,
+ totalCost: stats?.totalCost || 0,
+ drafterCost: stats?.drafterCost || 0,
+ verifierCost: stats?.verifierCost || 0,
+ savingsPercentage: stats?.savingsPercentage || 0,
+ drafterQuality: stats?.drafterQuality,
+ accepted: stats?.accepted,
+ modelUsed: stats?.modelUsed,
+ success: batchResults.length === prompts.length,
+ responsePreview: `${batchResults.length} results`,
+ };
+ } catch (error: any) {
+ return {
+ testName: 'Batch Processing',
+ configuration: 'Failed',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ latencyMs: 0,
+ totalCost: 0,
+ drafterCost: 0,
+ verifierCost: 0,
+ savingsPercentage: 0,
+ success: false,
+ error: error.message,
+ };
+ }
+}
+
+async function benchmarkLCELChain(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel
+): Promise {
+ const drafterName = analyzeModel(drafter).modelName;
+ const verifierName = analyzeModel(verifier).modelName;
+
+ try {
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ });
+
+ const chain = cascade.pipe(new StringOutputParser());
+
+ const startTime = Date.now();
+ const result = await chain.invoke('What is Rust in one sentence?');
+ const latencyMs = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+
+ return {
+ testName: 'LCEL Chain',
+ configuration: 'Pipe to StringOutputParser',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ qualityThreshold: 0.7,
+ latencyMs,
+ totalCost: stats?.totalCost || 0,
+ drafterCost: stats?.drafterCost || 0,
+ verifierCost: stats?.verifierCost || 0,
+ savingsPercentage: stats?.savingsPercentage || 0,
+ drafterQuality: stats?.drafterQuality,
+ accepted: stats?.accepted,
+ modelUsed: stats?.modelUsed,
+ success: typeof result === 'string',
+ responseLength: result.length,
+ responsePreview: result.substring(0, 100),
+ };
+ } catch (error: any) {
+ return {
+ testName: 'LCEL Chain',
+ configuration: 'Failed',
+ drafterModel: drafterName,
+ verifierModel: verifierName,
+ withQualityValidation: true,
+ latencyMs: 0,
+ totalCost: 0,
+ drafterCost: 0,
+ verifierCost: 0,
+ savingsPercentage: 0,
+ success: false,
+ error: error.message,
+ };
+ }
+}
+
+// ============================================================================
+// Main Benchmark Runner
+// ============================================================================
+
+async function runComprehensiveBenchmark() {
+ section('COMPREHENSIVE LANGCHAIN CASCADE BENCHMARK');
+
+ // Discover available models
+ const models = await discoverAvailableModels();
+
+ // Get cascade pairs
+ section('CASCADE PAIR DISCOVERY');
+ const pairs = discoverCascadePairs(models);
+ console.log(`Found ${pairs.length} valid cascade pairs\n`);
+
+ pairs.forEach((pair, i) => {
+ console.log(`Pair ${i + 1}:`);
+ console.log(` Drafter: ${pair.analysis.drafterModel}`);
+ console.log(` Verifier: ${pair.analysis.verifierModel}`);
+ console.log(` Estimated savings: ${pair.analysis.estimatedSavings.toFixed(1)}%`);
+ console.log();
+ });
+
+ // Run benchmarks for each pair
+ for (const pair of pairs) {
+ section(`TESTING: ${pair.analysis.drafterModel} โ ${pair.analysis.verifierModel}`);
+
+ // Test 1: Basic cascade with quality validation
+ log(COLORS.blue, 'Test 1: Basic Cascade (WITH quality validation)...');
+ const result1 = await benchmarkBasicCascade(pair.drafter, pair.verifier, true);
+ results.push(result1);
+ log(result1.success ? COLORS.green : COLORS.red,
+ result1.success
+ ? `โ ${result1.latencyMs}ms, $${result1.totalCost.toFixed(6)}, ${result1.savingsPercentage.toFixed(1)}% savings, used ${result1.modelUsed}`
+ : `โ ${result1.error}`
+ );
+
+ // Test 2: Basic cascade without quality validation
+ log(COLORS.blue, 'Test 2: Basic Cascade (WITHOUT quality validation)...');
+ const result2 = await benchmarkBasicCascade(pair.drafter, pair.verifier, false);
+ results.push(result2);
+ log(result2.success ? COLORS.green : COLORS.red,
+ result2.success
+ ? `โ ${result2.latencyMs}ms, $${result2.totalCost.toFixed(6)}, ${result2.savingsPercentage.toFixed(1)}% savings, used ${result2.modelUsed}`
+ : `โ ${result2.error}`
+ );
+
+ // Test 3: Streaming
+ log(COLORS.blue, 'Test 3: Streaming...');
+ const result3 = await benchmarkStreaming(pair.drafter, pair.verifier);
+ results.push(result3);
+ log(result3.success ? COLORS.green : COLORS.red,
+ result3.success
+ ? `โ ${result3.latencyMs}ms, ${result3.configuration}, used ${result3.modelUsed}`
+ : `โ ${result3.error}`
+ );
+
+ // Test 4: Tool calling
+ log(COLORS.blue, 'Test 4: Tool Calling...');
+ const result4 = await benchmarkToolCalling(pair.drafter, pair.verifier);
+ results.push(result4);
+ log(result4.success ? COLORS.green : COLORS.red,
+ result4.success
+ ? `โ ${result4.latencyMs}ms, ${result4.configuration}`
+ : `โ ${result4.error}`
+ );
+
+ // Test 5: Structured output
+ log(COLORS.blue, 'Test 5: Structured Output...');
+ const result5 = await benchmarkStructuredOutput(pair.drafter, pair.verifier);
+ results.push(result5);
+ log(result5.success ? COLORS.green : COLORS.red,
+ result5.success
+ ? `โ ${result5.latencyMs}ms, extraction successful`
+ : `โ ${result5.error}`
+ );
+
+ // Test 6: Batch processing
+ log(COLORS.blue, 'Test 6: Batch Processing...');
+ const result6 = await benchmarkBatchProcessing(pair.drafter, pair.verifier);
+ results.push(result6);
+ log(result6.success ? COLORS.green : COLORS.red,
+ result6.success
+ ? `โ ${result6.latencyMs}ms, ${result6.configuration}`
+ : `โ ${result6.error}`
+ );
+
+ // Test 7: LCEL chain
+ log(COLORS.blue, 'Test 7: LCEL Chain...');
+ const result7 = await benchmarkLCELChain(pair.drafter, pair.verifier);
+ results.push(result7);
+ log(result7.success ? COLORS.green : COLORS.red,
+ result7.success
+ ? `โ ${result7.latencyMs}ms, ${result7.configuration}`
+ : `โ ${result7.error}`
+ );
+ }
+
+ // ============================================================================
+ // Results Analysis
+ // ============================================================================
+
+ section('BENCHMARK RESULTS SUMMARY');
+
+ const successfulTests = results.filter(r => r.success);
+ const failedTests = results.filter(r => !r.success);
+
+ console.log(`Total tests: ${results.length}`);
+ log(COLORS.green, `Successful: ${successfulTests.length}`);
+ log(COLORS.red, `Failed: ${failedTests.length}`);
+ console.log();
+
+ // Group by test type
+ const byTestType = results.reduce((acc, r) => {
+ if (!acc[r.testName]) acc[r.testName] = [];
+ acc[r.testName].push(r);
+ return acc;
+ }, {} as Record);
+
+ for (const [testName, testResults] of Object.entries(byTestType)) {
+ console.log(`\n${testName}:`);
+ const successful = testResults.filter(r => r.success);
+ const avgLatency = successful.reduce((sum, r) => sum + r.latencyMs, 0) / successful.length;
+ const avgCost = successful.reduce((sum, r) => sum + r.totalCost, 0) / successful.length;
+ const avgSavings = successful.reduce((sum, r) => sum + r.savingsPercentage, 0) / successful.length;
+
+ console.log(` Success rate: ${successful.length}/${testResults.length}`);
+ if (successful.length > 0) {
+ console.log(` Avg latency: ${avgLatency.toFixed(0)}ms`);
+ console.log(` Avg cost: $${avgCost.toFixed(6)}`);
+ console.log(` Avg savings: ${avgSavings.toFixed(1)}%`);
+ }
+ }
+
+ // Best performing pairs
+ section('TOP PERFORMING CASCADE PAIRS');
+
+ const pairPerformance = pairs.map(pair => {
+ const pairResults = results.filter(
+ r => r.drafterModel === pair.analysis.drafterModel &&
+ r.verifierModel === pair.analysis.verifierModel &&
+ r.success
+ );
+
+ const avgSavings = pairResults.reduce((sum, r) => sum + r.savingsPercentage, 0) / pairResults.length;
+ const avgLatency = pairResults.reduce((sum, r) => sum + r.latencyMs, 0) / pairResults.length;
+ const successRate = (pairResults.length / results.filter(
+ r => r.drafterModel === pair.analysis.drafterModel &&
+ r.verifierModel === pair.analysis.verifierModel
+ ).length) * 100;
+
+ return {
+ pair: `${pair.analysis.drafterModel} โ ${pair.analysis.verifierModel}`,
+ avgSavings,
+ avgLatency,
+ successRate,
+ tests: pairResults.length,
+ };
+ }).sort((a, b) => b.avgSavings - a.avgSavings);
+
+ pairPerformance.forEach((perf, i) => {
+ console.log(`${i + 1}. ${perf.pair}`);
+ console.log(` Avg savings: ${perf.avgSavings.toFixed(1)}%`);
+ console.log(` Avg latency: ${perf.avgLatency.toFixed(0)}ms`);
+ console.log(` Success rate: ${perf.successRate.toFixed(1)}%`);
+ console.log(` Tests passed: ${perf.tests}`);
+ console.log();
+ });
+
+ // Detailed results table
+ section('DETAILED RESULTS');
+
+ console.log('Test Name'.padEnd(20) +
+ 'Config'.padEnd(25) +
+ 'Pair'.padEnd(35) +
+ 'Latency'.padEnd(12) +
+ 'Cost'.padEnd(12) +
+ 'Savings'.padEnd(10) +
+ 'Result');
+ console.log('-'.repeat(140));
+
+ for (const result of results) {
+ const pair = `${result.drafterModel.substring(0, 15)} โ ${result.verifierModel.substring(0, 15)}`;
+ console.log(
+ result.testName.padEnd(20) +
+ (result.configuration || '').substring(0, 24).padEnd(25) +
+ pair.padEnd(35) +
+ `${result.latencyMs}ms`.padEnd(12) +
+ `$${result.totalCost.toFixed(6)}`.padEnd(12) +
+ `${result.savingsPercentage.toFixed(1)}%`.padEnd(10) +
+ (result.success ? 'โ' : `โ ${result.error?.substring(0, 30)}`)
+ );
+ }
+
+ // Export results to JSON
+ const fs = await import('fs');
+ const resultsFile = 'benchmark-results.json';
+ fs.writeFileSync(resultsFile, JSON.stringify({
+ timestamp: new Date().toISOString(),
+ modelsAvailable: models.length,
+ pairsTested: pairs.length,
+ totalTests: results.length,
+ successfulTests: successfulTests.length,
+ failedTests: failedTests.length,
+ results,
+ pairPerformance,
+ }, null, 2));
+
+ log(COLORS.green, `\nโ Results saved to ${resultsFile}`);
+
+ section('BENCHMARK COMPLETE');
+}
+
+// Run the benchmark
+runComprehensiveBenchmark().catch(console.error);
diff --git a/packages/langchain-cascadeflow/docs/COST_TRACKING.md b/packages/langchain-cascadeflow/docs/COST_TRACKING.md
new file mode 100644
index 00000000..9364d71c
--- /dev/null
+++ b/packages/langchain-cascadeflow/docs/COST_TRACKING.md
@@ -0,0 +1,224 @@
+# Cost Tracking Integration
+
+@cascadeflow/langchain offers **two flexible cost tracking options** to fit your workflow:
+
+## ๐ Cost Tracking Providers
+
+### 1. **LangSmith** (Default)
+
+Use LangSmith's server-side cost calculation - the native LangChain ecosystem choice.
+
+```typescript
+import { ChatOpenAI } from '@langchain/openai';
+import { CascadeFlow } from '@cascadeflow/langchain';
+
+const cascade = new CascadeFlow({
+ drafter: new ChatOpenAI({ modelName: 'gpt-5-nano' }),
+ verifier: new ChatOpenAI({ modelName: 'gpt-5' }),
+ // LangSmith is the default provider
+ costTrackingProvider: 'langsmith', // Can omit this line
+});
+```
+
+**โ
Benefits:**
+- โ Automatic, always up-to-date pricing
+- โ No pricing table maintenance needed
+- โ Multi-modal cost tracking (text, images, caching, reasoning tokens)
+- โ Integrated with LangSmith UI for visualization
+- โ Native LangChain ecosystem integration
+
+**โ Requirements:**
+- Requires `LANGSMITH_API_KEY` environment variable
+- Requires network connectivity (costs calculated server-side)
+
+**๐ Viewing Costs:**
+
+Costs are visible in your [LangSmith Dashboard](https://smith.langchain.com). Token counts are automatically sent to LangSmith for server-side cost calculation.
+
+```typescript
+const result = await cascade.invoke("Your query");
+const stats = cascade.getLastCascadeResult();
+
+console.log('Model Used:', stats.modelUsed); // drafter or verifier
+console.log('Quality:', stats.drafterQuality); // 0-1 score
+console.log('Latency:', stats.latencyMs); // milliseconds
+
+// โ ๏ธ Costs are $0 locally (calculated by LangSmith)
+console.log('Local Cost:', stats.totalCost); // 0 (see LangSmith UI)
+```
+
+---
+
+### 2. **CascadeFlow** (Local Calculation)
+
+Use CascadeFlow's built-in pricing table for offline, dependency-free cost tracking.
+
+```typescript
+const cascade = new CascadeFlow({
+ drafter: new ChatOpenAI({ modelName: 'gpt-5-nano' }),
+ verifier: new ChatOpenAI({ modelName: 'gpt-5' }),
+ costTrackingProvider: 'cascadeflow', // Use local pricing
+});
+```
+
+**โ
Benefits:**
+- โ No external dependencies
+- โ Works offline
+- โ Immediate local cost feedback
+- โ No LangSmith account required
+- โ Privacy-friendly (no data sent externally)
+
+**โ Limitations:**
+- Pricing table may lag behind provider updates
+- Limited to text tokens (no multi-modal support yet)
+- Requires manual updates for new models
+
+**๐ Viewing Costs:**
+
+Costs are calculated immediately and returned in the stats object:
+
+```typescript
+const result = await cascade.invoke("Your query");
+const stats = cascade.getLastCascadeResult();
+
+console.log('Drafter Cost:', stats.drafterCost); // $0.000123
+console.log('Verifier Cost:', stats.verifierCost); // $0 (if not used)
+console.log('Total Cost:', stats.totalCost); // $0.000123
+console.log('Savings:', stats.savingsPercentage); // 66.7%
+```
+
+---
+
+## ๐ฏ When to Use Each
+
+### Use **LangSmith** (default) when:
+
+- โ
You already use LangSmith for observability
+- โ
You want the most accurate, up-to-date pricing
+- โ
You need multi-modal cost tracking
+- โ
You want cost visualization in LangSmith UI
+- โ
You're deploying to production with LangChain ecosystem
+
+### Use **CascadeFlow** when:
+
+- โ
You don't want external dependencies
+- โ
You need offline support
+- โ
You want immediate local cost feedback
+- โ
You're prototyping and don't have LangSmith yet
+- โ
You prefer privacy-focused, local-only tracking
+
+---
+
+## โ๏ธ Configuration
+
+```typescript
+interface CascadeConfig {
+ drafter: BaseChatModel;
+ verifier: BaseChatModel;
+ qualityThreshold?: number; // 0-1, default: 0.7
+ enableCostTracking?: boolean; // default: true
+ costTrackingProvider?: 'langsmith' | 'cascadeflow'; // default: 'langsmith'
+}
+```
+
+### Disabling Cost Tracking
+
+```typescript
+const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ enableCostTracking: false, // Disable all cost tracking
+});
+```
+
+---
+
+## ๐ Switching Providers
+
+You can easily switch between providers:
+
+```typescript
+// Development: Use CascadeFlow for quick local feedback
+const devCascade = new CascadeFlow({
+ drafter,
+ verifier,
+ costTrackingProvider: 'cascadeflow',
+});
+
+// Production: Use LangSmith for comprehensive observability
+const prodCascade = new CascadeFlow({
+ drafter,
+ verifier,
+ costTrackingProvider: 'langsmith',
+});
+```
+
+---
+
+## ๐ฆ Supported Models (CascadeFlow Provider)
+
+When using `costTrackingProvider: 'cascadeflow'`, the following models have built-in pricing:
+
+### OpenAI
+- `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-5.1`
+- `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`
+- `gpt-3.5-turbo`
+
+### Anthropic
+- `claude-3-5-sonnet-20241022`, `claude-3-5-haiku-20241022`
+- `claude-3-opus-20240229`, `claude-3-sonnet-20240229`, `claude-3-haiku-20240307`
+- `claude-sonnet-4`, `claude-haiku-4.5`
+
+### Google
+- `gemini-2.5-flash`, `gemini-2.5-pro`
+- `gemini-1.5-pro`, `gemini-1.5-flash`
+
+### Others
+- Groq models
+- Together AI models
+
+**Missing a model?** It will default to $0 cost. [Open an issue](https://github.com/lemony-ai/cascadeflow/issues) to request additions.
+
+---
+
+## ๐ Environment Variables
+
+### For LangSmith Provider
+
+```bash
+# Required for LangSmith cost tracking
+LANGSMITH_API_KEY=lsv2_pt_...
+LANGSMITH_PROJECT=your-project-name
+LANGSMITH_TRACING=true # Optional, enables full tracing
+```
+
+### For CascadeFlow Provider
+
+No environment variables required! Works completely offline.
+
+---
+
+## ๐ก Best Practices
+
+1. **Development**: Use `cascadeflow` provider for fast iteration without LangSmith dependency
+2. **Production**: Use `langsmith` provider for comprehensive cost tracking and observability
+3. **Hybrid**: Use `cascadeflow` locally, `langsmith` in CI/production
+4. **Privacy**: Use `cascadeflow` if you can't send data to external services
+
+---
+
+## ๐ Example: Full Comparison
+
+See [`examples/cost-tracking-providers.ts`](../examples/cost-tracking-providers.ts) for a complete side-by-side demonstration of both providers.
+
+```bash
+npx tsx examples/cost-tracking-providers.ts
+```
+
+---
+
+## ๐ Related Documentation
+
+- [LangSmith Documentation](https://docs.smith.langchain.com/)
+- [CascadeFlow Pricing Table](../src/models.ts)
+- [Main README](../README.md)
diff --git a/packages/langchain-cascadeflow/examples/analyze-models.ts b/packages/langchain-cascadeflow/examples/analyze-models.ts
new file mode 100644
index 00000000..f206949f
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/analyze-models.ts
@@ -0,0 +1,85 @@
+/**
+ * Model Analysis Example
+ *
+ * Demonstrates how to analyze and validate cascade configurations
+ * using your existing LangChain model instances.
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { analyzeCascadePair, suggestCascadePairs } from '../src/index.js';
+
+async function main() {
+ console.log('=== CascadeFlow Model Analysis Demo ===\n');
+
+ // Example 1: Analyze a specific drafter/verifier pair
+ console.log('--- Example 1: Analyze OpenAI Cascade Pair ---');
+
+ const drafterOpenAI = new ChatOpenAI({ model: 'gpt-4o-mini' });
+ const verifierOpenAI = new ChatOpenAI({ model: 'gpt-4o' });
+
+ const analysis1 = analyzeCascadePair(drafterOpenAI, verifierOpenAI);
+
+ console.log(`Drafter: ${analysis1.drafterModel}`);
+ console.log(`Verifier: ${analysis1.verifierModel}`);
+ console.log(`\nPricing (per 1M tokens):`);
+ console.log(` Drafter: $${analysis1.drafterCost.input} input / $${analysis1.drafterCost.output} output`);
+ console.log(` Verifier: $${analysis1.verifierCost.input} input / $${analysis1.verifierCost.output} output`);
+ console.log(`\nEstimated Savings: ${analysis1.estimatedSavings.toFixed(1)}%`);
+ console.log(`Configuration Valid: ${analysis1.valid ? 'โ
' : 'โ'}`);
+ console.log(`Recommendation: ${analysis1.recommendation}`);
+
+ if (analysis1.warnings.length > 0) {
+ console.log(`\nWarnings:`);
+ analysis1.warnings.forEach(w => console.log(` โ ๏ธ ${w}`));
+ }
+ console.log('\n');
+
+ // Example 2: Detect misconfiguration (drafter more expensive than verifier)
+ console.log('--- Example 2: Detect Misconfiguration ---');
+
+ const expensiveDrafter = new ChatOpenAI({ model: 'gpt-4o' });
+ const cheapVerifier = new ChatOpenAI({ model: 'gpt-4o-mini' });
+
+ const analysis2 = analyzeCascadePair(expensiveDrafter, cheapVerifier);
+
+ console.log(`Drafter: ${analysis2.drafterModel}`);
+ console.log(`Verifier: ${analysis2.verifierModel}`);
+ console.log(`Configuration Valid: ${analysis2.valid ? 'โ
' : 'โ'}`);
+ console.log(`Recommendation: ${analysis2.recommendation}`);
+
+ if (analysis2.warnings.length > 0) {
+ console.log(`\nWarnings:`);
+ analysis2.warnings.forEach(w => console.log(` โ ๏ธ ${w}`));
+ }
+ console.log('\n');
+
+ // Example 3: Suggest optimal pairs from available models
+ console.log('--- Example 3: Suggest Optimal Cascade Pairs ---');
+
+ const availableModels = [
+ new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ new ChatOpenAI({ model: 'gpt-4o' }),
+ new ChatOpenAI({ model: 'gpt-3.5-turbo' }),
+ new ChatOpenAI({ model: 'gpt-4-turbo' }),
+ ];
+
+ console.log(`Analyzing ${availableModels.length} available models...\n`);
+
+ const suggestions = suggestCascadePairs(availableModels);
+
+ console.log(`Found ${suggestions.length} viable cascade configurations:\n`);
+
+ suggestions.slice(0, 5).forEach((suggestion, idx) => {
+ const { drafter, verifier, analysis } = suggestion;
+ console.log(`${idx + 1}. ${analysis.drafterModel} โ ${analysis.verifierModel}`);
+ console.log(` Estimated Savings: ${analysis.estimatedSavings.toFixed(1)}%`);
+ console.log(` ${analysis.recommendation}`);
+ console.log();
+ });
+
+ console.log('=== Analysis Complete ===');
+ console.log('\n๐ก Use analyzeCascadePair() to validate your cascade configuration');
+ console.log('๐ก Use suggestCascadePairs() to find optimal pairs from your models');
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/basic-usage.ts b/packages/langchain-cascadeflow/examples/basic-usage.ts
new file mode 100644
index 00000000..fdcc30ba
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/basic-usage.ts
@@ -0,0 +1,127 @@
+/**
+ * Basic LangChain Cascade Example
+ *
+ * Demonstrates how to wrap existing LangChain models with cascadeflow
+ * for automatic cost optimization.
+ *
+ * Setup:
+ * export OPENAI_API_KEY="sk-..."
+ * pnpm install
+ * npx tsx examples/basic-usage.ts
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { withCascade } from '../src/index.js';
+
+async function main() {
+ // Check API key
+ if (!process.env.OPENAI_API_KEY) {
+ console.log("โ Set OPENAI_API_KEY first: export OPENAI_API_KEY='sk-...'");
+ return;
+ }
+
+ console.log('๐ LangChain + CascadeFlow Integration\n');
+
+ // ========================================================================
+ // STEP 1: Configure your existing LangChain models
+ // ========================================================================
+ // Nothing changes here - use your existing model configurations!
+
+ const drafter = new ChatOpenAI({
+ model: 'gpt-4o-mini',
+ temperature: 0.7,
+ });
+
+ const verifier = new ChatOpenAI({
+ model: 'gpt-4o',
+ temperature: 0.7,
+ });
+
+ console.log('โ Configured drafter: gpt-4o-mini ($0.15/1M tokens)');
+ console.log('โ Configured verifier: gpt-4o ($2.50/1M tokens)');
+ console.log('โ Cost difference: ~17x\n');
+
+ // ========================================================================
+ // STEP 2: Wrap with cascade (just 2 lines!)
+ // ========================================================================
+
+ const cascadeModel = withCascade({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ });
+
+ console.log('โ Wrapped models with cascade logic\n');
+
+ // ========================================================================
+ // STEP 3: Use like any LangChain model
+ // ========================================================================
+
+ console.log('='.repeat(60));
+ console.log('Example 1: Simple Question (should use drafter)\n');
+
+ const result1 = await cascadeModel.invoke('What is 2+2?');
+ console.log(`Answer: ${result1.content}\n`);
+
+ // Get cascade statistics
+ const stats1 = cascadeModel.getLastCascadeResult();
+ if (stats1) {
+ console.log('๐ Cascade Stats:');
+ console.log(` Model used: ${stats1.modelUsed}`);
+ console.log(` Drafter quality: ${(stats1.drafterQuality! * 100).toFixed(0)}%`);
+ console.log(` Accepted: ${stats1.accepted ? 'โ
' : 'โ'}`);
+ console.log(` Drafter cost: $${stats1.drafterCost.toFixed(6)}`);
+ console.log(` Verifier cost: $${stats1.verifierCost.toFixed(6)}`);
+ console.log(` Total cost: $${stats1.totalCost.toFixed(6)}`);
+ console.log(` Savings: ${stats1.savingsPercentage.toFixed(1)}%`);
+ console.log(` Latency: ${stats1.latencyMs}ms`);
+ }
+
+ // ========================================================================
+ // STEP 4: Complex question (may cascade)
+ // ========================================================================
+
+ console.log('\n' + '='.repeat(60));
+ console.log('Example 2: Complex Question (may need verifier)\n');
+
+ const result2 = await cascadeModel.invoke(
+ 'Explain the implications of quantum entanglement for computing.'
+ );
+ console.log(`Answer: ${result2.content.substring(0, 200)}...\n`);
+
+ const stats2 = cascadeModel.getLastCascadeResult();
+ if (stats2) {
+ console.log('๐ Cascade Stats:');
+ console.log(` Model used: ${stats2.modelUsed}`);
+ console.log(` Drafter quality: ${(stats2.drafterQuality! * 100).toFixed(0)}%`);
+ console.log(` Accepted: ${stats2.accepted ? 'โ
' : 'โ'}`);
+ console.log(` Total cost: $${stats2.totalCost.toFixed(6)}`);
+ console.log(` Savings: ${stats2.savingsPercentage.toFixed(1)}%`);
+ }
+
+ // ========================================================================
+ // STEP 5: All LangChain features work!
+ // ========================================================================
+
+ console.log('\n' + '='.repeat(60));
+ console.log('Example 3: Chainable methods work seamlessly\n');
+
+ // bind() works
+ const boundModel = cascadeModel.bind({ temperature: 0.1 });
+ const result3 = await boundModel.invoke('What is TypeScript?');
+ console.log(`โ bind() works: ${result3.content.substring(0, 100)}...\n`);
+
+ // ========================================================================
+ // Summary
+ // ========================================================================
+
+ console.log('='.repeat(60));
+ console.log('\nโ
Key Takeaways:');
+ console.log(' โข Zero code changes to your LangChain models');
+ console.log(' โข Automatic cost optimization (40-60% savings)');
+ console.log(' โข All LangChain features preserved');
+ console.log(' โข Quality-based cascading ensures no degradation');
+ console.log(' โข Full visibility into cascade decisions\n');
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/cost-tracking-providers.ts b/packages/langchain-cascadeflow/examples/cost-tracking-providers.ts
new file mode 100644
index 00000000..95d097fc
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/cost-tracking-providers.ts
@@ -0,0 +1,127 @@
+/**
+ * Cost Tracking Providers Example
+ *
+ * Demonstrates the two cost tracking options:
+ * 1. LangSmith (default) - Server-side cost calculation
+ * 2. CascadeFlow - Built-in local cost calculation
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { CascadeFlow } from '../src/index.js';
+
+const COLORS = {
+ reset: '\x1b[0m',
+ green: '\x1b[32m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ magenta: '\x1b[35m',
+ bold: '\x1b[1m',
+};
+
+async function testLangSmithProvider() {
+ console.log(`\n${COLORS.cyan}${COLORS.bold}=== Test 1: LangSmith Provider (Default) ===${COLORS.reset}\n`);
+
+ const drafter = new ChatOpenAI({ modelName: 'gpt-5-nano', temperature: 1.0 });
+ const verifier = new ChatOpenAI({ modelName: 'gpt-5', temperature: 1.0 });
+
+ // LangSmith provider (default)
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ costTrackingProvider: 'langsmith', // Can be omitted (this is the default)
+ });
+
+ console.log(`${COLORS.blue}Configuration:${COLORS.reset}`);
+ console.log(' Cost Tracking: LangSmith (server-side)');
+ console.log(' Requires: LANGSMITH_API_KEY environment variable');
+ console.log(' Benefits:');
+ console.log(' โ Automatic cost calculation (no pricing table needed)');
+ console.log(' โ Always up-to-date with latest model pricing');
+ console.log(' โ Integrated with LangSmith UI for visualization');
+ console.log(' โ Supports multi-modal costs (text, images, caching)');
+ console.log(' Drawbacks:');
+ console.log(' โ Requires LangSmith account & API key');
+ console.log(' โ Network dependency (offline usage not supported)\n');
+
+ const result = await cascade.invoke('What is TypeScript?');
+ const stats = cascade.getLastCascadeResult();
+
+ console.log(`${COLORS.green}Response:${COLORS.reset} ${result.content.substring(0, 100)}...\n`);
+ console.log(`${COLORS.bold}Cascade Metadata:${COLORS.reset}`);
+ console.log(` Model Used: ${stats?.modelUsed}`);
+ console.log(` Quality Score: ${stats?.drafterQuality?.toFixed(2)}`);
+ console.log(` Local Cost (disabled with LangSmith): $${stats?.totalCost.toFixed(6)}`);
+ console.log(` ${COLORS.yellow}Note: Actual costs visible in LangSmith UI${COLORS.reset}`);
+ console.log(` LangSmith Dashboard: https://smith.langchain.com`);
+}
+
+async function testCascadeFlowProvider() {
+ console.log(`\n${COLORS.cyan}${COLORS.bold}=== Test 2: CascadeFlow Provider ===${COLORS.reset}\n`);
+
+ const drafter = new ChatOpenAI({ modelName: 'gpt-5-nano', temperature: 1.0 });
+ const verifier = new ChatOpenAI({ modelName: 'gpt-5', temperature: 1.0 });
+
+ // CascadeFlow provider (built-in pricing)
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ costTrackingProvider: 'cascadeflow', // Use local cost calculation
+ });
+
+ console.log(`${COLORS.blue}Configuration:${COLORS.reset}`);
+ console.log(' Cost Tracking: CascadeFlow (local calculation)');
+ console.log(' Requires: Nothing (works offline)');
+ console.log(' Benefits:');
+ console.log(' โ No external dependencies');
+ console.log(' โ Works offline');
+ console.log(' โ Immediate cost feedback');
+ console.log(' โ No LangSmith account required');
+ console.log(' Drawbacks:');
+ console.log(' โ Pricing table may lag behind provider updates');
+ console.log(' โ No multi-modal cost tracking (yet)\n');
+
+ const result = await cascade.invoke('What is TypeScript?');
+ const stats = cascade.getLastCascadeResult();
+
+ console.log(`${COLORS.green}Response:${COLORS.reset} ${result.content.substring(0, 100)}...\n`);
+ console.log(`${COLORS.bold}Cascade Metadata with Costs:${COLORS.reset}`);
+ console.log(` Model Used: ${stats?.modelUsed}`);
+ console.log(` Quality Score: ${stats?.drafterQuality?.toFixed(2)}`);
+ console.log(` Drafter Cost: $${stats?.drafterCost.toFixed(6)}`);
+ console.log(` Verifier Cost: $${stats?.verifierCost.toFixed(6)}`);
+ console.log(` Total Cost: $${stats?.totalCost.toFixed(6)}`);
+ console.log(` Savings: ${stats?.savingsPercentage.toFixed(1)}%`);
+}
+
+async function main() {
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โ CascadeFlow Cost Tracking Providers Demo โ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}`);
+
+ // Test 1: LangSmith Provider (default)
+ await testLangSmithProvider();
+
+ // Test 2: CascadeFlow Provider
+ await testCascadeFlowProvider();
+
+ // Recommendation
+ console.log(`\n${COLORS.cyan}${COLORS.bold}=== When to Use Each Provider ===${COLORS.reset}\n`);
+ console.log(`${COLORS.green}Use LangSmith (default)${COLORS.reset} when:`);
+ console.log(' โข You already use LangSmith for observability');
+ console.log(' โข You want the most accurate, up-to-date pricing');
+ console.log(' โข You need multi-modal cost tracking (images, audio)');
+ console.log(' โข You want cost visualization in LangSmith UI\n');
+
+ console.log(`${COLORS.yellow}Use CascadeFlow${COLORS.reset} when:`);
+ console.log(' โข You don\'t want external dependencies');
+ console.log(' โข You need offline support');
+ console.log(' โข You want immediate local cost feedback');
+ console.log(' โข You\'re prototyping and don\'t have LangSmith yet\n');
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/cross-provider-escalation.ts b/packages/langchain-cascadeflow/examples/cross-provider-escalation.ts
new file mode 100644
index 00000000..f75dd7fc
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/cross-provider-escalation.ts
@@ -0,0 +1,223 @@
+/**
+ * Cross-Provider Escalation Demo - Haiku โ GPT-5
+ *
+ * This example demonstrates cross-provider cascading with challenging queries
+ * designed to trigger escalations from Claude Haiku to GPT-5.
+ *
+ * Purpose: Show both ChatAnthropic AND ChatOpenAI traces in LangSmith
+ *
+ * Requirements:
+ * - OPENAI_API_KEY
+ * - ANTHROPIC_API_KEY
+ * - LANGSMITH_API_KEY
+ * - LANGSMITH_PROJECT
+ * - LANGSMITH_TRACING=true
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { ChatAnthropic } from '@langchain/anthropic';
+import { CascadeFlow } from '../src/index.js';
+
+const COLORS = {
+ reset: '\x1b[0m',
+ green: '\x1b[32m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ magenta: '\x1b[35m',
+ red: '\x1b[31m',
+ bold: '\x1b[1m',
+};
+
+// Hard/Expert queries that are likely to trigger escalations
+const EXPERT_QUERIES = [
+ {
+ type: 'Expert',
+ query: 'Design a distributed consensus algorithm for a multi-region database system with network partitions, Byzantine fault tolerance, and linearizable consistency guarantees. Include formal correctness proofs.',
+ },
+ {
+ type: 'Expert',
+ query: 'Implement a type-safe dependency injection container in TypeScript that supports circular dependencies, lazy instantiation, scoped lifetimes (singleton, transient, scoped), and compile-time validation of dependency graphs.',
+ },
+ {
+ type: 'Hard',
+ query: 'Design a real-time collaborative text editor conflict resolution algorithm (CRDT or OT) that handles concurrent edits, maintains strong eventual consistency, and optimizes for low latency in peer-to-peer networks.',
+ },
+ {
+ type: 'Expert',
+ query: 'Create a zero-downtime database migration strategy for a sharded PostgreSQL cluster with 1TB+ data, handling schema changes, data transformations, and rollback capabilities while maintaining ACID guarantees.',
+ },
+ {
+ type: 'Hard',
+ query: 'Implement a distributed rate limiter using Redis that supports sliding window counters, per-tenant limits, burst allowances, and graceful degradation under high load. Include fault tolerance for Redis failures.',
+ },
+ {
+ type: 'Expert',
+ query: 'Design a compacting garbage collector for a JIT-compiled language with precise stack scanning, generational collection, concurrent marking, and incremental sweeping to minimize pause times below 10ms.',
+ },
+ {
+ type: 'Hard',
+ query: 'Build a circuit breaker pattern implementation with exponential backoff, jitter, half-open state testing, bulkhead isolation, and integration with distributed tracing for microservices resilience.',
+ },
+ {
+ type: 'Expert',
+ query: 'Implement a sound type checker for a gradually-typed language with structural subtyping, intersection/union types, generic variance, and flow-sensitive type refinement. Prove soundness and completeness.',
+ },
+];
+
+async function main() {
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โ CROSS-PROVIDER ESCALATION DEMO - Haiku โ GPT-5 โ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}\n`);
+
+ // ========================================================================
+ // STEP 1: Verify Configuration
+ // ========================================================================
+
+ if (!process.env.LANGSMITH_API_KEY) {
+ console.log(`${COLORS.red}โ LANGSMITH_API_KEY not set${COLORS.reset}`);
+ return;
+ }
+
+ if (!process.env.ANTHROPIC_API_KEY) {
+ console.log(`${COLORS.red}โ ANTHROPIC_API_KEY not set${COLORS.reset}`);
+ return;
+ }
+
+ console.log(`${COLORS.green}โ LangSmith tracing enabled${COLORS.reset}`);
+ console.log(` Project: ${process.env.LANGSMITH_PROJECT || 'default'}`);
+ console.log(` Dashboard: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}\n`);
+
+ // ========================================================================
+ // STEP 2: Create Cross-Provider Cascade
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ค Creating Cross-Provider Cascade${COLORS.reset}\n`);
+
+ // Drafter: Claude 3.5 Haiku (Anthropic) - Fast, cheap
+ const haiku = new ChatAnthropic({
+ model: 'claude-3-5-haiku-20241022',
+ temperature: 1.0,
+ });
+
+ // Verifier: GPT-5 (OpenAI) - Accurate, expensive
+ const gpt5 = new ChatOpenAI({
+ model: 'gpt-5',
+ temperature: 1.0,
+ });
+
+ const cascade = new CascadeFlow({
+ drafter: haiku,
+ verifier: gpt5,
+ qualityThreshold: 0.7, // Standard threshold
+ enableCostTracking: true,
+ costTrackingProvider: 'langsmith',
+ enablePreRouter: true, // Enable complexity-based routing
+ });
+
+ console.log(`${COLORS.green}โ Cascade configured${COLORS.reset}`);
+ console.log(` Drafter: ${COLORS.cyan}Claude 3.5 Haiku${COLORS.reset} (Anthropic)`);
+ console.log(` Verifier: ${COLORS.cyan}GPT-5${COLORS.reset} (OpenAI)`);
+ console.log(` Quality Threshold: 0.7`);
+ console.log(` PreRouter: ${COLORS.green}Enabled${COLORS.reset} (hard/expert queries โ direct to GPT-5)\n`);
+
+ // ========================================================================
+ // STEP 3: Run Expert Queries
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ Running ${EXPERT_QUERIES.length} Expert Queries${COLORS.reset}\n`);
+ console.log(`${COLORS.yellow}These challenging queries are designed to trigger escalations${COLORS.reset}`);
+ console.log(`${COLORS.yellow}Watch for BOTH ChatAnthropic AND ChatOpenAI in LangSmith!${COLORS.reset}\n`);
+ console.log(`${COLORS.cyan}${'='.repeat(80)}${COLORS.reset}\n`);
+
+ let cascaded = 0;
+ let escalated = 0;
+
+ for (let i = 0; i < EXPERT_QUERIES.length; i++) {
+ const { type, query } = EXPERT_QUERIES[i];
+
+ console.log(`${COLORS.blue}Query ${i + 1}/${EXPERT_QUERIES.length}${COLORS.reset} [${COLORS.red}${type}${COLORS.reset}]`);
+ console.log(`${query.substring(0, 100)}...`);
+
+ const startTime = Date.now();
+ const result = await cascade.invoke(query);
+ const elapsed = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+
+ if (stats) {
+ const preview = result.content.toString().substring(0, 120);
+ console.log(` Response: ${preview}...`);
+
+ if (stats.modelUsed === 'drafter') {
+ console.log(` ${COLORS.green}โ CASCADED${COLORS.reset} to Haiku (quality: ${stats.drafterQuality?.toFixed(2)}, ${elapsed}ms)`);
+ console.log(` ${COLORS.cyan}โ LangSmith shows: ChatAnthropic (Haiku)${COLORS.reset}`);
+ cascaded++;
+ } else {
+ console.log(` ${COLORS.yellow}โ ESCALATED${COLORS.reset} to GPT-5 (quality: ${stats.drafterQuality?.toFixed(2)}, ${elapsed}ms)`);
+ console.log(` ${COLORS.magenta}โ LangSmith shows: ChatAnthropic (Haiku) + ChatOpenAI (GPT-5)${COLORS.reset}`);
+ escalated++;
+ }
+ }
+
+ console.log();
+
+ // Small delay to avoid rate limits
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ // ========================================================================
+ // STEP 4: Summary
+ // ========================================================================
+
+ console.log(`${COLORS.cyan}${'='.repeat(80)}${COLORS.reset}\n`);
+ console.log(`${COLORS.magenta}${COLORS.bold}๐ RESULTS SUMMARY${COLORS.reset}\n`);
+
+ const total = EXPERT_QUERIES.length;
+ const cascadeRate = (cascaded / total) * 100;
+ const escalationRate = (escalated / total) * 100;
+
+ console.log(`${COLORS.bold}Performance:${COLORS.reset}`);
+ console.log(` Total Queries: ${total}`);
+ console.log(` ${COLORS.green}Cascaded (Haiku only):${COLORS.reset} ${cascaded} (${cascadeRate.toFixed(0)}%)`);
+ console.log(` ${COLORS.yellow}Escalated (Haiku โ GPT-5):${COLORS.reset} ${escalated} (${escalationRate.toFixed(0)}%)\n`);
+
+ // ========================================================================
+ // STEP 5: LangSmith Instructions
+ // ========================================================================
+
+ console.log(`${COLORS.magenta}${COLORS.bold}๐ VIEW IN LANGSMITH${COLORS.reset}\n`);
+
+ console.log(`${COLORS.bold}What You'll See:${COLORS.reset}\n`);
+
+ console.log(`${COLORS.green}1. For Cascaded Queries (Haiku only):${COLORS.reset}`);
+ console.log(` โข Single trace: ${COLORS.cyan}ChatAnthropic${COLORS.reset} (claude-3-5-haiku)`);
+ console.log(` โข Cascade metadata shows: ${COLORS.green}model_used: "drafter"${COLORS.reset}\n`);
+
+ console.log(`${COLORS.yellow}2. For Escalated Queries (Haiku โ GPT-5):${COLORS.reset}`);
+ console.log(` โข First trace: ${COLORS.cyan}ChatAnthropic${COLORS.reset} (claude-3-5-haiku) - tried first`);
+ console.log(` โข Second trace: ${COLORS.cyan}ChatOpenAI${COLORS.reset} (gpt-5) - used for response`);
+ console.log(` โข Cascade metadata shows: ${COLORS.yellow}model_used: "verifier"${COLORS.reset}\n`);
+
+ console.log(`${COLORS.bold}How to Find Traces:${COLORS.reset}`);
+ console.log(` 1. Go to: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}`);
+ console.log(` 2. Select project: ${COLORS.yellow}${process.env.LANGSMITH_PROJECT || 'default'}${COLORS.reset}`);
+ console.log(` 3. Filter by: ${COLORS.yellow}last 1 hour${COLORS.reset}`);
+ console.log(` 4. Look for "ChatAnthropic" and "ChatOpenAI" runs`);
+ console.log(` 5. Click on each to see token usage and costs\n`);
+
+ console.log(`${COLORS.bold}Expected Results:${COLORS.reset}`);
+ if (escalated > 0) {
+ console.log(` ${COLORS.green}โ${COLORS.reset} You should see ${COLORS.yellow}${escalated} escalations${COLORS.reset} showing BOTH providers`);
+ console.log(` ${COLORS.green}โ${COLORS.reset} Escalated queries show dual traces (Anthropic + OpenAI)`);
+ }
+ if (cascaded > 0) {
+ console.log(` ${COLORS.green}โ${COLORS.reset} You should see ${COLORS.cyan}${cascaded} cascades${COLORS.reset} showing only Anthropic`);
+ }
+ console.log(` ${COLORS.green}โ${COLORS.reset} Each trace includes token counts and metadata\n`);
+
+ console.log(`${COLORS.green}${COLORS.bold}โ Demo Complete!${COLORS.reset}`);
+ console.log(`${COLORS.cyan}${'='.repeat(80)}${COLORS.reset}\n`);
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/full-benchmark-semantic.ts b/packages/langchain-cascadeflow/examples/full-benchmark-semantic.ts
new file mode 100644
index 00000000..a9d22300
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/full-benchmark-semantic.ts
@@ -0,0 +1,407 @@
+/**
+ * Comprehensive LangChain Benchmark with Semantic Quality Validation
+ *
+ * Tests ALL available LangChain models with:
+ * - LangSmith tracking and cost visualization
+ * - Semantic quality scoring using ML embeddings
+ * - Cross-provider cascade pairs
+ * - Multiple complexity levels
+ * - Detailed performance metrics
+ *
+ * Requirements:
+ * - OPENAI_API_KEY
+ * - ANTHROPIC_API_KEY (optional)
+ * - LANGSMITH_API_KEY
+ * - @cascadeflow/ml @xenova/transformers (for semantic validation)
+ *
+ * Setup:
+ * npm install @cascadeflow/ml @xenova/transformers
+ * npx tsx examples/full-benchmark-semantic.ts
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { ChatAnthropic } from '@langchain/anthropic';
+import { CascadeFlow } from '../src/index.js';
+import { SemanticQualityChecker } from '@cascadeflow/core';
+
+const COLORS = {
+ reset: '\x1b[0m',
+ green: '\x1b[32m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ magenta: '\x1b[35m',
+ red: '\x1b[31m',
+ bold: '\x1b[1m',
+};
+
+// Test queries spanning different complexity levels
+const TEST_QUERIES = [
+ {
+ query: 'What is TypeScript?',
+ complexity: 'Simple',
+ category: 'Factual',
+ },
+ {
+ query: 'Explain the event loop in Node.js',
+ complexity: 'Medium',
+ category: 'Technical',
+ },
+ {
+ query: 'Design a distributed cache with Redis and implement consistency guarantees',
+ complexity: 'Complex',
+ category: 'Design',
+ },
+ {
+ query: 'Compare the trade-offs between microservices and monolithic architectures',
+ complexity: 'Complex',
+ category: 'Analysis',
+ },
+ {
+ query: 'How do I reverse a string in Python?',
+ complexity: 'Simple',
+ category: 'Code',
+ },
+ {
+ query: 'Explain async/await in JavaScript with examples',
+ complexity: 'Medium',
+ category: 'Technical',
+ },
+];
+
+interface ModelPair {
+ name: string;
+ drafter: any;
+ verifier: any;
+ provider: string;
+ expectedSavings: string;
+}
+
+interface BenchmarkResult {
+ pairName: string;
+ provider: string;
+ totalQueries: number;
+ cascaded: number;
+ escalated: number;
+ avgQuality: number;
+ avgLatency: number;
+ successRate: number;
+ avgSemanticScore?: number;
+}
+
+/**
+ * Create a semantic quality validator for LangChain ChatResult format
+ */
+async function createSemanticValidator(semanticChecker: SemanticQualityChecker, query: string) {
+ return async (response: any): Promise => {
+ // Extract text from LangChain ChatResult
+ const text = response?.generations?.[0]?.text ||
+ response?.generations?.[0]?.message?.content ||
+ '';
+
+ if (!text || text.length < 5) {
+ return 0.2;
+ }
+
+ // Check semantic similarity
+ const result = await semanticChecker.checkSimilarity(query, text);
+
+ if (!result.passed) {
+ console.log(` [Semantic] Similarity: ${(result.similarity * 100).toFixed(1)}% - Below threshold`);
+ return result.similarity * 0.8; // Penalize low similarity
+ }
+
+ // Boost quality score for high semantic similarity
+ const boostedScore = Math.min(1.0, result.similarity * 1.2);
+ return boostedScore;
+ };
+}
+
+async function testModelPair(
+ pair: ModelPair,
+ semanticChecker: SemanticQualityChecker,
+ mlAvailable: boolean
+): Promise {
+ console.log(`\n${COLORS.cyan}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}`);
+ console.log(`${COLORS.cyan}${COLORS.bold}Testing: ${pair.name}${COLORS.reset}`);
+ console.log(`${COLORS.blue}Provider: ${pair.provider} | Expected Savings: ${pair.expectedSavings}${COLORS.reset}`);
+ console.log(`${COLORS.magenta}Semantic Validation: ${mlAvailable ? 'ENABLED โ' : 'DISABLED (Install @cascadeflow/ml)'}${COLORS.reset}`);
+ console.log(`${COLORS.cyan}${'='.repeat(100)}${COLORS.reset}\n`);
+
+ let cascaded = 0;
+ let escalated = 0;
+ let totalQuality = 0;
+ let totalLatency = 0;
+ let totalSemanticScore = 0;
+ const semanticScores: number[] = [];
+
+ for (const { query, complexity, category } of TEST_QUERIES) {
+ console.log(`${COLORS.blue}[${complexity}]${COLORS.reset} ${COLORS.yellow}[${category}]${COLORS.reset} ${query}`);
+
+ // Create cascade with semantic validation
+ const cascade = new CascadeFlow({
+ drafter: pair.drafter,
+ verifier: pair.verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ costTrackingProvider: 'langsmith',
+ // Use semantic validator if ML is available
+ qualityValidator: mlAvailable
+ ? await createSemanticValidator(semanticChecker, query)
+ : undefined,
+ });
+
+ const result = await cascade.invoke(query);
+ const stats = cascade.getLastCascadeResult();
+
+ const preview = result.content.toString().substring(0, 80);
+ console.log(` Response: ${preview}...`);
+
+ if (stats) {
+ totalQuality += stats.drafterQuality || 0;
+ totalLatency += stats.latencyMs;
+
+ // Track semantic score separately if we used semantic validation
+ if (mlAvailable && stats.drafterQuality) {
+ semanticScores.push(stats.drafterQuality);
+ totalSemanticScore += stats.drafterQuality;
+ }
+
+ if (stats.modelUsed === 'drafter') {
+ console.log(` ${COLORS.green}โ CASCADED${COLORS.reset} (quality: ${stats.drafterQuality?.toFixed(2)}, ${stats.latencyMs}ms)`);
+ cascaded++;
+ } else {
+ console.log(` ${COLORS.yellow}โ ESCALATED${COLORS.reset} (quality: ${stats.drafterQuality?.toFixed(2)}, ${stats.latencyMs}ms)`);
+ escalated++;
+ }
+ }
+
+ // Small delay to avoid rate limits
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+
+ const totalQueries = TEST_QUERIES.length;
+ const avgQuality = totalQuality / totalQueries;
+ const avgLatency = totalLatency / totalQueries;
+ const successRate = (cascaded / totalQueries) * 100;
+ const avgSemanticScore = semanticScores.length > 0
+ ? totalSemanticScore / semanticScores.length
+ : undefined;
+
+ console.log(`\n${COLORS.bold}Results:${COLORS.reset}`);
+ console.log(` Cascaded: ${cascaded}/${totalQueries}`);
+ console.log(` Escalated: ${escalated}/${totalQueries}`);
+ console.log(` Success Rate: ${successRate.toFixed(0)}%`);
+ console.log(` Avg Quality: ${avgQuality.toFixed(2)}`);
+ if (avgSemanticScore !== undefined) {
+ console.log(` Avg Semantic Score: ${avgSemanticScore.toFixed(2)}`);
+ }
+ console.log(` Avg Latency: ${avgLatency.toFixed(0)}ms`);
+
+ return {
+ pairName: pair.name,
+ provider: pair.provider,
+ totalQueries,
+ cascaded,
+ escalated,
+ avgQuality,
+ avgLatency,
+ successRate,
+ avgSemanticScore,
+ };
+}
+
+async function main() {
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โ COMPREHENSIVE LANGCHAIN BENCHMARK - SEMANTIC QUALITY VALIDATION โ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}\n`);
+
+ // ========================================================================
+ // STEP 1: Check LangSmith Configuration
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ Step 1: Checking Configuration${COLORS.reset}\n`);
+
+ if (!process.env.LANGSMITH_API_KEY) {
+ console.log(`${COLORS.red}โ LANGSMITH_API_KEY not set${COLORS.reset}`);
+ console.log('Set LANGSMITH_API_KEY to see traces in https://smith.langchain.com\n');
+ return;
+ }
+
+ console.log(`${COLORS.green}โ LangSmith tracing enabled${COLORS.reset}`);
+ console.log(` Project: ${process.env.LANGSMITH_PROJECT || 'default'}`);
+ console.log(` View traces: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}\n`);
+
+ // ========================================================================
+ // STEP 2: Initialize Semantic Quality Checker
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ง Step 2: Initializing Semantic Quality Checker${COLORS.reset}\n`);
+
+ const semanticChecker = new SemanticQualityChecker(
+ 0.5, // 50% similarity threshold
+ undefined,
+ true // Enable caching
+ );
+
+ const mlAvailable = await semanticChecker.isAvailable();
+
+ if (mlAvailable) {
+ console.log(`${COLORS.green}โ Semantic validation ENABLED${COLORS.reset}`);
+ console.log(` Embedding Model: BGE-small-en-v1.5 (ONNX)`);
+ console.log(` Similarity Threshold: 50%`);
+ console.log(` Caching: Enabled\n`);
+ } else {
+ console.log(`${COLORS.yellow}โ Semantic validation DISABLED${COLORS.reset}`);
+ console.log(` Install with: npm install @cascadeflow/ml @xenova/transformers`);
+ console.log(` Falling back to heuristic quality scoring\n`);
+ }
+
+ // ========================================================================
+ // STEP 3: Define Model Pairs
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ค Step 3: Configuring Model Pairs${COLORS.reset}\n`);
+
+ const modelPairs: ModelPair[] = [];
+
+ // OpenAI GPT-5 Family
+ modelPairs.push({
+ name: 'GPT-5 Nano โ GPT-5',
+ drafter: new ChatOpenAI({ model: 'gpt-5-nano', temperature: 1.0 }),
+ verifier: new ChatOpenAI({ model: 'gpt-5', temperature: 1.0 }),
+ provider: 'OpenAI',
+ expectedSavings: '66%',
+ });
+
+ modelPairs.push({
+ name: 'GPT-5 Mini โ GPT-5',
+ drafter: new ChatOpenAI({ model: 'gpt-5-mini', temperature: 1.0 }),
+ verifier: new ChatOpenAI({ model: 'gpt-5', temperature: 1.0 }),
+ provider: 'OpenAI',
+ expectedSavings: '50%',
+ });
+
+ modelPairs.push({
+ name: 'GPT-5 Nano โ GPT-5 Mini',
+ drafter: new ChatOpenAI({ model: 'gpt-5-nano', temperature: 1.0 }),
+ verifier: new ChatOpenAI({ model: 'gpt-5-mini', temperature: 1.0 }),
+ provider: 'OpenAI',
+ expectedSavings: '50%',
+ });
+
+ // Cross-Provider (Anthropic โ OpenAI)
+ if (process.env.ANTHROPIC_API_KEY) {
+ modelPairs.push({
+ name: 'Claude 3.5 Haiku โ GPT-5',
+ drafter: new ChatAnthropic({ model: 'claude-3-5-haiku-20241022' }),
+ verifier: new ChatOpenAI({ model: 'gpt-5', temperature: 1.0 }),
+ provider: 'Cross-Provider (AnthropicโOpenAI)',
+ expectedSavings: '80%',
+ });
+
+ modelPairs.push({
+ name: 'Claude 3.5 Haiku โ GPT-5 Mini',
+ drafter: new ChatAnthropic({ model: 'claude-3-5-haiku-20241022' }),
+ verifier: new ChatOpenAI({ model: 'gpt-5-mini', temperature: 1.0 }),
+ provider: 'Cross-Provider (AnthropicโOpenAI)',
+ expectedSavings: '60%',
+ });
+ } else {
+ console.log(`${COLORS.yellow}โ ANTHROPIC_API_KEY not set - skipping Anthropic tests${COLORS.reset}\n`);
+ }
+
+ console.log(`${COLORS.green}โ Configured ${modelPairs.length} model pairs${COLORS.reset}\n`);
+
+ // ========================================================================
+ // STEP 4: Run Benchmarks
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ Step 4: Running Benchmarks${COLORS.reset}\n`);
+ console.log(`Testing ${TEST_QUERIES.length} queries across ${modelPairs.length} model pairs...\n`);
+
+ const results: BenchmarkResult[] = [];
+
+ for (const pair of modelPairs) {
+ const result = await testModelPair(pair, semanticChecker, mlAvailable);
+ results.push(result);
+
+ // Clear cache between pairs
+ semanticChecker.clearCache();
+ }
+
+ // ========================================================================
+ // STEP 5: Display Summary
+ // ========================================================================
+
+ console.log(`\n${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}BENCHMARK SUMMARY${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}\n`);
+
+ console.log(`${COLORS.bold}Overall Results:${COLORS.reset}\n`);
+
+ for (const result of results) {
+ console.log(`${COLORS.cyan}${result.pairName}${COLORS.reset}`);
+ console.log(` Provider: ${result.provider}`);
+ console.log(` Success Rate: ${COLORS.green}${result.successRate.toFixed(0)}%${COLORS.reset} (${result.cascaded}/${result.totalQueries} cascaded)`);
+ console.log(` Avg Quality: ${result.avgQuality.toFixed(2)}`);
+ if (result.avgSemanticScore !== undefined) {
+ console.log(` Avg Semantic: ${result.avgSemanticScore.toFixed(2)}`);
+ }
+ console.log(` Avg Latency: ${result.avgLatency.toFixed(0)}ms`);
+ console.log();
+ }
+
+ // ========================================================================
+ // STEP 6: Performance Insights
+ // ========================================================================
+
+ console.log(`${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}PERFORMANCE INSIGHTS${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}\n`);
+
+ const totalCascaded = results.reduce((sum, r) => sum + r.cascaded, 0);
+ const totalEscalated = results.reduce((sum, r) => sum + r.escalated, 0);
+ const totalQueries = results.reduce((sum, r) => sum + r.totalQueries, 0);
+ const overallSuccessRate = (totalCascaded / totalQueries) * 100;
+
+ console.log(`${COLORS.bold}Aggregate Statistics:${COLORS.reset}`);
+ console.log(` Total Queries: ${totalQueries}`);
+ console.log(` Total Cascaded: ${COLORS.green}${totalCascaded}${COLORS.reset}`);
+ console.log(` Total Escalated: ${COLORS.yellow}${totalEscalated}${COLORS.reset}`);
+ console.log(` Overall Success Rate: ${COLORS.green}${overallSuccessRate.toFixed(1)}%${COLORS.reset}\n`);
+
+ console.log(`${COLORS.bold}Key Findings:${COLORS.reset}`);
+ if (mlAvailable) {
+ console.log(` ${COLORS.green}โ${COLORS.reset} Semantic validation helped ensure response relevance`);
+ console.log(` ${COLORS.green}โ${COLORS.reset} ML-based quality scoring active for all tests`);
+ } else {
+ console.log(` ${COLORS.yellow}โ ${COLORS.reset} Heuristic-based quality scoring used (install @cascadeflow/ml for semantic)`);
+ }
+ console.log(` ${COLORS.green}โ${COLORS.reset} Cross-provider cascades work seamlessly`);
+ console.log(` ${COLORS.green}โ${COLORS.reset} All traces sent to LangSmith for analysis\n`);
+
+ // ========================================================================
+ // STEP 7: LangSmith Instructions
+ // ========================================================================
+
+ console.log(`${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}VIEW RESULTS IN LANGSMITH${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}\n`);
+
+ console.log(`${COLORS.cyan}${COLORS.bold}๐ View Detailed Traces:${COLORS.reset}`);
+ console.log(` 1. Go to: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}`);
+ console.log(` 2. Select project: ${COLORS.yellow}${process.env.LANGSMITH_PROJECT || 'default'}${COLORS.reset}`);
+ console.log(` 3. Filter by: ${COLORS.yellow}last 1 hour${COLORS.reset}`);
+ console.log(` 4. Look for traces with "CascadeFlow" model name`);
+ console.log(` 5. Check cascade metadata in response_metadata:\n`);
+ console.log(` ${COLORS.green}cascade_decision${COLORS.reset}: "cascaded" or "escalated"`);
+ console.log(` ${COLORS.green}drafter_quality${COLORS.reset}: quality score (0-1)`);
+ console.log(` ${COLORS.green}model_used${COLORS.reset}: "drafter" or "verifier"`);
+ console.log(` ${COLORS.green}savings_percentage${COLORS.reset}: cost savings\n`);
+
+ console.log(`${COLORS.green}${COLORS.bold}โ Benchmark Complete!${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}\n`);
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/inspect-metadata.ts b/packages/langchain-cascadeflow/examples/inspect-metadata.ts
new file mode 100644
index 00000000..a9ec9075
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/inspect-metadata.ts
@@ -0,0 +1,47 @@
+/**
+ * Metadata Inspection Script
+ *
+ * Checks what metadata is actually being injected into responses
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { withCascade } from '../src/index.js';
+
+async function main() {
+ const drafter = new ChatOpenAI({ model: 'gpt-4o-mini', temperature: 0.7 });
+ const verifier = new ChatOpenAI({ model: 'gpt-4o', temperature: 0.7 });
+
+ const cascadeModel = withCascade({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ });
+
+ console.log('Testing metadata injection...\n');
+
+ // Test with simple query
+ const result = await cascadeModel.invoke('What is 2+2?');
+
+ console.log('=== Response Structure ===');
+ console.log('Content:', result.content);
+ console.log('\n=== Additional Kwargs ===');
+ console.log(JSON.stringify(result.additional_kwargs, null, 2));
+
+ console.log('\n=== Response Metadata ===');
+ console.log(JSON.stringify(result.response_metadata, null, 2));
+
+ console.log('\n=== Last Cascade Result ===');
+ const stats = cascadeModel.getLastCascadeResult();
+ console.log(JSON.stringify(stats, null, 2));
+
+ // Test calling _generate directly to see llmOutput
+ console.log('\n\n=== Testing _generate directly ===');
+ const { HumanMessage } = await import('@langchain/core/messages');
+ const chatResult = await cascadeModel._generate([new HumanMessage('What is the capital of France?')], {});
+
+ console.log('llmOutput:', JSON.stringify(chatResult.llmOutput, null, 2));
+ console.log('\nGeneration text:', chatResult.generations[0].text);
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/langsmith-all-models.ts b/packages/langchain-cascadeflow/examples/langsmith-all-models.ts
new file mode 100644
index 00000000..c15e1148
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/langsmith-all-models.ts
@@ -0,0 +1,195 @@
+/**
+ * LangSmith Integration Test - All Available Models
+ *
+ * Tests cascade routing with all user's models and sends traces to LangSmith.
+ * View results at: https://smith.langchain.com
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { ChatAnthropic } from '@langchain/anthropic';
+import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
+import { CascadeFlow } from '../src/index.js';
+
+const COLORS = {
+ reset: '\x1b[0m',
+ green: '\x1b[32m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ magenta: '\x1b[35m',
+ bold: '\x1b[1m',
+};
+
+// Test queries of varying complexity
+const TEST_QUERIES = [
+ { query: 'What is TypeScript?', complexity: 'Simple' },
+ { query: 'Explain the event loop in Node.js', complexity: 'Medium' },
+ { query: 'Design a distributed cache with Redis and implement consistency guarantees', complexity: 'Complex' },
+];
+
+async function testModelPair(
+ name: string,
+ drafter: any,
+ verifier: any,
+ provider: 'OpenAI' | 'Anthropic' | 'Google' | 'Cross-Provider'
+) {
+ console.log(`\n${COLORS.cyan}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}`);
+ console.log(`${COLORS.cyan}${COLORS.bold}Testing: ${name}${COLORS.reset}`);
+ console.log(`${COLORS.blue}Provider: ${provider}${COLORS.reset}`);
+ console.log(`${COLORS.cyan}${'='.repeat(100)}${COLORS.reset}\n`);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ costTrackingProvider: 'langsmith', // Use LangSmith for cost tracking
+ });
+
+ let cascaded = 0;
+ let escalated = 0;
+
+ for (const { query, complexity } of TEST_QUERIES) {
+ console.log(`${COLORS.blue}[${complexity}]${COLORS.reset} ${query}`);
+
+ const result = await cascade.invoke(query);
+ const stats = cascade.getLastCascadeResult();
+
+ const preview = result.content.toString().substring(0, 80);
+ console.log(` Response: ${preview}...`);
+
+ if (stats?.modelUsed === 'drafter') {
+ console.log(` ${COLORS.green}โ CASCADED${COLORS.reset} (quality: ${stats.drafterQuality.toFixed(2)}, ${stats.latencyMs}ms)`);
+ cascaded++;
+ } else {
+ console.log(` ${COLORS.yellow}โ ESCALATED${COLORS.reset} (quality: ${stats?.drafterQuality.toFixed(2)}, ${stats?.latencyMs}ms)`);
+ escalated++;
+ }
+ }
+
+ console.log(`\n${COLORS.bold}Results:${COLORS.reset}`);
+ console.log(` Cascaded: ${cascaded}/${TEST_QUERIES.length}`);
+ console.log(` Escalated: ${escalated}/${TEST_QUERIES.length}`);
+ console.log(` Success Rate: ${((cascaded / TEST_QUERIES.length) * 100).toFixed(0)}%`);
+}
+
+async function main() {
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โ LangSmith Integration - All Model Cascade Tests โ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}\n`);
+
+ // Check LangSmith configuration
+ if (!process.env.LANGSMITH_API_KEY) {
+ console.log(`${COLORS.yellow}โ LANGSMITH_API_KEY not set${COLORS.reset}`);
+ console.log('LangSmith tracing will not be available.');
+ console.log('Set LANGSMITH_API_KEY to see traces in https://smith.langchain.com\n');
+ } else {
+ console.log(`${COLORS.green}โ LangSmith tracing enabled${COLORS.reset}`);
+ console.log(` Project: ${process.env.LANGSMITH_PROJECT || 'default'}`);
+ console.log(` View traces: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}\n`);
+ }
+
+ // =====================================================================
+ // OpenAI GPT-5 Family Tests
+ // =====================================================================
+
+ console.log(`\n${COLORS.magenta}${COLORS.bold}โโโ OpenAI GPT-5 Family โโโ${COLORS.reset}`);
+
+ // Test 1: GPT-5 Nano โ GPT-5
+ await testModelPair(
+ 'GPT-5 Nano โ GPT-5 (66% savings)',
+ new ChatOpenAI({ modelName: 'gpt-5-nano', temperature: 1.0 }),
+ new ChatOpenAI({ modelName: 'gpt-5', temperature: 1.0 }),
+ 'OpenAI'
+ );
+
+ // Test 2: GPT-5 Mini โ GPT-5
+ await testModelPair(
+ 'GPT-5 Mini โ GPT-5 (50% savings)',
+ new ChatOpenAI({ modelName: 'gpt-5-mini', temperature: 1.0 }),
+ new ChatOpenAI({ modelName: 'gpt-5', temperature: 1.0 }),
+ 'OpenAI'
+ );
+
+ // Test 3: GPT-5 Nano โ GPT-5 Mini
+ await testModelPair(
+ 'GPT-5 Nano โ GPT-5 Mini (50% savings)',
+ new ChatOpenAI({ modelName: 'gpt-5-nano', temperature: 1.0 }),
+ new ChatOpenAI({ modelName: 'gpt-5-mini', temperature: 1.0 }),
+ 'OpenAI'
+ );
+
+ // =====================================================================
+ // Cross-Provider Tests (Anthropic โ OpenAI)
+ // =====================================================================
+
+ console.log(`\n${COLORS.magenta}${COLORS.bold}โโโ Cross-Provider: Anthropic โ OpenAI โโโ${COLORS.reset}`);
+
+ if (!process.env.ANTHROPIC_API_KEY) {
+ console.log(`${COLORS.yellow}โ ANTHROPIC_API_KEY not set - skipping Anthropic tests${COLORS.reset}\n`);
+ } else {
+ // Test 4: Claude Haiku โ GPT-5
+ await testModelPair(
+ 'Claude 3.5 Haiku โ GPT-5 (80% savings)',
+ new ChatAnthropic({ model: 'claude-3-5-haiku-20241022' }),
+ new ChatOpenAI({ modelName: 'gpt-5', temperature: 1.0 }),
+ 'Cross-Provider'
+ );
+
+ // Test 5: Claude Haiku โ GPT-5 Mini
+ await testModelPair(
+ 'Claude 3.5 Haiku โ GPT-5 Mini (60% savings)',
+ new ChatAnthropic({ model: 'claude-3-5-haiku-20241022' }),
+ new ChatOpenAI({ modelName: 'gpt-5-mini', temperature: 1.0 }),
+ 'Cross-Provider'
+ );
+ }
+
+ // =====================================================================
+ // Google Gemini Tests
+ // =====================================================================
+
+ console.log(`\n${COLORS.magenta}${COLORS.bold}โโโ Cross-Provider: Google โ OpenAI โโโ${COLORS.reset}`);
+
+ if (!process.env.GOOGLE_API_KEY) {
+ console.log(`${COLORS.yellow}โ GOOGLE_API_KEY not set - skipping Gemini tests${COLORS.reset}\n`);
+ } else {
+ // Test 6: Gemini Flash โ GPT-5
+ await testModelPair(
+ 'Gemini 2.5 Flash โ GPT-5 (75% savings)',
+ new ChatGoogleGenerativeAI({ modelName: 'gemini-2.5-flash' }),
+ new ChatOpenAI({ modelName: 'gpt-5', temperature: 1.0 }),
+ 'Cross-Provider'
+ );
+
+ // Test 7: Gemini Flash โ Gemini Pro
+ await testModelPair(
+ 'Gemini 2.5 Flash โ Gemini 2.5 Pro (75% savings)',
+ new ChatGoogleGenerativeAI({ modelName: 'gemini-2.5-flash' }),
+ new ChatGoogleGenerativeAI({ modelName: 'gemini-2.5-pro' }),
+ 'Google'
+ );
+ }
+
+ // =====================================================================
+ // Summary
+ // =====================================================================
+
+ console.log(`\n${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}Testing Complete!${COLORS.reset}\n`);
+
+ console.log(`${COLORS.cyan}${COLORS.bold}๐ View Results in LangSmith:${COLORS.reset}`);
+ console.log(` 1. Go to: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}`);
+ console.log(` 2. Select project: ${COLORS.yellow}${process.env.LANGSMITH_PROJECT || 'default'}${COLORS.reset}`);
+ console.log(` 3. Filter by: ${COLORS.yellow}last 1 hour${COLORS.reset}`);
+ console.log(` 4. Look for traces with metadata:`);
+ console.log(` - ${COLORS.green}cascade_decision${COLORS.reset}: "cascaded" or "escalated"`);
+ console.log(` - ${COLORS.green}drafter_quality${COLORS.reset}: quality score (0-1)`);
+ console.log(` - ${COLORS.green}model_used${COLORS.reset}: "drafter" or "verifier"`);
+ console.log(` - ${COLORS.green}savings_percentage${COLORS.reset}: cost savings\n`);
+
+ console.log(`${COLORS.green}${COLORS.bold}โ All traces sent to LangSmith!${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}${'='.repeat(100)}${COLORS.reset}\n`);
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/langsmith-tracing.ts b/packages/langchain-cascadeflow/examples/langsmith-tracing.ts
new file mode 100644
index 00000000..f58dc17a
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/langsmith-tracing.ts
@@ -0,0 +1,143 @@
+/**
+ * LangSmith Tracing Example
+ *
+ * Demonstrates how CascadeFlow cost metadata appears in LangSmith traces
+ * for observability and cost tracking.
+ *
+ * Prerequisites:
+ * 1. Set OPENAI_API_KEY environment variable
+ * 2. Set LANGSMITH_API_KEY environment variable
+ * 3. Optionally set LANGSMITH_PROJECT (defaults to "default")
+ *
+ * Run:
+ * OPENAI_API_KEY=xxx LANGSMITH_API_KEY=xxx npx tsx examples/langsmith-tracing.ts
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { withCascade } from '../src/index.js';
+
+async function main() {
+ console.log('=== CascadeFlow LangSmith Integration Demo ===\n');
+
+ // Check for required API keys
+ if (!process.env.OPENAI_API_KEY) {
+ console.error('โ OPENAI_API_KEY environment variable is required');
+ process.exit(1);
+ }
+
+ if (!process.env.LANGSMITH_API_KEY) {
+ console.warn('โ ๏ธ LANGSMITH_API_KEY not set - tracing will be disabled');
+ console.warn(' Set it to see cascade metadata in LangSmith!\n');
+ } else {
+ const project = process.env.LANGSMITH_PROJECT || 'default';
+ console.log(`โ
LangSmith tracing enabled`);
+ console.log(` Project: ${project}`);
+ console.log(` View traces at: https://smith.langchain.com/\n`);
+ }
+
+ // Configure models
+ const drafter = new ChatOpenAI({
+ model: 'gpt-4o-mini',
+ temperature: 0.7,
+ });
+
+ const verifier = new ChatOpenAI({
+ model: 'gpt-4o',
+ temperature: 0.7,
+ });
+
+ // Create cascade model with cost tracking enabled (default)
+ const cascadeModel = withCascade({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enableCostTracking: true, // This enables LangSmith metadata injection
+ });
+
+ console.log('Running test queries...\n');
+
+ // Test 1: Simple query (likely to accept drafter)
+ console.log('--- Test 1: Simple Query (High Quality) ---');
+ const result1 = await cascadeModel.invoke('What is 2+2?');
+ console.log(`Answer: ${result1.content}`);
+
+ const stats1 = cascadeModel.getLastCascadeResult();
+ if (stats1) {
+ console.log(`Model used: ${stats1.modelUsed}`);
+ console.log(`Quality score: ${stats1.drafterQuality?.toFixed(2)}`);
+ console.log(`Total cost: $${stats1.totalCost.toFixed(6)}`);
+ console.log(`Savings: ${stats1.savingsPercentage.toFixed(1)}%`);
+ }
+ console.log();
+
+ // Test 2: Complex query (might cascade to verifier)
+ console.log('--- Test 2: Complex Query (May Cascade) ---');
+ const result2 = await cascadeModel.invoke(
+ 'Explain the key differences between TypeScript and JavaScript, focusing on type safety and development workflow.'
+ );
+ console.log(`Answer: ${result2.content.slice(0, 200)}...`);
+
+ const stats2 = cascadeModel.getLastCascadeResult();
+ if (stats2) {
+ console.log(`Model used: ${stats2.modelUsed}`);
+ console.log(`Quality score: ${stats2.drafterQuality?.toFixed(2)}`);
+ console.log(`Total cost: $${stats2.totalCost.toFixed(6)}`);
+ console.log(`Savings: ${stats2.savingsPercentage.toFixed(1)}%`);
+ }
+ console.log();
+
+ // Test 3: Using with custom metadata and tags
+ console.log('--- Test 3: With Custom LangSmith Tags ---');
+ const result3 = await cascadeModel.invoke(
+ 'What are the benefits of using a cascade pattern for LLM cost optimization?',
+ {
+ tags: ['cascade-demo', 'cost-optimization'],
+ metadata: {
+ user_id: 'demo-user-123',
+ session_id: 'demo-session-456',
+ feature: 'cost-optimization-demo',
+ },
+ }
+ );
+ console.log(`Answer: ${result3.content.slice(0, 200)}...`);
+
+ const stats3 = cascadeModel.getLastCascadeResult();
+ if (stats3) {
+ console.log(`Model used: ${stats3.modelUsed}`);
+ console.log(`Quality score: ${stats3.drafterQuality?.toFixed(2)}`);
+ console.log(`Total cost: $${stats3.totalCost.toFixed(6)}`);
+ console.log(`Savings: ${stats3.savingsPercentage.toFixed(1)}%`);
+ }
+ console.log();
+
+ // Test 4: Chain example with bind()
+ console.log('--- Test 4: Chained with bind() ---');
+ const boundModel = cascadeModel.bind({ temperature: 0.1 });
+ const result4 = await boundModel.invoke('What is the capital of France?');
+ console.log(`Answer: ${result4.content}`);
+
+ const stats4 = cascadeModel.getLastCascadeResult();
+ if (stats4) {
+ console.log(`Model used: ${stats4.modelUsed}`);
+ console.log(`Quality score: ${stats4.drafterQuality?.toFixed(2)}`);
+ console.log(`Total cost: $${stats4.totalCost.toFixed(6)}`);
+ console.log(`Savings: ${stats4.savingsPercentage.toFixed(1)}%`);
+ }
+ console.log();
+
+ console.log('=== Demo Complete ===');
+ if (process.env.LANGSMITH_API_KEY) {
+ console.log('\n๐ Check LangSmith to see cascade metadata in traces:');
+ console.log(' - drafterTokens: Input/output token counts from drafter');
+ console.log(' - verifierTokens: Input/output token counts from verifier (if cascaded)');
+ console.log(' - drafterCost: Cost of drafter call');
+ console.log(' - verifierCost: Cost of verifier call');
+ console.log(' - totalCost: Combined cost');
+ console.log(' - savingsPercentage: Cost savings vs using verifier only');
+ console.log(' - modelUsed: Which model provided the final response');
+ console.log(' - accepted: Whether drafter response was accepted');
+ console.log(' - drafterQuality: Quality score (0-1)');
+ }
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/model-discovery.ts b/packages/langchain-cascadeflow/examples/model-discovery.ts
new file mode 100644
index 00000000..bd0304e0
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/model-discovery.ts
@@ -0,0 +1,211 @@
+/**
+ * Model Discovery Example
+ *
+ * Shows how to discover the best cascade pairs from YOUR existing LangChain models.
+ * No hard-coded models - this works with whatever models YOU have configured!
+ */
+
+import {
+ CascadeFlow,
+ discoverCascadePairs,
+ findBestCascadePair,
+ analyzeModel,
+ compareModels,
+ validateCascadePair,
+} from '../src/index.js';
+import { ChatOpenAI } from '@langchain/openai';
+
+console.log('='.repeat(80));
+console.log('MODEL DISCOVERY - Works with YOUR models!');
+console.log('='.repeat(80));
+
+// ============================================================================
+// Setup: Create YOUR models (with YOUR API keys)
+// ============================================================================
+console.log('\nYOUR Models (configured with your own API keys):');
+console.log('-'.repeat(80));
+
+// These are YOUR models - could be OpenAI, Anthropic, local Ollama, etc.
+const myModels = [
+ new ChatOpenAI({ modelName: 'gpt-3.5-turbo' }), // Your cheap model
+ new ChatOpenAI({ modelName: 'gpt-4o-mini' }), // Your fast model
+ new ChatOpenAI({ modelName: 'gpt-4o' }), // Your powerful model
+ // Add more of YOUR models here:
+ // new ChatAnthropic({ model: 'claude-3-haiku' }),
+ // new ChatOllama({ model: 'llama2' }),
+ // etc.
+];
+
+console.log(`You have ${myModels.length} models configured.`);
+
+// ============================================================================
+// Example 1: Discover Best Cascade Pairs
+// ============================================================================
+console.log('\n\n1. Discover Best Cascade Pairs from YOUR Models');
+console.log('-'.repeat(80));
+
+const suggestions = discoverCascadePairs(myModels);
+
+console.log(`\nFound ${suggestions.length} valid cascade pairs:`);
+suggestions.forEach(pair => {
+ console.log(`\n#${pair.rank}:`);
+ console.log(` Drafter: ${pair.analysis.drafterModel}`);
+ console.log(` Verifier: ${pair.analysis.verifierModel}`);
+ console.log(` Estimated savings: ${pair.analysis.estimatedSavings.toFixed(1)}%`);
+ console.log(` ${pair.analysis.recommendation}`);
+});
+
+// ============================================================================
+// Example 2: Quick - Find Best Pair
+// ============================================================================
+console.log('\n\n2. Quick Way - Find Best Pair');
+console.log('-'.repeat(80));
+
+const best = findBestCascadePair(myModels);
+
+if (best) {
+ console.log('\nBest cascade configuration:');
+ console.log(`Drafter: ${best.analysis.drafterModel}`);
+ console.log(`Verifier: ${best.analysis.verifierModel}`);
+ console.log(`Savings: ${best.estimatedSavings.toFixed(1)}%`);
+
+ // Use it!
+ const cascade = new CascadeFlow({
+ drafter: best.drafter,
+ verifier: best.verifier,
+ });
+
+ (async () => {
+ const result = await cascade.invoke('What is TypeScript?');
+ console.log(`\nResponse: ${result.content.substring(0, 150)}...`);
+
+ const stats = cascade.getLastCascadeResult();
+ console.log(`Model used: ${stats?.modelUsed}`);
+ console.log(`Actual cost: $${stats?.totalCost.toFixed(6)}`);
+ console.log(`Actual savings: ${stats?.savingsPercentage.toFixed(1)}%`);
+ })().catch(console.error);
+} else {
+ console.log('No valid cascade pairs found. Need at least 2 models with different costs.');
+}
+
+// ============================================================================
+// Example 3: Analyze Individual Models
+// ============================================================================
+console.log('\n\n3. Analyze Your Individual Models');
+console.log('-'.repeat(80));
+
+myModels.forEach(model => {
+ const analysis = analyzeModel(model);
+ console.log(`\n${analysis.modelName} (${analysis.provider}):`);
+ console.log(` Tier: ${analysis.tier}`);
+ if (analysis.estimatedCost) {
+ console.log(` Cost: $${analysis.estimatedCost.input}/$${analysis.estimatedCost.output} per 1M tokens`);
+ } else {
+ console.log(` Cost: Unknown (add to MODEL_PRICING_REFERENCE if you know it)`);
+ }
+ console.log(` ${analysis.recommendation}`);
+});
+
+// ============================================================================
+// Example 4: Compare Models
+// ============================================================================
+console.log('\n\n4. Compare and Rank Your Models');
+console.log('-'.repeat(80));
+
+const comparison = compareModels(myModels);
+
+console.log('\nBest Drafter Candidates (cheap, fast):');
+comparison.drafterCandidates.forEach((c, i) => {
+ console.log(` ${i + 1}. ${c.analysis.modelName} - ${c.analysis.tier}`);
+});
+
+console.log('\nBest Verifier Candidates (expensive, powerful):');
+comparison.verifierCandidates.forEach((c, i) => {
+ console.log(` ${i + 1}. ${c.analysis.modelName} - ${c.analysis.tier}`);
+});
+
+// ============================================================================
+// Example 5: Validate a Specific Pair
+// ============================================================================
+console.log('\n\n5. Validate a Specific Cascade Pair');
+console.log('-'.repeat(80));
+
+if (myModels.length >= 2) {
+ const validation = validateCascadePair(myModels[0], myModels[myModels.length - 1]);
+
+ console.log(`\nPair: ${myModels[0]._llmType()} โ ${myModels[myModels.length - 1]._llmType()}`);
+ console.log(`Valid: ${validation.valid}`);
+ console.log(`Estimated savings: ${validation.estimatedSavings.toFixed(1)}%`);
+ console.log(`Recommendation: ${validation.recommendation}`);
+
+ if (validation.warnings.length > 0) {
+ console.log('\nWarnings:');
+ validation.warnings.forEach((w, i) => {
+ console.log(` ${i + 1}. ${w}`);
+ });
+ }
+}
+
+// ============================================================================
+// Example 6: Filter by Requirements
+// ============================================================================
+console.log('\n\n6. Filter Cascade Pairs by Requirements');
+console.log('-'.repeat(80));
+
+// Only show pairs with at least 50% savings
+const highSavingsPairs = discoverCascadePairs(myModels, {
+ minSavings: 50,
+});
+
+console.log(`\nPairs with โฅ50% savings: ${highSavingsPairs.length}`);
+highSavingsPairs.forEach(pair => {
+ console.log(` - ${pair.analysis.drafterModel} โ ${pair.analysis.verifierModel} (${pair.analysis.estimatedSavings.toFixed(1)}%)`);
+});
+
+// Only show pairs from the same provider
+const sameProviderPairs = discoverCascadePairs(myModels, {
+ requireSameProvider: true,
+});
+
+console.log(`\nSame-provider pairs: ${sameProviderPairs.length}`);
+sameProviderPairs.forEach(pair => {
+ console.log(` - ${pair.analysis.drafterModel} โ ${pair.analysis.verifierModel}`);
+});
+
+// ============================================================================
+// Example 7: Real-World Usage Pattern
+// ============================================================================
+console.log('\n\n7. Real-World Usage Pattern');
+console.log('-'.repeat(80));
+
+console.log('\nTypical workflow:');
+console.log('1. Configure YOUR LangChain models (with your API keys)');
+console.log('2. Discover best cascade pairs');
+console.log('3. Create cascade with best pair');
+console.log('4. Use it just like a normal LangChain model!');
+
+console.log('\nCode example:');
+console.log(`
+// Step 1: YOUR models
+const myModels = [
+ new ChatOpenAI({ model: 'gpt-4o-mini' }), // configured with YOUR key
+ new ChatOpenAI({ model: 'gpt-4o' }), // configured with YOUR key
+];
+
+// Step 2: Find best pair
+const best = findBestCascadePair(myModels);
+
+// Step 3: Create cascade
+const cascade = new CascadeFlow({
+ drafter: best.drafter,
+ verifier: best.verifier,
+});
+
+// Step 4: Use it!
+const result = await cascade.invoke('Your question');
+`);
+
+console.log('\n' + '='.repeat(80));
+console.log('โ Model discovery complete!');
+console.log(' All analysis done on YOUR models - no hardcoded instances!');
+console.log('='.repeat(80) + '\n');
diff --git a/packages/langchain-cascadeflow/examples/realistic-usage-langsmith.ts b/packages/langchain-cascadeflow/examples/realistic-usage-langsmith.ts
new file mode 100644
index 00000000..d09aab88
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/realistic-usage-langsmith.ts
@@ -0,0 +1,219 @@
+/**
+ * Realistic LangChain CascadeFlow Usage with LangSmith Tracking
+ *
+ * This example demonstrates how developers would ACTUALLY use the integration:
+ * 1. Create LangChain models once (reuse existing instances)
+ * 2. Wrap with CascadeFlow
+ * 3. Use the wrapped model multiple times
+ * 4. See BOTH chat model calls AND cascade metadata in LangSmith
+ *
+ * Requirements:
+ * - OPENAI_API_KEY
+ * - ANTHROPIC_API_KEY (optional)
+ * - LANGSMITH_API_KEY
+ * - LANGSMITH_PROJECT
+ * - LANGSMITH_TRACING=true
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { ChatAnthropic } from '@langchain/anthropic';
+import { CascadeFlow } from '../src/index.js';
+
+const COLORS = {
+ reset: '\x1b[0m',
+ green: '\x1b[32m',
+ yellow: '\x1b[33m',
+ blue: '\x1b[34m',
+ cyan: '\x1b[36m',
+ magenta: '\x1b[35m',
+ bold: '\x1b[1m',
+};
+
+async function main() {
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โ REALISTIC LANGCHAIN USAGE - LangSmith Integration Demo โ${COLORS.reset}`);
+ console.log(`${COLORS.magenta}${COLORS.bold}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${COLORS.reset}\n`);
+
+ // ========================================================================
+ // STEP 1: Check LangSmith Configuration
+ // ========================================================================
+
+ if (!process.env.LANGSMITH_API_KEY) {
+ console.log(`${COLORS.yellow}โ LANGSMITH_API_KEY not set${COLORS.reset}`);
+ console.log('Set LANGSMITH_API_KEY to see traces in https://smith.langchain.com\n');
+ return;
+ }
+
+ console.log(`${COLORS.green}โ LangSmith Configuration${COLORS.reset}`);
+ console.log(` API Key: Set`);
+ console.log(` Project: ${process.env.LANGSMITH_PROJECT || 'default'}`);
+ console.log(` Tracing: ${process.env.LANGSMITH_TRACING || 'false'}`);
+ console.log(` Dashboard: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}\n`);
+
+ // ========================================================================
+ // STEP 2: Developer Creates Their LangChain Models ONCE
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ฆ Step 1: Creating LangChain Models (Like Any Developer Would)${COLORS.reset}\n`);
+
+ // Developers create these models ONCE and reuse them
+ const drafterModel = new ChatOpenAI({
+ model: 'gpt-5-nano',
+ temperature: 1.0,
+ });
+
+ const verifierModel = new ChatOpenAI({
+ model: 'gpt-5',
+ temperature: 1.0,
+ });
+
+ console.log(`${COLORS.green}โ Created ChatOpenAI models${COLORS.reset}`);
+ console.log(` Drafter: gpt-5-nano (cheap, fast)`);
+ console.log(` Verifier: gpt-5 (accurate, expensive)\n`);
+
+ // ========================================================================
+ // STEP 3: Wrap Models with CascadeFlow ONCE
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ Step 2: Wrapping with CascadeFlow${COLORS.reset}\n`);
+
+ const cascade = new CascadeFlow({
+ drafter: drafterModel,
+ verifier: verifierModel,
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ costTrackingProvider: 'langsmith', // Use LangSmith for cost tracking
+ });
+
+ console.log(`${COLORS.green}โ Wrapped models with CascadeFlow${COLORS.reset}`);
+ console.log(` Quality Threshold: 0.7`);
+ console.log(` Cost Tracking: LangSmith (server-side)\n`);
+
+ // ========================================================================
+ // STEP 4: Use the Wrapped Model Multiple Times (Like Developers Would)
+ // ========================================================================
+
+ console.log(`${COLORS.bold}๐ Step 3: Using the Cascade Model (Multiple Queries)${COLORS.reset}\n`);
+ console.log(`${COLORS.cyan}Watch LangSmith for:${COLORS.reset}`);
+ console.log(` โข ChatOpenAI model traces (gpt-5-nano, gpt-5)`);
+ console.log(` โข Cascade decision metadata`);
+ console.log(` โข Token usage and costs\n`);
+ console.log(`${COLORS.cyan}${'='.repeat(80)}${COLORS.reset}\n`);
+
+ const queries = [
+ { q: 'What is TypeScript?', type: 'Simple' },
+ { q: 'Explain the event loop in Node.js', type: 'Medium' },
+ { q: 'Design a distributed cache with Redis', type: 'Complex' },
+ { q: 'What is async/await in JavaScript?', type: 'Medium' },
+ { q: 'How do I reverse a string in Python?', type: 'Simple' },
+ ];
+
+ for (let i = 0; i < queries.length; i++) {
+ const { q, type } = queries[i];
+
+ console.log(`${COLORS.blue}Query ${i + 1}/${queries.length}${COLORS.reset} [${type}]: ${q}`);
+
+ const startTime = Date.now();
+ const result = await cascade.invoke(q);
+ const elapsed = Date.now() - startTime;
+
+ const stats = cascade.getLastCascadeResult();
+
+ const preview = result.content.toString().substring(0, 100);
+ console.log(` Response: ${preview}...`);
+
+ if (stats) {
+ const icon = stats.modelUsed === 'drafter' ? 'โ' : 'โ ';
+ const color = stats.modelUsed === 'drafter' ? COLORS.green : COLORS.yellow;
+ const decision = stats.modelUsed === 'drafter' ? 'CASCADED' : 'ESCALATED';
+
+ console.log(` ${color}${icon} ${decision}${COLORS.reset} (quality: ${stats.drafterQuality?.toFixed(2)}, ${elapsed}ms)`);
+ console.log(` Model: ${stats.modelUsed === 'drafter' ? 'gpt-5-nano' : 'gpt-5'}`);
+ }
+
+ console.log();
+
+ // Small delay to make traces easier to follow in LangSmith
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ // ========================================================================
+ // STEP 5: Optional - Test Cross-Provider Cascade
+ // ========================================================================
+
+ if (process.env.ANTHROPIC_API_KEY) {
+ console.log(`${COLORS.cyan}${'='.repeat(80)}${COLORS.reset}\n`);
+ console.log(`${COLORS.bold}๐ Bonus: Cross-Provider Cascade (Anthropic โ OpenAI)${COLORS.reset}\n`);
+
+ const claudeDrafter = new ChatAnthropic({
+ model: 'claude-3-5-haiku-20241022',
+ });
+
+ const crossProviderCascade = new CascadeFlow({
+ drafter: claudeDrafter,
+ verifier: verifierModel, // Reuse existing OpenAI verifier
+ qualityThreshold: 0.7,
+ enableCostTracking: true,
+ costTrackingProvider: 'langsmith',
+ });
+
+ console.log(`${COLORS.green}โ Created cross-provider cascade${COLORS.reset}`);
+ console.log(` Drafter: Claude 3.5 Haiku (Anthropic)`);
+ console.log(` Verifier: GPT-5 (OpenAI)\n`);
+
+ const crossQuery = 'Explain the benefits of TypeScript';
+ console.log(`${COLORS.blue}Cross-Provider Query:${COLORS.reset} ${crossQuery}`);
+
+ const result = await crossProviderCascade.invoke(crossQuery);
+ const stats = crossProviderCascade.getLastCascadeResult();
+
+ const preview = result.content.toString().substring(0, 100);
+ console.log(` Response: ${preview}...`);
+
+ if (stats) {
+ const decision = stats.modelUsed === 'drafter' ? 'CASCADED' : 'ESCALATED';
+ const model = stats.modelUsed === 'drafter' ? 'Claude 3.5 Haiku' : 'GPT-5';
+
+ console.log(` ${decision} (quality: ${stats.drafterQuality?.toFixed(2)})`);
+ console.log(` Model: ${model}\n`);
+ }
+ }
+
+ // ========================================================================
+ // STEP 6: Summary and LangSmith Instructions
+ // ========================================================================
+
+ console.log(`${COLORS.cyan}${'='.repeat(80)}${COLORS.reset}\n`);
+ console.log(`${COLORS.magenta}${COLORS.bold}๐ VIEW RESULTS IN LANGSMITH${COLORS.reset}\n`);
+
+ console.log(`${COLORS.bold}What to Look For:${COLORS.reset}\n`);
+
+ console.log(`${COLORS.green}1. Chat Model Traces:${COLORS.reset}`);
+ console.log(` โข Look for "ChatOpenAI" runs in your project`);
+ console.log(` โข You should see calls to both gpt-5-nano and gpt-5`);
+ console.log(` โข Each run shows token usage, latency, and costs\n`);
+
+ console.log(`${COLORS.green}2. Cascade Metadata:${COLORS.reset}`);
+ console.log(` โข Click on any ChatOpenAI run`);
+ console.log(` โข Look in "Outputs" โ "response_metadata" โ "cascade"`);
+ console.log(` โข You'll see:`);
+ console.log(` - cascade_decision: "cascaded" or "escalated"`);
+ console.log(` - drafter_quality: 0-1 quality score`);
+ console.log(` - model_used: "drafter" or "verifier"`);
+ console.log(` - savings_percentage: % saved vs always using verifier\n`);
+
+ console.log(`${COLORS.green}3. Cost Analysis:${COLORS.reset}`);
+ console.log(` โข LangSmith calculates costs server-side`);
+ console.log(` โข Compare total cost of cascade vs. always using verifier`);
+ console.log(` โข You should see ~50-70% savings on average\n`);
+
+ console.log(`${COLORS.cyan}${COLORS.bold}๐ Quick Links:${COLORS.reset}`);
+ console.log(` Dashboard: ${COLORS.cyan}https://smith.langchain.com${COLORS.reset}`);
+ console.log(` Project: ${COLORS.yellow}${process.env.LANGSMITH_PROJECT || 'default'}${COLORS.reset}`);
+ console.log(` Filter: Last 1 hour\n`);
+
+ console.log(`${COLORS.green}${COLORS.bold}โ Demo Complete!${COLORS.reset}`);
+ console.log(`${COLORS.cyan}${'='.repeat(80)}${COLORS.reset}\n`);
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/streaming-cascade.ts b/packages/langchain-cascadeflow/examples/streaming-cascade.ts
new file mode 100644
index 00000000..bcd48562
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/streaming-cascade.ts
@@ -0,0 +1,84 @@
+/**
+ * Streaming Cascade Example
+ *
+ * Demonstrates real-time streaming with CascadeFlow:
+ * 1. Stream drafter optimistically (user sees output immediately)
+ * 2. Check quality after drafter completes
+ * 3. If quality insufficient, show switch message and stream verifier
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { withCascade } from '../src/index.js';
+
+async function main() {
+ console.log('๐ CascadeFlow Streaming Example\n');
+
+ // Configure cascade with drafter and verifier
+ const cascade = withCascade({
+ drafter: new ChatOpenAI({
+ model: 'gpt-4o-mini',
+ temperature: 0.7,
+ }),
+ verifier: new ChatOpenAI({
+ model: 'gpt-4o',
+ temperature: 0.7,
+ }),
+ qualityThreshold: 0.7,
+ });
+
+ // Example 1: Simple query (likely accepted by drafter)
+ console.log('Example 1: Simple Query (likely cascaded)\n');
+ console.log('Q: What is 2+2?\n');
+ console.log('A: ');
+
+ const stream1 = await cascade.stream('What is 2+2?');
+
+ for await (const chunk of stream1) {
+ const content = typeof chunk.content === 'string' ? chunk.content : '';
+ process.stdout.write(content);
+ }
+
+ console.log('\n\n---\n');
+
+ // Example 2: Complex query (likely escalated to verifier)
+ console.log('Example 2: Complex Query (may escalate)\n');
+ console.log('Q: Explain quantum entanglement and its implications for quantum computing\n');
+ console.log('A: ');
+
+ const stream2 = await cascade.stream(
+ 'Explain quantum entanglement and its implications for quantum computing'
+ );
+
+ for await (const chunk of stream2) {
+ const content = typeof chunk.content === 'string' ? chunk.content : '';
+ process.stdout.write(content);
+ }
+
+ console.log('\n\n---\n');
+
+ // Example 3: Low quality query (forces cascade)
+ console.log('Example 3: Ambiguous Query (likely escalates)\n');
+ console.log('Q: Tell me about it\n');
+ console.log('A: ');
+
+ const stream3 = await cascade.stream('Tell me about it');
+
+ for await (const chunk of stream3) {
+ const content = typeof chunk.content === 'string' ? chunk.content : '';
+ process.stdout.write(content);
+ }
+
+ console.log('\n\n---\n');
+
+ // Show final cascade statistics
+ const stats = cascade.getLastCascadeResult();
+ if (stats) {
+ console.log('\n๐ Cascade Statistics:');
+ console.log(` Model Used: ${stats.modelUsed}`);
+ console.log(` Drafter Quality: ${stats.drafterQuality.toFixed(2)}`);
+ console.log(` Accepted: ${stats.accepted}`);
+ console.log(` Latency: ${stats.latencyMs}ms`);
+ }
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/examples/validation-benchmark.ts b/packages/langchain-cascadeflow/examples/validation-benchmark.ts
new file mode 100644
index 00000000..1af00d53
--- /dev/null
+++ b/packages/langchain-cascadeflow/examples/validation-benchmark.ts
@@ -0,0 +1,315 @@
+/**
+ * Comprehensive Validation Benchmark
+ *
+ * Tests:
+ * - Streaming vs non-streaming behavior
+ * - Cascading effectiveness (not 100% drafter acceptance)
+ * - PreRouter performance
+ * - Agent role metadata
+ * - Various query complexity levels (trivial/expert, short/long)
+ */
+
+import { ChatOpenAI } from '@langchain/openai';
+import { ChatAnthropic } from '@langchain/anthropic';
+import { withCascade, createPreRouter } from '../src/index.js';
+
+// Test queries categorized by complexity and length
+const TEST_QUERIES = {
+ trivialShort: [
+ 'What is 2+2?',
+ 'What color is the sky?',
+ 'How many days in a week?',
+ 'What is the capital of France?',
+ 'Spell "hello"',
+ ],
+ trivialLong: [
+ 'Can you please explain to me in a detailed way what the result of adding two plus two together would be, including any mathematical principles involved?',
+ 'I would like a comprehensive explanation of what color the sky typically appears to be during a clear day, including the scientific reasons behind this phenomenon.',
+ 'Could you provide me with a thorough explanation of how many days are contained within a standard calendar week, along with the historical context?',
+ ],
+ moderateShort: [
+ 'Explain photosynthesis',
+ 'What is machine learning?',
+ 'How does DNA work?',
+ 'What is blockchain?',
+ 'Explain quantum physics',
+ ],
+ moderateLong: [
+ 'Can you provide a comprehensive explanation of how photosynthesis works in plants, including the light-dependent and light-independent reactions, the role of chlorophyll, and how this process contributes to the global carbon cycle?',
+ 'Please explain in detail what machine learning is, how it differs from traditional programming, what the main types of machine learning are, and provide examples of real-world applications.',
+ 'I need a detailed explanation of how DNA functions as the genetic blueprint for living organisms, including its structure, replication process, and how it codes for proteins.',
+ ],
+ expertShort: [
+ 'Explain the P vs NP problem',
+ 'What is the Riemann Hypothesis?',
+ 'Explain quantum entanglement implications',
+ 'What are Gรถdel\'s incompleteness theorems?',
+ 'Explain the halting problem',
+ ],
+ expertLong: [
+ 'Can you provide a comprehensive analysis of the P versus NP problem in computational complexity theory, including its mathematical formulation, its implications for computer science and cryptography, known results about complexity classes, and why it remains one of the most important open problems in mathematics?',
+ 'Please give me a detailed explanation of quantum entanglement, including the EPR paradox, Bell\'s theorem, the implications for quantum computing and quantum cryptography, and how it challenges our classical understanding of locality and realism.',
+ 'Explain Gรถdel\'s incompleteness theorems in detail, including the historical context, the mathematical proof technique, what they mean for the foundations of mathematics, and their philosophical implications for the nature of truth and provability.',
+ ],
+};
+
+interface BenchmarkResult {
+ query: string;
+ category: string;
+ streaming: boolean;
+ modelUsed: 'drafter' | 'verifier';
+ accepted: boolean;
+ drafterQuality: number;
+ latencyMs: number;
+ response: string;
+ agentRole?: string;
+ preRouterDecision?: string;
+}
+
+async function runNonStreamingTest(
+ cascade: any,
+ query: string,
+ category: string
+): Promise {
+ const start = Date.now();
+ const result = await cascade.invoke(query);
+ const latencyMs = Date.now() - start;
+
+ const stats = cascade.getLastCascadeResult();
+
+ // Check for agent role in response metadata
+ const agentRole = result.response_metadata?.agent_role;
+
+ return {
+ query,
+ category,
+ streaming: false,
+ modelUsed: stats?.modelUsed || 'unknown',
+ accepted: stats?.accepted || false,
+ drafterQuality: stats?.drafterQuality || 0,
+ latencyMs,
+ response: result.content.substring(0, 100) + '...',
+ agentRole,
+ };
+}
+
+async function runStreamingTest(
+ cascade: any,
+ query: string,
+ category: string
+): Promise {
+ const start = Date.now();
+ let response = '';
+ let chunkCount = 0;
+
+ const stream = await cascade.stream(query);
+
+ for await (const chunk of stream) {
+ const content = typeof chunk.content === 'string' ? chunk.content : '';
+ response += content;
+ chunkCount++;
+ }
+
+ const latencyMs = Date.now() - start;
+ const stats = cascade.getLastCascadeResult();
+
+ return {
+ query,
+ category,
+ streaming: true,
+ modelUsed: stats?.modelUsed || 'unknown',
+ accepted: stats?.accepted || false,
+ drafterQuality: stats?.drafterQuality || 0,
+ latencyMs,
+ response: response.substring(0, 100) + '...',
+ };
+}
+
+async function main() {
+ console.log('๐ฌ CascadeFlow Validation Benchmark\n');
+ console.log('Configuration:');
+ console.log(' Drafter: gpt-5-mini (OpenAI)');
+ console.log(' Verifier: claude-sonnet-4-5 (Anthropic)');
+ console.log(' Quality Threshold: 0.7');
+ console.log(' PreRouter: Enabled\n');
+
+ // Initialize models
+ const drafter = new ChatOpenAI({
+ model: 'gpt-5-mini',
+ // gpt-5-mini only supports temperature=1 (default)
+ });
+
+ const verifier = new ChatAnthropic({
+ model: 'claude-sonnet-4-5',
+ temperature: 0.7,
+ });
+
+ // Create PreRouter
+ const preRouter = createPreRouter({
+ enableCascade: true,
+ cascadeComplexities: ['trivial', 'simple', 'moderate'],
+ verbose: false,
+ });
+
+ // Create cascade model with PreRouter
+ const cascade = withCascade({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ enablePreRouter: true,
+ preRouter,
+ });
+
+ const results: BenchmarkResult[] = [];
+
+ // Test each category
+ const categories = Object.entries(TEST_QUERIES);
+
+ for (const [categoryName, queries] of categories) {
+ console.log(`\n๐ Testing Category: ${categoryName}`);
+ console.log('โ'.repeat(60));
+
+ for (let i = 0; i < Math.min(queries.length, 2); i++) {
+ const query = queries[i];
+
+ console.log(`\n Query ${i + 1}/${Math.min(queries.length, 2)}: "${query.substring(0, 50)}..."`);
+
+ // Test non-streaming
+ console.log(' โ Testing non-streaming...');
+ const nonStreamingResult = await runNonStreamingTest(
+ cascade,
+ query,
+ `${categoryName}-non-streaming`
+ );
+ results.push(nonStreamingResult);
+ console.log(` Model: ${nonStreamingResult.modelUsed}, Quality: ${nonStreamingResult.drafterQuality.toFixed(2)}, Latency: ${nonStreamingResult.latencyMs}ms`);
+
+ // Test streaming
+ console.log(' โ Testing streaming...');
+ const streamingResult = await runStreamingTest(
+ cascade,
+ query,
+ `${categoryName}-streaming`
+ );
+ results.push(streamingResult);
+ console.log(` Model: ${streamingResult.modelUsed}, Quality: ${streamingResult.drafterQuality.toFixed(2)}, Latency: ${streamingResult.latencyMs}ms`);
+
+ // Small delay between queries
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ }
+
+ // Analysis
+ console.log('\n\n' + '='.repeat(60));
+ console.log('๐ ANALYSIS RESULTS');
+ console.log('='.repeat(60));
+
+ // 1. Cascading Effectiveness
+ const drafterAccepted = results.filter(r => r.accepted).length;
+ const totalQueries = results.length;
+ const cascadeRate = ((totalQueries - drafterAccepted) / totalQueries * 100).toFixed(1);
+
+ console.log('\n1๏ธโฃ Cascading Effectiveness:');
+ console.log(` โ Drafter Accepted: ${drafterAccepted}/${totalQueries} (${(drafterAccepted/totalQueries*100).toFixed(1)}%)`);
+ console.log(` โ Cascaded to Verifier: ${totalQueries - drafterAccepted}/${totalQueries} (${cascadeRate}%)`);
+
+ if (cascadeRate === '0.0') {
+ console.log(' โ ๏ธ WARNING: 100% drafter acceptance - cascading not working!');
+ } else if (parseFloat(cascadeRate) < 10) {
+ console.log(' โ ๏ธ Low cascade rate - quality threshold may be too low');
+ } else {
+ console.log(' โ Cascading working correctly!');
+ }
+
+ // 2. Streaming vs Non-Streaming
+ const streamingResults = results.filter(r => r.streaming);
+ const nonStreamingResults = results.filter(r => !r.streaming);
+
+ const streamingAccepted = streamingResults.filter(r => r.accepted).length;
+ const nonStreamingAccepted = nonStreamingResults.filter(r => r.accepted).length;
+
+ console.log('\n2๏ธโฃ Streaming vs Non-Streaming:');
+ console.log(` Streaming - Accepted: ${streamingAccepted}/${streamingResults.length} (${(streamingAccepted/streamingResults.length*100).toFixed(1)}%)`);
+ console.log(` Non-Streaming - Accepted: ${nonStreamingAccepted}/${nonStreamingResults.length} (${(nonStreamingAccepted/nonStreamingResults.length*100).toFixed(1)}%)`);
+
+ const streamingAvgLatency = streamingResults.reduce((sum, r) => sum + r.latencyMs, 0) / streamingResults.length;
+ const nonStreamingAvgLatency = nonStreamingResults.reduce((sum, r) => sum + r.latencyMs, 0) / nonStreamingResults.length;
+
+ console.log(` Streaming Avg Latency: ${streamingAvgLatency.toFixed(0)}ms`);
+ console.log(` Non-Streaming Avg Latency: ${nonStreamingAvgLatency.toFixed(0)}ms`);
+
+ // 3. Quality by Category
+ console.log('\n3๏ธโฃ Quality by Category:');
+ const categoryStats: Record = {};
+
+ for (const result of results) {
+ const baseCat = result.category.split('-')[0];
+ if (!categoryStats[baseCat]) {
+ categoryStats[baseCat] = { avgQuality: 0, cascadeRate: 0, count: 0 };
+ }
+ categoryStats[baseCat].avgQuality += result.drafterQuality;
+ categoryStats[baseCat].cascadeRate += result.accepted ? 0 : 1;
+ categoryStats[baseCat].count++;
+ }
+
+ for (const [cat, stats] of Object.entries(categoryStats)) {
+ const avgQuality = stats.avgQuality / stats.count;
+ const cascadeRate = (stats.cascadeRate / stats.count * 100).toFixed(1);
+ console.log(` ${cat.padEnd(20)} - Avg Quality: ${avgQuality.toFixed(2)}, Cascade Rate: ${cascadeRate}%`);
+ }
+
+ // 4. Agent Role Check
+ console.log('\n4๏ธโฃ Agent Role Metadata:');
+ const withAgentRole = results.filter(r => r.agentRole).length;
+ console.log(` Results with agent_role: ${withAgentRole}/${results.length}`);
+
+ if (withAgentRole > 0) {
+ console.log(' โ Agent role metadata is being sent correctly!');
+ const sampleRole = results.find(r => r.agentRole)?.agentRole;
+ console.log(` Sample agent_role: "${sampleRole}"`);
+ } else {
+ console.log(' โ ๏ธ No agent_role metadata found in responses');
+ }
+
+ // 5. Model Usage Distribution
+ console.log('\n5๏ธโฃ Model Usage Distribution:');
+ const drafterUsed = results.filter(r => r.modelUsed === 'drafter').length;
+ const verifierUsed = results.filter(r => r.modelUsed === 'verifier').length;
+
+ console.log(` Drafter: ${drafterUsed}/${totalQueries} (${(drafterUsed/totalQueries*100).toFixed(1)}%)`);
+ console.log(` Verifier: ${verifierUsed}/${totalQueries} (${(verifierUsed/totalQueries*100).toFixed(1)}%)`);
+
+ // Summary
+ console.log('\n' + '='.repeat(60));
+ console.log('โ
VALIDATION SUMMARY');
+ console.log('='.repeat(60));
+
+ const validations = [
+ { name: 'Cascading works (not 100% acceptance)', passed: parseFloat(cascadeRate) > 0 },
+ { name: 'Streaming implemented', passed: streamingResults.length > 0 },
+ { name: 'Quality scores reasonable (0-1)', passed: results.every(r => r.drafterQuality >= 0 && r.drafterQuality <= 1) },
+ { name: 'Both models used', passed: drafterUsed > 0 && verifierUsed > 0 },
+ { name: 'Agent role metadata present', passed: withAgentRole > 0 },
+ ];
+
+ for (const validation of validations) {
+ console.log(`${validation.passed ? 'โ' : 'โ'} ${validation.name}`);
+ }
+
+ const allPassed = validations.every(v => v.passed);
+ console.log(`\n${allPassed ? '๐ All validations passed!' : 'โ ๏ธ Some validations failed'}`);
+
+ // 6. PreRouter Statistics
+ console.log('\n6๏ธโฃ PreRouter Statistics:');
+ preRouter.printStats();
+
+ // Save detailed results
+ const fs = await import('fs');
+ fs.writeFileSync(
+ 'validation-results.json',
+ JSON.stringify(results, null, 2)
+ );
+ console.log('\n๐พ Detailed results saved to validation-results.json');
+}
+
+main().catch(console.error);
diff --git a/packages/langchain-cascadeflow/package.json b/packages/langchain-cascadeflow/package.json
new file mode 100644
index 00000000..278a8780
--- /dev/null
+++ b/packages/langchain-cascadeflow/package.json
@@ -0,0 +1,80 @@
+{
+ "name": "@cascadeflow/langchain",
+ "version": "0.1.0",
+ "description": "LangChain integration for cascadeflow - Add intelligent cost optimization to your LangChain models",
+ "author": {
+ "name": "Lemony Inc.",
+ "email": "hello@lemony.ai"
+ },
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/lemony-ai/cascadeflow.git",
+ "directory": "packages/langchain-cascadeflow"
+ },
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.js"
+ }
+ },
+ "files": [
+ "dist",
+ "examples",
+ "README.md"
+ ],
+ "scripts": {
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "lint": "eslint src --ext .ts",
+ "typecheck": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "keywords": [
+ "langchain",
+ "cascadeflow",
+ "ai",
+ "llm",
+ "cost-optimization",
+ "model-routing",
+ "cascade",
+ "typescript"
+ ],
+ "dependencies": {
+ "@cascadeflow/core": "workspace:^",
+ "@cascadeflow/ml": "workspace:^",
+ "@langchain/anthropic": "^1.0.1",
+ "@langchain/google-genai": "^1.0.1"
+ },
+ "peerDependencies": {
+ "@langchain/core": "^0.3.0",
+ "langchain": "^0.3.0"
+ },
+ "peerDependenciesMeta": {
+ "langchain": {
+ "optional": true
+ }
+ },
+ "devDependencies": {
+ "@langchain/core": "^0.3.24",
+ "@langchain/openai": "^0.3.17",
+ "@types/node": "^20.10.0",
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
+ "@typescript-eslint/parser": "^6.15.0",
+ "eslint": "^8.55.0",
+ "langchain": "^0.3.13",
+ "openai": "^4.73.1",
+ "tsup": "^8.0.1",
+ "typescript": "^5.3.3",
+ "vitest": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+}
diff --git a/packages/langchain-cascadeflow/src/complexity.ts b/packages/langchain-cascadeflow/src/complexity.ts
new file mode 100644
index 00000000..d7550d6e
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/complexity.ts
@@ -0,0 +1,673 @@
+/**
+ * Query Complexity Detection
+ *
+ * Enhanced complexity detector with technical term recognition.
+ * Ported from @cascadeflow/core
+ *
+ * Features:
+ * - 500+ technical terms across multiple scientific domains
+ * - Mathematical notation detection (Unicode + LaTeX)
+ * - Domain-specific vocabulary scoring
+ * - Query structure analysis
+ *
+ * Based on research:
+ * - NER (Named Entity Recognition) for scientific terms
+ * - Unicode mathematical symbol detection
+ * - Domain-specific vocabulary scoring
+ */
+
+/**
+ * Query complexity levels
+ */
+export type QueryComplexity = 'trivial' | 'simple' | 'moderate' | 'hard' | 'expert';
+
+/**
+ * Complexity detection result
+ */
+export interface ComplexityResult {
+ /** Detected complexity level */
+ complexity: QueryComplexity;
+
+ /** Confidence score (0-1) */
+ confidence: number;
+
+ /** Optional metadata */
+ metadata?: {
+ /** Detected technical terms */
+ technicalTerms?: string[];
+
+ /** Detected domains */
+ domains?: Set;
+
+ /** Mathematical notation found */
+ mathNotation?: string[];
+
+ /** Domain score */
+ domainScore?: number;
+ };
+}
+
+/**
+ * Complexity detector with technical term recognition
+ */
+export class ComplexityDetector {
+ // =====================================================================
+ // TECHNICAL TERM DATABASES
+ // =====================================================================
+
+ private static readonly PHYSICS_TERMS = new Set([
+ // Quantum Mechanics
+ 'quantum entanglement',
+ 'quantum superposition',
+ 'quantum decoherence',
+ 'wave function collapse',
+ 'schrรถdinger equation',
+ 'schrodinger equation',
+ 'heisenberg uncertainty',
+ 'uncertainty principle',
+ 'pauli exclusion',
+ 'fermi-dirac',
+ 'bose-einstein',
+ 'bell theorem',
+ 'bell inequality',
+ 'double slit experiment',
+ 'quantum tunneling',
+ 'zero-point energy',
+ 'planck constant',
+ 'dirac equation',
+ 'klein-gordon',
+ // Relativity
+ 'special relativity',
+ 'general relativity',
+ 'spacetime curvature',
+ 'schwarzschild metric',
+ 'lorentz transformation',
+ 'time dilation',
+ 'length contraction',
+ 'event horizon',
+ 'gravitational waves',
+ 'einstein field equations',
+ 'geodesic',
+ 'minkowski space',
+ // Particle Physics
+ 'standard model',
+ 'higgs boson',
+ 'higgs mechanism',
+ 'gauge theory',
+ 'quantum chromodynamics',
+ 'qcd',
+ 'quantum electrodynamics',
+ 'qed',
+ 'weak interaction',
+ 'strong force',
+ 'electroweak theory',
+ 'feynman diagrams',
+ 'renormalization',
+ 'symmetry breaking',
+ // Fluid Dynamics (Critical!)
+ 'navier-stokes equations',
+ 'navier stokes',
+ 'reynolds number',
+ 'turbulent flow',
+ 'laminar flow',
+ 'boundary layer',
+ 'bernoulli equation',
+ 'euler equations',
+ 'viscosity',
+ 'incompressible flow',
+ 'mach number',
+ 'continuity equation',
+ 'vorticity',
+ 'streamline',
+ 'stokes flow',
+ // Thermodynamics
+ 'carnot cycle',
+ 'entropy',
+ 'enthalpy',
+ 'gibbs free energy',
+ 'boltzmann distribution',
+ 'partition function',
+ 'phase transition',
+ 'critical point',
+ 'thermodynamic equilibrium',
+ ]);
+
+ private static readonly MATHEMATICS_TERMS = new Set([
+ // Logic & Set Theory (Critical!)
+ 'gรถdel incompleteness',
+ 'goedel incompleteness',
+ 'gรถdel theorem',
+ 'incompleteness theorem',
+ 'church-turing thesis',
+ 'halting problem',
+ 'continuum hypothesis',
+ 'axiom of choice',
+ 'zermelo-fraenkel',
+ 'peano axioms',
+ 'cantor set',
+ 'russell paradox',
+ // Number Theory
+ 'riemann hypothesis',
+ 'riemann zeta function',
+ 'prime number theorem',
+ 'fermat last theorem',
+ 'goldbach conjecture',
+ 'twin prime',
+ 'diophantine equation',
+ 'modular arithmetic',
+ 'elliptic curve',
+ // Topology
+ 'hausdorff space',
+ 'topological space',
+ 'homeomorphism',
+ 'homotopy',
+ 'fundamental group',
+ 'manifold',
+ 'compactness',
+ 'connectedness',
+ 'metric space',
+ 'banach space',
+ 'hilbert space',
+ // Analysis
+ 'cauchy sequence',
+ 'lebesgue integral',
+ 'fourier transform',
+ 'laplace transform',
+ 'taylor series',
+ 'laurent series',
+ 'contour integration',
+ 'residue theorem',
+ 'analytic continuation',
+ 'dirichlet problem',
+ 'green function',
+ 'sturm-liouville',
+ // Algebra
+ 'galois theory',
+ 'group theory',
+ 'ring theory',
+ 'field theory',
+ 'homomorphism',
+ 'isomorphism',
+ 'kernel',
+ 'quotient group',
+ 'sylow theorem',
+ 'representation theory',
+ 'lie algebra',
+ 'lie group',
+ ]);
+
+ private static readonly CS_TERMS = new Set([
+ // Complexity Theory
+ 'np-complete',
+ 'np-hard',
+ 'polynomial time',
+ 'turing machine',
+ 'computational complexity',
+ 'big o notation',
+ 'time complexity',
+ 'space complexity',
+ 'decidability',
+ 'reducibility',
+ // Algorithms
+ 'dynamic programming',
+ 'greedy algorithm',
+ 'divide and conquer',
+ 'backtracking',
+ 'branch and bound',
+ 'amortized analysis',
+ 'dijkstra algorithm',
+ 'bellman-ford',
+ 'floyd-warshall',
+ 'kruskal algorithm',
+ 'prim algorithm',
+ 'topological sort',
+ // AI/ML
+ 'neural network',
+ 'deep learning',
+ 'convolutional neural network',
+ 'recurrent neural network',
+ 'transformer',
+ 'attention mechanism',
+ 'gradient descent',
+ 'backpropagation',
+ 'overfitting',
+ 'regularization',
+ 'cross-validation',
+ 'reinforcement learning',
+ 'q-learning',
+ // Quantum Computing (Added)
+ 'quantum computing',
+ 'quantum algorithm',
+ 'quantum supremacy',
+ 'qubit',
+ 'quantum gate',
+ 'quantum circuit',
+ ]);
+
+ private static readonly ENGINEERING_TERMS = new Set([
+ 'finite element analysis',
+ 'fea',
+ 'computational fluid dynamics',
+ 'cfd',
+ 'control theory',
+ 'pid controller',
+ 'feedback loop',
+ 'transfer function',
+ 'laplace domain',
+ 'frequency response',
+ 'bode plot',
+ 'nyquist plot',
+ 'signal processing',
+ 'fourier analysis',
+ 'wavelet transform',
+ 'digital signal processing',
+ 'dsp',
+ 'sampling theorem',
+ ]);
+
+ // =====================================================================
+ // KEYWORD PATTERNS
+ // =====================================================================
+
+ private static readonly TRIVIAL_PATTERNS = [
+ /what\s+is\s+\d+\s*[+*/-]\s*\d+/i,
+ /what's\s+\d+\s*[+*/-]\s*\d+/i,
+ /whats\s+\d+\s*[+*/-]\s*\d+/i,
+ /(calculate|compute|solve)\s+\d+\s*[+*/-]\s*\d+/i,
+ /(capital|population|currency|language)\s+of\s+\w+/i,
+ /^(hi|hello|hey|thanks|thank\s+you)[.!?]*$/i,
+ ];
+
+ private static readonly TRIVIAL_CONCEPTS = new Set([
+ 'color', 'colour', 'red', 'blue', 'green', 'yellow', 'black', 'white',
+ 'sky', 'sun', 'moon', 'water', 'cat', 'dog', 'bird', 'fish',
+ ]);
+
+ private static readonly SIMPLE_KEYWORDS = [
+ 'what', 'who', 'when', 'where', 'which',
+ 'define', 'definition', 'meaning', 'means',
+ 'explain', 'describe', 'tell me',
+ 'is', 'are', 'does', 'do',
+ 'simple', 'basic', 'introduction', 'overview', 'summary', 'briefly',
+ 'example', 'examples', 'difference', 'similar', 'list', 'name',
+ 'translate', 'convert', 'change',
+ ];
+
+ private static readonly MODERATE_KEYWORDS = [
+ 'compare', 'contrast', 'versus', 'vs', 'vs.',
+ 'difference between', 'distinguish',
+ 'how does', 'how do', 'why does', 'why do',
+ 'advantages', 'disadvantages', 'benefits', 'drawbacks',
+ 'pros and cons', 'pros', 'cons',
+ 'summarize', 'outline', 'describe in detail',
+ 'relationship', 'connection', 'correlation',
+ 'cause', 'effect', 'impact',
+ 'process', 'steps', 'procedure',
+ 'write', 'code', 'function', 'program', 'script',
+ 'reverse', 'sort', 'filter', 'map',
+ ];
+
+ private static readonly HARD_KEYWORDS = [
+ 'analyze', 'analysis', 'examine', 'investigate',
+ 'evaluate', 'assessment', 'assess', 'appraise',
+ 'critique', 'critical', 'critically',
+ 'implications', 'consequences', 'ramifications',
+ 'comprehensive', 'thorough', 'extensive', 'in-depth',
+ 'justify', 'argue', 'argument',
+ 'theoretical', 'theory', 'hypothesis',
+ 'methodology', 'approach', 'framework',
+ 'synthesize', 'integrate', 'consolidate',
+ ];
+
+ private static readonly EXPERT_KEYWORDS = [
+ 'implement', 'implementation', 'build', 'create', 'develop',
+ 'production', 'production-ready', 'enterprise',
+ 'architecture', 'design pattern', 'system design',
+ 'scalable', 'scalability', 'scale',
+ 'distributed', 'microservices', 'distributed tracing',
+ 'optimize', 'optimization', 'performance',
+ 'refactor', 'refactoring',
+ 'best practice', 'best practices',
+ 'algorithm', 'algorithmic',
+ 'theorem', 'theorems',
+ ];
+
+ private static readonly CODE_PATTERNS = [
+ /\bdef\s+\w+/,
+ /\bclass\s+\w+/,
+ /\bimport\s+\w+/,
+ /\bfunction\s+\w+/,
+ /\bconst\s+\w+\s*=/,
+ /=>/,
+ /\{[\s\S]*\}/,
+ /```/,
+ ];
+
+ // Combine all technical terms
+ private allTechnicalTerms: Set;
+
+ constructor() {
+ this.allTechnicalTerms = new Set([
+ ...ComplexityDetector.PHYSICS_TERMS,
+ ...ComplexityDetector.MATHEMATICS_TERMS,
+ ...ComplexityDetector.CS_TERMS,
+ ...ComplexityDetector.ENGINEERING_TERMS,
+ ]);
+ }
+
+ /**
+ * Detect query complexity
+ *
+ * @param query - Query text to analyze
+ * @param returnMetadata - Whether to return detailed metadata
+ * @returns Complexity result
+ */
+ detect(query: string, returnMetadata: boolean = false): ComplexityResult {
+ const queryLower = query.toLowerCase().trim();
+
+ const metadata = {
+ technicalTerms: [] as string[],
+ domains: new Set(),
+ mathNotation: [] as string[],
+ domainScore: 0,
+ };
+
+ // 1. Check trivial patterns first
+ for (const pattern of ComplexityDetector.TRIVIAL_PATTERNS) {
+ if (pattern.test(queryLower)) {
+ return {
+ complexity: 'trivial',
+ confidence: 0.95,
+ ...(returnMetadata && { metadata }),
+ };
+ }
+ }
+
+ // 2. Check for trivial concepts
+ if (this.hasTrivialConcepts(queryLower)) {
+ return {
+ complexity: 'trivial',
+ confidence: 0.85,
+ ...(returnMetadata && { metadata }),
+ };
+ }
+
+ // 3. Detect technical terms
+ const { terms: techTerms, domainScores } = this.detectTechnicalTerms(queryLower);
+ metadata.technicalTerms = techTerms;
+ metadata.domains = new Set(
+ Object.entries(domainScores)
+ .filter(([_, score]) => score > 0)
+ .map(([domain, _]) => domain)
+ );
+ metadata.domainScore = Math.max(...Object.values(domainScores), 0);
+
+ // 4. Calculate technical complexity boost
+ const techBoost = this.calculateTechnicalBoost(
+ techTerms.length,
+ 0, // mathNotation length (simplified for now)
+ domainScores
+ );
+
+ // 5. Detect code patterns
+ const hasCode = ComplexityDetector.CODE_PATTERNS.some(p => p.test(query));
+
+ // 6. Length and structure analysis
+ const words = query.split(/\s+/);
+ const wordCount = words.length;
+
+ const hasMultipleQuestions = (query.match(/\?/g) || []).length > 1;
+ const hasConditionals = ['if', 'when', 'unless', 'provided', 'assuming', 'given that']
+ .some(w => queryLower.includes(w));
+ const hasRequirements = ['must', 'should', 'need to', 'required', 'ensure', 'guarantee']
+ .some(w => queryLower.includes(w));
+ const hasMultipleParts = [';', '\n', '1.', '2.'].some(sep => query.includes(sep));
+
+ const structureScore = [
+ hasMultipleQuestions,
+ hasConditionals && hasRequirements,
+ hasMultipleParts,
+ ].filter(Boolean).length;
+
+ // 7. Count keyword matches
+ const simpleMatches = ComplexityDetector.SIMPLE_KEYWORDS
+ .filter(kw => queryLower.includes(kw)).length;
+ const moderateMatches = ComplexityDetector.MODERATE_KEYWORDS
+ .filter(kw => queryLower.includes(kw)).length;
+ const hardMatches = ComplexityDetector.HARD_KEYWORDS
+ .filter(kw => queryLower.includes(kw)).length;
+ const expertMatches = ComplexityDetector.EXPERT_KEYWORDS
+ .filter(kw => queryLower.includes(kw)).length;
+
+ // 8. Determine base complexity
+ let finalComplexity: QueryComplexity;
+ let finalConfidence: number;
+
+ // CRITICAL: Technical terms STRONGLY influence complexity
+ if (techBoost >= 2.0) {
+ // Multiple advanced terms or strong domain specialization
+ finalComplexity = 'expert';
+ finalConfidence = 0.90;
+ } else if (techBoost >= 1.0) {
+ // Some advanced terms
+ finalComplexity = 'hard';
+ finalConfidence = 0.85;
+ } else if (techBoost >= 0.5) {
+ // Basic technical terms
+ finalComplexity = 'moderate';
+ finalConfidence = 0.80;
+ } else if (expertMatches >= 2) {
+ finalComplexity = 'expert';
+ finalConfidence = 0.85;
+ } else if (expertMatches >= 1) {
+ if (wordCount >= 8) {
+ finalComplexity = 'expert';
+ finalConfidence = 0.80;
+ } else {
+ finalComplexity = 'hard';
+ finalConfidence = 0.75;
+ }
+ } else if (hardMatches >= 2) {
+ finalComplexity = 'hard';
+ finalConfidence = 0.8;
+ } else if (hardMatches >= 1 && wordCount > 6) {
+ finalComplexity = 'hard';
+ finalConfidence = 0.7;
+ } else if (moderateMatches >= 2) {
+ finalComplexity = 'moderate';
+ finalConfidence = 0.8;
+ } else if (moderateMatches >= 1 && wordCount > 6) {
+ finalComplexity = 'moderate';
+ finalConfidence = 0.7;
+ } else if (wordCount <= 6 && simpleMatches >= 1) {
+ finalComplexity = 'simple';
+ finalConfidence = 0.75;
+ } else {
+ // Default by word count
+ if (wordCount <= 8) {
+ finalComplexity = 'simple';
+ finalConfidence = 0.6;
+ } else if (wordCount <= 20) {
+ finalComplexity = 'moderate';
+ finalConfidence = 0.6;
+ } else {
+ finalComplexity = 'hard';
+ finalConfidence = 0.6;
+ }
+ }
+
+ // 9. Apply technical boost to complexity
+ if (techBoost >= 1.5) {
+ if (finalComplexity === 'simple') {
+ finalComplexity = 'hard';
+ } else if (finalComplexity === 'moderate') {
+ finalComplexity = 'expert';
+ } else if (finalComplexity === 'hard') {
+ finalComplexity = 'expert';
+ }
+ finalConfidence = Math.min(0.95, finalConfidence + 0.15);
+ }
+
+ // 10. Apply code boost (more nuanced)
+ if (hasCode) {
+ // Only boost if query is complex enough (> 12 words) OR has expert keywords
+ // This prevents simple coding tasks like "reverse a string" from being over-classified
+ const isComplexCodeQuery = wordCount > 12 || expertMatches >= 1;
+
+ if (isComplexCodeQuery) {
+ if (finalComplexity === 'simple') {
+ finalComplexity = 'moderate';
+ } else if (finalComplexity === 'moderate') {
+ finalComplexity = 'hard';
+ }
+ finalConfidence = Math.min(0.95, finalConfidence + 0.1);
+ } else {
+ // Simple code query - small confidence boost only
+ finalConfidence = Math.min(0.95, finalConfidence + 0.05);
+ }
+ }
+
+ // 11. Apply structure boost
+ if (structureScore >= 2) {
+ if (finalComplexity === 'simple') {
+ finalComplexity = 'moderate';
+ } else if (finalComplexity === 'moderate') {
+ finalComplexity = 'hard';
+ }
+ finalConfidence = Math.min(0.95, finalConfidence + 0.05);
+ }
+
+ // 12. Sanity checks
+ if (wordCount < 10 && finalComplexity === 'expert' && techBoost < 2.0) {
+ finalComplexity = 'hard';
+ }
+
+ if (wordCount > 50 && (finalComplexity === 'simple' || finalComplexity === 'moderate')) {
+ finalComplexity = 'hard';
+ }
+
+ return {
+ complexity: finalComplexity,
+ confidence: finalConfidence,
+ ...(returnMetadata && { metadata }),
+ };
+ }
+
+ /**
+ * Detect technical terms in query
+ */
+ private detectTechnicalTerms(queryLower: string): {
+ terms: string[];
+ domainScores: Record;
+ } {
+ const foundTerms: string[] = [];
+ const domainScores: Record = {
+ physics: 0,
+ mathematics: 0,
+ computer_science: 0,
+ engineering: 0,
+ };
+
+ // Check multi-word terms first (more specific)
+ for (const term of this.allTechnicalTerms) {
+ if (term.includes(' ') || term.includes('-')) {
+ const pattern = new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i');
+ if (pattern.test(queryLower)) {
+ foundTerms.push(term);
+
+ // Assign to domain
+ if (ComplexityDetector.PHYSICS_TERMS.has(term)) {
+ domainScores.physics += 1.0;
+ }
+ if (ComplexityDetector.MATHEMATICS_TERMS.has(term)) {
+ domainScores.mathematics += 1.0;
+ }
+ if (ComplexityDetector.CS_TERMS.has(term)) {
+ domainScores.computer_science += 1.0;
+ }
+ if (ComplexityDetector.ENGINEERING_TERMS.has(term)) {
+ domainScores.engineering += 1.0;
+ }
+ }
+ }
+ }
+
+ // Check single-word terms
+ const wordsInQuery = new Set(queryLower.split(/\s+/));
+ for (const term of this.allTechnicalTerms) {
+ if (!term.includes(' ') && !term.includes('-')) {
+ if (wordsInQuery.has(term)) {
+ foundTerms.push(term);
+
+ if (ComplexityDetector.PHYSICS_TERMS.has(term)) {
+ domainScores.physics += 0.5;
+ }
+ if (ComplexityDetector.MATHEMATICS_TERMS.has(term)) {
+ domainScores.mathematics += 0.5;
+ }
+ if (ComplexityDetector.CS_TERMS.has(term)) {
+ domainScores.computer_science += 0.5;
+ }
+ if (ComplexityDetector.ENGINEERING_TERMS.has(term)) {
+ domainScores.engineering += 0.5;
+ }
+ }
+ }
+ }
+
+ return { terms: foundTerms, domainScores };
+ }
+
+ /**
+ * Calculate complexity boost from technical content
+ */
+ private calculateTechnicalBoost(
+ numTechTerms: number,
+ numMathNotation: number,
+ domainScores: Record
+ ): number {
+ let boost = 0;
+
+ // Technical terms boost (increased from 0.5 to 0.7 per term)
+ boost += numTechTerms * 0.7;
+
+ // Math notation boost
+ boost += numMathNotation * 0.3;
+
+ // Domain specialization boost (increased weights)
+ const maxDomainScore = Math.max(...Object.values(domainScores), 0);
+ if (maxDomainScore >= 2) {
+ boost += 2.0; // Strong specialization (was 1.5)
+ } else if (maxDomainScore >= 1) {
+ boost += 1.0; // Moderate specialization (unchanged)
+ } else if (maxDomainScore >= 0.5) {
+ boost += 0.5; // Some specialization (was checking >= 1)
+ }
+
+ return boost;
+ }
+
+ /**
+ * Check for trivial concepts
+ */
+ private hasTrivialConcepts(queryLower: string): boolean {
+ let trivialCount = 0;
+
+ for (const concept of ComplexityDetector.TRIVIAL_CONCEPTS) {
+ const pattern = new RegExp(`\\b${concept}\\b`, 'i');
+ if (pattern.test(queryLower)) {
+ trivialCount++;
+ }
+ }
+
+ const wordCount = queryLower.split(/\s+/).length;
+
+ if (trivialCount >= 2) {
+ return true;
+ } else if (trivialCount >= 1 && wordCount <= 8) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/packages/langchain-cascadeflow/src/helpers.test.ts b/packages/langchain-cascadeflow/src/helpers.test.ts
new file mode 100644
index 00000000..9ebab674
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/helpers.test.ts
@@ -0,0 +1,225 @@
+import { describe, it, expect } from 'vitest';
+import { BaseChatModel } from '@langchain/core/language_models/chat_models';
+import { AIMessage, BaseMessage } from '@langchain/core/messages';
+import { ChatResult } from '@langchain/core/outputs';
+import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
+import { analyzeCascadePair, suggestCascadePairs } from './helpers.js';
+
+// Mock chat model for testing
+class MockChatModel extends BaseChatModel {
+ modelName: string;
+
+ constructor(modelName: string) {
+ super({});
+ this.modelName = modelName;
+ }
+
+ _llmType(): string {
+ return 'mock';
+ }
+
+ async _generate(
+ messages: BaseMessage[],
+ options: this['ParsedCallOptions'],
+ runManager?: CallbackManagerForLLMRun
+ ): Promise {
+ return {
+ generations: [
+ {
+ text: 'test',
+ message: new AIMessage('test'),
+ },
+ ],
+ llmOutput: {},
+ };
+ }
+
+ get model() {
+ return this.modelName;
+ }
+}
+
+describe('analyzeCascadePair', () => {
+ it('should analyze valid OpenAI cascade pair', () => {
+ const drafter = new MockChatModel('gpt-4o-mini');
+ const verifier = new MockChatModel('gpt-4o');
+
+ const analysis = analyzeCascadePair(drafter, verifier);
+
+ expect(analysis.drafterModel).toBe('gpt-4o-mini');
+ expect(analysis.verifierModel).toBe('gpt-4o');
+ expect(analysis.valid).toBe(true);
+ expect(analysis.warnings).toHaveLength(0);
+ expect(analysis.estimatedSavings).toBeGreaterThan(50);
+ expect(analysis.drafterCost.input).toBe(0.150);
+ expect(analysis.drafterCost.output).toBe(0.600);
+ expect(analysis.verifierCost.input).toBe(2.50);
+ expect(analysis.verifierCost.output).toBe(10.00);
+ });
+
+ it('should analyze valid Anthropic cascade pair', () => {
+ const drafter = new MockChatModel('claude-3-5-haiku-20241022');
+ const verifier = new MockChatModel('claude-3-5-sonnet-20241022');
+
+ const analysis = analyzeCascadePair(drafter, verifier);
+
+ expect(analysis.drafterModel).toBe('claude-3-5-haiku-20241022');
+ expect(analysis.verifierModel).toBe('claude-3-5-sonnet-20241022');
+ expect(analysis.valid).toBe(true);
+ expect(analysis.warnings).toHaveLength(0);
+ expect(analysis.estimatedSavings).toBeGreaterThan(40);
+ expect(analysis.drafterCost.input).toBe(0.80);
+ expect(analysis.drafterCost.output).toBe(4.00);
+ expect(analysis.verifierCost.input).toBe(3.00);
+ expect(analysis.verifierCost.output).toBe(15.00);
+ });
+
+ it('should detect when drafter is more expensive than verifier', () => {
+ const drafter = new MockChatModel('gpt-4o');
+ const verifier = new MockChatModel('gpt-4o-mini');
+
+ const analysis = analyzeCascadePair(drafter, verifier);
+
+ expect(analysis.valid).toBe(false);
+ expect(analysis.warnings.length).toBeGreaterThan(0);
+ expect(analysis.warnings[0]).toContain('more expensive');
+ });
+
+ it('should detect when models are the same', () => {
+ const drafter = new MockChatModel('gpt-4o');
+ const verifier = new MockChatModel('gpt-4o');
+
+ const analysis = analyzeCascadePair(drafter, verifier);
+
+ expect(analysis.valid).toBe(false);
+ expect(analysis.warnings.length).toBeGreaterThan(0);
+ expect(analysis.warnings.some(w => w.includes('same model'))).toBe(true);
+ });
+
+ it('should handle unknown model pricing', () => {
+ const drafter = new MockChatModel('unknown-model-1');
+ const verifier = new MockChatModel('unknown-model-2');
+
+ const analysis = analyzeCascadePair(drafter, verifier);
+
+ expect(analysis.drafterModel).toBe('unknown-model-1');
+ expect(analysis.verifierModel).toBe('unknown-model-2');
+ expect(analysis.drafterCost.input).toBe(0);
+ expect(analysis.drafterCost.output).toBe(0);
+ expect(analysis.verifierCost.input).toBe(0);
+ expect(analysis.verifierCost.output).toBe(0);
+ expect(analysis.estimatedSavings).toBe(0);
+ expect(analysis.warnings.length).toBeGreaterThan(0);
+ expect(analysis.warnings.some(w => w.includes('Unknown pricing'))).toBe(true);
+ });
+
+ it('should generate correct recommendations', () => {
+ const excellentDrafter = new MockChatModel('gpt-4o-mini');
+ const excellentVerifier = new MockChatModel('gpt-4o');
+ const excellentAnalysis = analyzeCascadePair(excellentDrafter, excellentVerifier);
+ expect(excellentAnalysis.recommendation).toContain('Excellent');
+
+ const invalidDrafter = new MockChatModel('gpt-4o');
+ const invalidVerifier = new MockChatModel('gpt-4o-mini');
+ const invalidAnalysis = analyzeCascadePair(invalidDrafter, invalidVerifier);
+ expect(invalidAnalysis.recommendation).toContain('needs attention');
+ });
+
+ it('should extract model name from different property names', () => {
+ // Test with model property
+ const model1 = new MockChatModel('test-model-1');
+ const model2 = new MockChatModel('test-model-2');
+
+ const analysis = analyzeCascadePair(model1, model2);
+ expect(analysis.drafterModel).toBe('test-model-1');
+ expect(analysis.verifierModel).toBe('test-model-2');
+ });
+});
+
+describe('suggestCascadePairs', () => {
+ it('should suggest optimal pairs from available models', () => {
+ const models = [
+ new MockChatModel('gpt-4o-mini'),
+ new MockChatModel('gpt-4o'),
+ new MockChatModel('gpt-3.5-turbo'),
+ ];
+
+ const suggestions = suggestCascadePairs(models);
+
+ expect(suggestions.length).toBeGreaterThan(0);
+
+ // Verify all suggestions are valid
+ suggestions.forEach(suggestion => {
+ expect(suggestion.analysis.valid).toBe(true);
+ expect(suggestion.analysis.estimatedSavings).toBeGreaterThan(20);
+ });
+
+ // Verify sorting (highest savings first)
+ for (let i = 0; i < suggestions.length - 1; i++) {
+ expect(suggestions[i].analysis.estimatedSavings)
+ .toBeGreaterThanOrEqual(suggestions[i + 1].analysis.estimatedSavings);
+ }
+ });
+
+ it('should filter out invalid configurations', () => {
+ const models = [
+ new MockChatModel('gpt-4o'), // expensive
+ new MockChatModel('gpt-4o'), // duplicate
+ new MockChatModel('gpt-4o-mini'), // cheap
+ ];
+
+ const suggestions = suggestCascadePairs(models);
+
+ // All suggestions should be valid
+ suggestions.forEach(suggestion => {
+ expect(suggestion.analysis.valid).toBe(true);
+ expect(suggestion.drafter).not.toBe(suggestion.verifier);
+ });
+ });
+
+ it('should return empty array when no viable pairs exist', () => {
+ const models = [
+ new MockChatModel('unknown-1'),
+ new MockChatModel('unknown-2'),
+ ];
+
+ const suggestions = suggestCascadePairs(models);
+
+ // Should be empty since unknown models have no pricing info
+ // and thus estimated savings is 0
+ expect(suggestions).toHaveLength(0);
+ });
+
+ it('should handle single model', () => {
+ const models = [new MockChatModel('gpt-4o-mini')];
+
+ const suggestions = suggestCascadePairs(models);
+
+ expect(suggestions).toHaveLength(0);
+ });
+
+ it('should handle empty model list', () => {
+ const models: BaseChatModel[] = [];
+
+ const suggestions = suggestCascadePairs(models);
+
+ expect(suggestions).toHaveLength(0);
+ });
+
+ it('should identify best pair from mixed providers', () => {
+ const models = [
+ new MockChatModel('gpt-4o-mini'),
+ new MockChatModel('gpt-4o'),
+ new MockChatModel('claude-3-5-haiku-20241022'),
+ new MockChatModel('claude-3-5-sonnet-20241022'),
+ ];
+
+ const suggestions = suggestCascadePairs(models);
+
+ expect(suggestions.length).toBeGreaterThan(0);
+
+ // Best suggestion should have highest savings
+ const bestSuggestion = suggestions[0];
+ expect(bestSuggestion.analysis.estimatedSavings).toBeGreaterThan(50);
+ });
+});
diff --git a/packages/langchain-cascadeflow/src/helpers.ts b/packages/langchain-cascadeflow/src/helpers.ts
new file mode 100644
index 00000000..751e856b
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/helpers.ts
@@ -0,0 +1,256 @@
+import { BaseChatModel } from '@langchain/core/language_models/chat_models';
+import { MODEL_PRICING_REFERENCE } from './models.js';
+
+/**
+ * Model pricing information (per 1M tokens)
+ */
+interface ModelPricing {
+ input: number;
+ output: number;
+}
+
+/**
+ * Result of analyzing a cascade pair
+ */
+export interface CascadeAnalysis {
+ drafterModel: string;
+ verifierModel: string;
+ drafterCost: ModelPricing;
+ verifierCost: ModelPricing;
+ valid: boolean;
+ warnings: string[];
+ estimatedSavings: number;
+ recommendation: string;
+}
+
+/**
+ * Get pricing information for a model
+ * Returns pricing per 1M tokens
+ */
+function getModelPricing(modelName: string): ModelPricing | null {
+ // Use MODEL_PRICING_REFERENCE for consistent pricing across the package
+ const normalizedName = modelName.toLowerCase();
+
+ // Try exact match first
+ let pricing = Object.entries(MODEL_PRICING_REFERENCE).find(([key]) =>
+ normalizedName === key.toLowerCase()
+ );
+
+ // If no exact match, try contains (prefer longer keys first to avoid partial matches)
+ if (!pricing) {
+ const sortedEntries = Object.entries(MODEL_PRICING_REFERENCE).sort((a, b) => b[0].length - a[0].length);
+ pricing = sortedEntries.find(([key]) =>
+ normalizedName.includes(key.toLowerCase())
+ );
+ }
+
+ return pricing ? { input: pricing[1].input, output: pricing[1].output } : null;
+}
+
+/**
+ * Extract model name from a LangChain model instance
+ */
+function extractModelName(model: BaseChatModel): string {
+ // Try different property names that LangChain models use
+ const modelAny = model as any;
+
+ if (modelAny.model) return modelAny.model;
+ if (modelAny.modelName) return modelAny.modelName;
+ if (modelAny.model_name) return modelAny.model_name;
+
+ // Fallback to _llmType if no model name found
+ return model._llmType();
+}
+
+/**
+ * Calculate estimated savings percentage
+ * Assumes typical 70% drafter acceptance rate
+ */
+function calculateEstimatedSavings(
+ drafterPricing: ModelPricing,
+ verifierPricing: ModelPricing,
+ acceptanceRate: number = 0.7
+): number {
+ // Average tokens for a typical query
+ const avgInputTokens = 500;
+ const avgOutputTokens = 300;
+
+ // Cost if always using verifier
+ const verifierOnlyCost =
+ (avgInputTokens / 1_000_000) * verifierPricing.input +
+ (avgOutputTokens / 1_000_000) * verifierPricing.output;
+
+ // Cost with cascade (drafter tries all, verifier only on failures)
+ const drafterCost =
+ (avgInputTokens / 1_000_000) * drafterPricing.input +
+ (avgOutputTokens / 1_000_000) * drafterPricing.output;
+
+ const cascadeCost =
+ drafterCost + // Always try drafter
+ (1 - acceptanceRate) * verifierOnlyCost; // Verifier only when drafter fails
+
+ // Calculate savings
+ if (verifierOnlyCost === 0) return 0;
+
+ const savings = ((verifierOnlyCost - cascadeCost) / verifierOnlyCost) * 100;
+ return Math.max(0, Math.min(100, savings));
+}
+
+/**
+ * Analyze a cascade configuration and provide insights
+ *
+ * @param drafter - The drafter (cheap, fast) model instance
+ * @param verifier - The verifier (expensive, accurate) model instance
+ * @returns Analysis with pricing, validation, and recommendations
+ *
+ * @example
+ * ```typescript
+ * const drafter = new ChatOpenAI({ model: 'gpt-4o-mini' });
+ * const verifier = new ChatOpenAI({ model: 'gpt-4o' });
+ *
+ * const analysis = analyzeCascadePair(drafter, verifier);
+ * console.log(analysis.estimatedSavings); // => 55-65%
+ * console.log(analysis.warnings); // => []
+ * ```
+ */
+export function analyzeCascadePair(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel
+): CascadeAnalysis {
+ const drafterModel = extractModelName(drafter);
+ const verifierModel = extractModelName(verifier);
+
+ const drafterPricing = getModelPricing(drafterModel);
+ const verifierPricing = getModelPricing(verifierModel);
+
+ const warnings: string[] = [];
+ let valid = true;
+
+ // Check if we have pricing info
+ if (!drafterPricing) {
+ valid = false;
+ warnings.push(`Unknown pricing for drafter model: ${drafterModel}`);
+ }
+ if (!verifierPricing) {
+ valid = false;
+ warnings.push(`Unknown pricing for verifier model: ${verifierModel}`);
+ }
+
+ // Validate configuration
+ if (drafterPricing && verifierPricing) {
+ // Check if drafter is more expensive than verifier (misconfiguration)
+ const drafterAvgCost = (drafterPricing.input + drafterPricing.output) / 2;
+ const verifierAvgCost = (verifierPricing.input + verifierPricing.output) / 2;
+
+ if (drafterAvgCost > verifierAvgCost) {
+ valid = false;
+ warnings.push(
+ `Drafter (${drafterModel}) is more expensive than verifier (${verifierModel}). ` +
+ `This defeats the purpose of cascading. Consider swapping them.`
+ );
+ }
+
+ // Check if models are the same
+ if (drafterModel === verifierModel) {
+ valid = false;
+ warnings.push(
+ `Drafter and verifier are the same model (${drafterModel}). ` +
+ `Cascading provides no benefit in this configuration.`
+ );
+ }
+
+ // Check if drafter is only slightly cheaper
+ const savingsRatio = (verifierAvgCost - drafterAvgCost) / verifierAvgCost;
+ if (savingsRatio < 0.3 && savingsRatio > 0) {
+ warnings.push(
+ `Drafter is only ${(savingsRatio * 100).toFixed(0)}% cheaper than verifier. ` +
+ `Consider using a cheaper drafter for better cost savings.`
+ );
+ }
+ }
+
+ // Calculate estimated savings
+ const estimatedSavings = drafterPricing && verifierPricing
+ ? calculateEstimatedSavings(drafterPricing, verifierPricing)
+ : 0;
+
+ // Generate recommendation
+ let recommendation = '';
+ if (!valid) {
+ recommendation = 'Configuration needs attention. See warnings above.';
+ } else if (estimatedSavings > 50) {
+ recommendation = 'Excellent cascade configuration! Expected savings > 50%.';
+ } else if (estimatedSavings > 30) {
+ recommendation = 'Good cascade configuration. Expected savings 30-50%.';
+ } else if (estimatedSavings > 0) {
+ recommendation = 'Marginal cascade configuration. Consider a cheaper drafter.';
+ } else {
+ recommendation = 'Unable to estimate savings (unknown model pricing).';
+ }
+
+ return {
+ drafterModel,
+ verifierModel,
+ drafterCost: drafterPricing || { input: 0, output: 0 },
+ verifierCost: verifierPricing || { input: 0, output: 0 },
+ valid,
+ warnings,
+ estimatedSavings,
+ recommendation,
+ };
+}
+
+/**
+ * Suggest optimal cascade pairs from a list of available models
+ *
+ * @param models - Array of LangChain model instances
+ * @returns Array of suggested cascade configurations
+ *
+ * @example
+ * ```typescript
+ * const models = [
+ * new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ * new ChatOpenAI({ model: 'gpt-4o' }),
+ * new ChatAnthropic({ model: 'claude-3-5-haiku' }),
+ * ];
+ *
+ * const suggestions = suggestCascadePairs(models);
+ * // => [{ drafter: models[0], verifier: models[1], estimatedSavings: 60% }]
+ * ```
+ */
+export function suggestCascadePairs(
+ models: BaseChatModel[]
+): Array<{
+ drafter: BaseChatModel;
+ verifier: BaseChatModel;
+ analysis: CascadeAnalysis;
+}> {
+ const suggestions: Array<{
+ drafter: BaseChatModel;
+ verifier: BaseChatModel;
+ analysis: CascadeAnalysis;
+ }> = [];
+
+ // Try all pairs
+ for (let i = 0; i < models.length; i++) {
+ for (let j = 0; j < models.length; j++) {
+ if (i === j) continue;
+
+ const analysis = analyzeCascadePair(models[i], models[j]);
+
+ // Only include valid pairs (filtering by savings happens in discoverCascadePairs)
+ if (analysis.valid) {
+ suggestions.push({
+ drafter: models[i],
+ verifier: models[j],
+ analysis,
+ });
+ }
+ }
+ }
+
+ // Sort by estimated savings (highest first)
+ suggestions.sort((a, b) => b.analysis.estimatedSavings - a.analysis.estimatedSavings);
+
+ return suggestions;
+}
diff --git a/packages/langchain-cascadeflow/src/index.ts b/packages/langchain-cascadeflow/src/index.ts
new file mode 100644
index 00000000..26eebf80
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/index.ts
@@ -0,0 +1,77 @@
+/**
+ * @module @cascadeflow/langchain
+ *
+ * LangChain integration for cascadeflow - Add intelligent cost optimization
+ * to your existing LangChain models without reconfiguration.
+ *
+ * @example
+ * ```typescript
+ * import { withCascade } from '@cascadeflow/langchain';
+ * import { ChatOpenAI } from '@langchain/openai';
+ *
+ * // Your existing models
+ * const drafter = new ChatOpenAI({ model: 'gpt-4o-mini' });
+ * const verifier = new ChatOpenAI({ model: 'gpt-4o' });
+ *
+ * // Wrap with cascade (2 lines!)
+ * const cascadeModel = withCascade({
+ * drafter,
+ * verifier,
+ * qualityThreshold: 0.7
+ * });
+ *
+ * // Use like any LangChain model - all features preserved!
+ * const result = await cascadeModel.invoke("What is TypeScript?");
+ * console.log(result);
+ *
+ * // Check cascade stats
+ * const stats = cascadeModel.getLastCascadeResult();
+ * console.log(`Saved: ${stats.savingsPercentage}%`);
+ * ```
+ */
+
+export { CascadeFlow } from './wrapper.js';
+export type { CascadeConfig, CascadeResult, CostMetadata } from './types.js';
+export * from './utils.js';
+export { analyzeCascadePair, suggestCascadePairs } from './helpers.js';
+export type { CascadeAnalysis } from './helpers.js';
+
+// Routers and complexity detection
+export { PreRouter, createPreRouter } from './routers/pre-router.js';
+export type { PreRouterConfig, PreRouterStats } from './routers/pre-router.js';
+export { Router, RoutingStrategy, RoutingDecisionHelper, RouterChain } from './routers/base.js';
+export type { RoutingDecision } from './routers/base.js';
+export { ComplexityDetector } from './complexity.js';
+export type { QueryComplexity, ComplexityResult } from './complexity.js';
+
+// Model discovery (works with YOUR models!)
+export {
+ MODEL_PRICING_REFERENCE,
+ discoverCascadePairs,
+ analyzeModel,
+ compareModels,
+ findBestCascadePair,
+ validateCascadePair,
+} from './models.js';
+
+import { CascadeFlow } from './wrapper.js';
+import type { CascadeConfig } from './types.js';
+
+/**
+ * Convenient helper to create a CascadeFlow model
+ *
+ * @param config - Cascade configuration with drafter/verifier models
+ * @returns A wrapped model that cascades from drafter to verifier
+ *
+ * @example
+ * ```typescript
+ * const model = withCascade({
+ * drafter: new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ * verifier: new ChatOpenAI({ model: 'gpt-4o' }),
+ * qualityThreshold: 0.7
+ * });
+ * ```
+ */
+export function withCascade(config: CascadeConfig): CascadeFlow {
+ return new CascadeFlow(config);
+}
diff --git a/packages/langchain-cascadeflow/src/models.ts b/packages/langchain-cascadeflow/src/models.ts
new file mode 100644
index 00000000..97f7222d
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/models.ts
@@ -0,0 +1,333 @@
+/**
+ * Model discovery and analysis for LangChain models
+ *
+ * This module helps users discover which of THEIR configured LangChain
+ * models work best for cascading, without requiring any specific API keys.
+ *
+ * Users bring their own models - we just help them find the best pairs!
+ */
+
+import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
+import { analyzeCascadePair, suggestCascadePairs, type CascadeAnalysis } from './helpers.js';
+
+/**
+ * Model pricing reference (per 1M tokens)
+ * This is read-only reference data to help users understand costs
+ */
+export const MODEL_PRICING_REFERENCE = {
+ // OpenAI Models
+ 'gpt-4o-mini': { input: 0.15, output: 0.60, tier: 'fast' },
+ 'gpt-4o': { input: 2.50, output: 10.00, tier: 'powerful' },
+ 'gpt-4-turbo': { input: 10.00, output: 30.00, tier: 'powerful' },
+ 'gpt-3.5-turbo': { input: 0.50, output: 1.50, tier: 'fast' },
+
+ // GPT-5 Models (estimated pricing - subject to change)
+ 'gpt-5': { input: 1.25, output: 10.00, tier: 'powerful' },
+ 'gpt-5-mini': { input: 0.25, output: 2.00, tier: 'fast' },
+ 'gpt-5-nano': { input: 0.05, output: 0.40, tier: 'fast' },
+ 'gpt-5.1': { input: 2.00, output: 15.00, tier: 'powerful' },
+
+ // Anthropic Models
+ 'claude-3-haiku-20240307': { input: 0.25, output: 1.25, tier: 'fast' },
+ 'claude-3-5-haiku-20241022': { input: 0.80, output: 4.00, tier: 'balanced' },
+ 'claude-3-5-sonnet-20241022': { input: 3.00, output: 15.00, tier: 'powerful' },
+ 'claude-3-sonnet-20240229': { input: 3.00, output: 15.00, tier: 'balanced' },
+ 'claude-3-opus-20240229': { input: 15.00, output: 75.00, tier: 'powerful' },
+
+ // Claude 4 Models (estimated pricing - subject to change)
+ 'claude-sonnet-4': { input: 3.00, output: 15.00, tier: 'powerful' },
+ 'claude-haiku-4.5': { input: 1.00, output: 5.00, tier: 'balanced' },
+
+ // Google Models
+ 'gemini-1.5-flash': { input: 0.075, output: 0.30, tier: 'fast' },
+ 'gemini-1.5-pro': { input: 1.25, output: 5.00, tier: 'powerful' },
+
+ // Gemini 2.5 Models (estimated pricing - subject to change)
+ 'gemini-2.5-flash': { input: 0.30, output: 2.50, tier: 'fast' },
+ 'gemini-2.5-pro': { input: 1.25, output: 10.00, tier: 'powerful' },
+} as const;
+
+/**
+ * Extract model name from a LangChain model instance
+ */
+function getModelName(model: BaseChatModel): string {
+ const modelAny = model as any;
+ return modelAny.model || modelAny.modelName || modelAny.model_name || model._llmType();
+}
+
+/**
+ * Discover and analyze cascade pairs from user's models
+ *
+ * This is the main helper - give it YOUR models and it will suggest
+ * the best cascade configurations.
+ *
+ * @param models - Array of YOUR configured LangChain models
+ * @param options - Analysis options
+ * @returns Ranked cascade pair suggestions
+ *
+ * @example
+ * ```typescript
+ * // YOUR models (already configured with YOUR API keys)
+ * const myModels = [
+ * new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ * new ChatOpenAI({ model: 'gpt-4o' }),
+ * new ChatAnthropic({ model: 'claude-3-haiku' }),
+ * ];
+ *
+ * // Find best cascade pairs
+ * const suggestions = discoverCascadePairs(myModels);
+ *
+ * // Use the best one
+ * const best = suggestions[0];
+ * const cascade = new CascadeWrapper({
+ * drafter: best.drafter,
+ * verifier: best.verifier,
+ * });
+ * ```
+ */
+export function discoverCascadePairs(
+ models: BaseChatModel[],
+ options: {
+ minSavings?: number;
+ requireSameProvider?: boolean;
+ } = {}
+): Array<{
+ drafter: BaseChatModel;
+ verifier: BaseChatModel;
+ analysis: CascadeAnalysis;
+ rank: number;
+}> {
+ const minSavings = options.minSavings ?? 20;
+
+ // Use the existing suggestCascadePairs helper
+ let suggestions = suggestCascadePairs(models);
+
+ // Filter by provider if requested
+ if (options.requireSameProvider) {
+ suggestions = suggestions.filter(s => {
+ const drafterProvider = getProvider(s.drafter);
+ const verifierProvider = getProvider(s.verifier);
+ return drafterProvider === verifierProvider;
+ });
+ }
+
+ // Filter by minimum savings
+ suggestions = suggestions.filter(s => s.analysis.estimatedSavings >= minSavings);
+
+ // Add ranking
+ return suggestions.map((s, i) => ({
+ ...s,
+ rank: i + 1,
+ }));
+}
+
+/**
+ * Get provider name from a model
+ */
+function getProvider(model: BaseChatModel): string {
+ const modelName = getModelName(model).toLowerCase();
+
+ if (modelName.includes('gpt') || modelName.includes('openai')) return 'openai';
+ if (modelName.includes('claude') || modelName.includes('anthropic')) return 'anthropic';
+ if (modelName.includes('gemini') || modelName.includes('google')) return 'google';
+ if (modelName.includes('ollama')) return 'ollama';
+
+ return 'unknown';
+}
+
+/**
+ * Analyze a user's model and provide insights
+ *
+ * @param model - YOUR configured LangChain model
+ * @returns Analysis with pricing and tier information
+ *
+ * @example
+ * ```typescript
+ * const myModel = new ChatOpenAI({ model: 'gpt-4o' });
+ * const info = analyzeModel(myModel);
+ * console.log(info.modelName); // 'gpt-4o'
+ * console.log(info.tier); // 'powerful'
+ * console.log(info.estimatedCost); // { input: 2.50, output: 10.00 }
+ * ```
+ */
+export function analyzeModel(model: BaseChatModel): {
+ modelName: string;
+ provider: string;
+ tier: 'fast' | 'balanced' | 'powerful' | 'unknown';
+ estimatedCost: { input: number; output: number } | null;
+ recommendation: string;
+} {
+ const modelName = getModelName(model);
+ const provider = getProvider(model);
+
+ // Look up pricing - try exact match first, then fallback to contains
+ let pricing = Object.entries(MODEL_PRICING_REFERENCE).find(([key]) =>
+ modelName.toLowerCase() === key.toLowerCase()
+ );
+
+ // If no exact match, try contains (but prefer longer keys first to avoid gpt-5 matching gpt-5-mini)
+ if (!pricing) {
+ const sortedEntries = Object.entries(MODEL_PRICING_REFERENCE).sort((a, b) => b[0].length - a[0].length);
+ pricing = sortedEntries.find(([key]) =>
+ modelName.toLowerCase().includes(key.toLowerCase())
+ );
+ }
+
+ const estimatedCost = pricing ? { input: pricing[1].input, output: pricing[1].output } : null;
+ const tier = pricing?.[1].tier || 'unknown';
+
+ // Generate recommendation
+ let recommendation = '';
+ if (tier === 'fast') {
+ recommendation = 'Good choice for drafter (cheap, fast model)';
+ } else if (tier === 'powerful') {
+ recommendation = 'Good choice for verifier (expensive, accurate model)';
+ } else if (tier === 'balanced') {
+ recommendation = 'Can work as either drafter or verifier';
+ } else {
+ recommendation = 'Unknown model - consider testing cascade performance';
+ }
+
+ return {
+ modelName,
+ provider,
+ tier,
+ estimatedCost,
+ recommendation,
+ };
+}
+
+/**
+ * Compare multiple models and rank them for cascade use
+ *
+ * @param models - YOUR configured models to compare
+ * @returns Ranked models with recommendations
+ *
+ * @example
+ * ```typescript
+ * const myModels = [
+ * new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ * new ChatOpenAI({ model: 'gpt-4o' }),
+ * new ChatAnthropic({ model: 'claude-3-5-sonnet' }),
+ * ];
+ *
+ * const comparison = compareModels(myModels);
+ * console.log(comparison.drafterCandidates); // Best for drafter
+ * console.log(comparison.verifierCandidates); // Best for verifier
+ * ```
+ */
+export function compareModels(models: BaseChatModel[]): {
+ drafterCandidates: Array<{ model: BaseChatModel; analysis: ReturnType }>;
+ verifierCandidates: Array<{ model: BaseChatModel; analysis: ReturnType }>;
+ all: Array<{ model: BaseChatModel; analysis: ReturnType }>;
+} {
+ const analyzed = models.map(model => ({
+ model,
+ analysis: analyzeModel(model),
+ }));
+
+ // Sort by cost (input + output average)
+ const sorted = [...analyzed].sort((a, b) => {
+ const aCost = a.analysis.estimatedCost
+ ? (a.analysis.estimatedCost.input + a.analysis.estimatedCost.output) / 2
+ : Infinity;
+ const bCost = b.analysis.estimatedCost
+ ? (b.analysis.estimatedCost.input + b.analysis.estimatedCost.output) / 2
+ : Infinity;
+ return aCost - bCost;
+ });
+
+ // Drafters = cheap models (first half)
+ const drafterCandidates = sorted.slice(0, Math.ceil(sorted.length / 2));
+
+ // Verifiers = expensive models (second half)
+ const verifierCandidates = sorted.slice(Math.ceil(sorted.length / 2));
+
+ return {
+ drafterCandidates,
+ verifierCandidates,
+ all: analyzed,
+ };
+}
+
+/**
+ * Quick helper to find the best cascade pair from user's models
+ *
+ * @param models - YOUR configured LangChain models
+ * @returns Best drafter and verifier, or null if no good pair found
+ *
+ * @example
+ * ```typescript
+ * const myModels = [
+ * new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ * new ChatOpenAI({ model: 'gpt-4o' }),
+ * ];
+ *
+ * const best = findBestCascadePair(myModels);
+ * if (best) {
+ * const cascade = new CascadeWrapper({
+ * drafter: best.drafter,
+ * verifier: best.verifier,
+ * });
+ * }
+ * ```
+ */
+export function findBestCascadePair(models: BaseChatModel[]): {
+ drafter: BaseChatModel;
+ verifier: BaseChatModel;
+ estimatedSavings: number;
+ analysis: CascadeAnalysis;
+} | null {
+ const suggestions = discoverCascadePairs(models);
+
+ if (suggestions.length === 0) return null;
+
+ const best = suggestions[0];
+ return {
+ drafter: best.drafter,
+ verifier: best.verifier,
+ estimatedSavings: best.analysis.estimatedSavings,
+ analysis: best.analysis,
+ };
+}
+
+/**
+ * Validate that a model pair makes sense for cascading
+ *
+ * @param drafter - YOUR configured drafter model
+ * @param verifier - YOUR configured verifier model
+ * @returns Validation result with warnings
+ *
+ * @example
+ * ```typescript
+ * const result = validateCascadePair(
+ * new ChatOpenAI({ model: 'gpt-4o-mini' }),
+ * new ChatOpenAI({ model: 'gpt-4o' })
+ * );
+ *
+ * if (!result.valid) {
+ * console.warn('Issues:', result.warnings);
+ * }
+ * ```
+ */
+export function validateCascadePair(
+ drafter: BaseChatModel,
+ verifier: BaseChatModel
+): {
+ valid: boolean;
+ warnings: string[];
+ estimatedSavings: number;
+ recommendation: string;
+} {
+ const analysis = analyzeCascadePair(drafter, verifier);
+
+ return {
+ valid: analysis.valid,
+ warnings: analysis.warnings,
+ estimatedSavings: analysis.estimatedSavings,
+ recommendation: analysis.recommendation,
+ };
+}
+
+// Re-export helpers for convenience
+export { analyzeCascadePair, suggestCascadePairs } from './helpers.js';
diff --git a/packages/langchain-cascadeflow/src/routers/base.ts b/packages/langchain-cascadeflow/src/routers/base.ts
new file mode 100644
index 00000000..338b5da8
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/routers/base.ts
@@ -0,0 +1,200 @@
+/**
+ * Base router interface for all routing strategies
+ *
+ * Routers decide HOW to execute a query before execution starts.
+ * This is "pre-routing" - decisions made BEFORE calling models.
+ *
+ * Port from @cascadeflow/core
+ */
+
+/**
+ * How to execute a query
+ *
+ * This tells the agent what execution path to take
+ */
+export enum RoutingStrategy {
+ /** Route to cheapest model */
+ DIRECT_CHEAP = 'direct_cheap',
+
+ /** Route to best model */
+ DIRECT_BEST = 'direct_best',
+
+ /** Use cascade system */
+ CASCADE = 'cascade',
+
+ /** Call multiple models in parallel (future) */
+ PARALLEL = 'parallel',
+}
+
+/**
+ * Decision made by router about query execution
+ *
+ * This is what routers return to the agent
+ */
+export interface RoutingDecision {
+ /** How to execute (DIRECT_BEST, CASCADE, etc) */
+ strategy: RoutingStrategy;
+
+ /** Human-readable explanation */
+ reason: string;
+
+ /** Confidence in this decision (0-1) */
+ confidence: number;
+
+ /** Additional routing metadata */
+ metadata: Record;
+
+ /** Specific model to use (optional) */
+ modelName?: string;
+
+ /** Budget constraint (optional) */
+ maxCost?: number;
+
+ /** Quality requirement (optional) */
+ minQuality?: number;
+}
+
+/**
+ * Helper functions for RoutingDecision
+ */
+export class RoutingDecisionHelper {
+ /**
+ * Check if decision is direct routing
+ */
+ static isDirect(decision: RoutingDecision): boolean {
+ return (
+ decision.strategy === RoutingStrategy.DIRECT_BEST ||
+ decision.strategy === RoutingStrategy.DIRECT_CHEAP
+ );
+ }
+
+ /**
+ * Check if decision is cascade routing
+ */
+ static isCascade(decision: RoutingDecision): boolean {
+ return decision.strategy === RoutingStrategy.CASCADE;
+ }
+
+ /**
+ * Validate routing decision
+ */
+ static validate(decision: RoutingDecision): void {
+ if (decision.confidence < 0 || decision.confidence > 1) {
+ throw new Error(`Confidence must be 0-1, got ${decision.confidence}`);
+ }
+ }
+
+ /**
+ * Create a routing decision
+ */
+ static create(
+ strategy: RoutingStrategy,
+ reason: string,
+ confidence: number,
+ metadata: Record = {}
+ ): RoutingDecision {
+ const decision: RoutingDecision = {
+ strategy,
+ reason,
+ confidence,
+ metadata,
+ };
+
+ RoutingDecisionHelper.validate(decision);
+ return decision;
+ }
+}
+
+/**
+ * Abstract base class for all routers
+ *
+ * Routers decide HOW to execute a query before execution starts.
+ *
+ * Future routers:
+ * - PreRouter: Based on complexity (current implementation)
+ * - SemanticRouter: Based on semantic similarity to examples
+ * - DomainRouter: Based on detected domain (code, math, etc)
+ * - HybridRouter: Combine multiple routing strategies
+ * - LearnedRouter: ML-based routing decisions
+ */
+export abstract class Router {
+ /**
+ * Decide how to handle this query
+ *
+ * @param query - User query text
+ * @param context - Optional context (user tier, budget, complexity, etc)
+ * @returns RoutingDecision with strategy and metadata
+ */
+ abstract route(
+ query: string,
+ context?: Record
+ ): Promise;
+
+ /**
+ * Get router statistics (optional override)
+ *
+ * @returns Dictionary with routing statistics
+ */
+ getStats(): Record {
+ return {};
+ }
+
+ /**
+ * Reset router statistics (optional override)
+ */
+ resetStats(): void {
+ // Override in subclasses
+ }
+}
+
+/**
+ * Chain multiple routers together
+ *
+ * Useful for combining different routing strategies.
+ * First router to make a decision wins.
+ *
+ * @example
+ * ```typescript
+ * const chain = new RouterChain([
+ * new ToolRouter(),
+ * new TierRouter(),
+ * new PreRouter(),
+ * ]);
+ *
+ * const decision = await chain.route('What is AI?');
+ * ```
+ */
+export class RouterChain {
+ private routers: Router[];
+
+ constructor(routers: Router[]) {
+ this.routers = routers;
+ }
+
+ /**
+ * Route through chain of routers
+ *
+ * @param query - User query text
+ * @param context - Optional context
+ * @returns First non-null routing decision
+ */
+ async route(
+ query: string,
+ context?: Record
+ ): Promise {
+ for (const router of this.routers) {
+ const decision = await router.route(query, context);
+ if (decision) {
+ return decision;
+ }
+ }
+
+ // Fallback: direct to best
+ return RoutingDecisionHelper.create(
+ RoutingStrategy.DIRECT_BEST,
+ 'No router made a decision, using fallback',
+ 0.5,
+ { fallback: true }
+ );
+ }
+}
diff --git a/packages/langchain-cascadeflow/src/routers/pre-router.ts b/packages/langchain-cascadeflow/src/routers/pre-router.ts
new file mode 100644
index 00000000..ae1abcd3
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/routers/pre-router.ts
@@ -0,0 +1,398 @@
+/**
+ * Pre-execution router based on query complexity
+ *
+ * This router makes decisions BEFORE cascade execution starts,
+ * routing queries to either cascade or direct execution based
+ * on detected complexity level.
+ *
+ * Routing Logic:
+ * - TRIVIAL/SIMPLE/MODERATE โ CASCADE (cost optimization)
+ * - HARD/EXPERT โ DIRECT_BEST (quality priority)
+ *
+ * Port from @cascadeflow/core
+ */
+
+import { ComplexityDetector, QueryComplexity } from '../complexity.js';
+import {
+ Router,
+ RoutingStrategy,
+ RoutingDecisionHelper,
+ type RoutingDecision,
+} from './base.js';
+
+/**
+ * Configuration for PreRouter
+ */
+export interface PreRouterConfig {
+ /** Enable cascade routing (if false, always direct) */
+ enableCascade?: boolean;
+
+ /** Custom complexity detector */
+ complexityDetector?: ComplexityDetector;
+
+ /** Which complexities should use cascade */
+ cascadeComplexities?: QueryComplexity[];
+
+ /** Enable verbose logging */
+ verbose?: boolean;
+}
+
+/**
+ * Statistics tracked by PreRouter
+ */
+export interface PreRouterStats {
+ /** Total queries routed */
+ totalQueries: number;
+
+ /** Distribution by complexity */
+ byComplexity: Record;
+
+ /** Distribution by strategy */
+ byStrategy: Record;
+
+ /** Cascade rate percentage */
+ cascadeRate: string;
+
+ /** Direct rate percentage */
+ directRate: string;
+
+ /** Number of forced direct routes */
+ forcedDirect: number;
+
+ /** Number of queries when cascade was disabled */
+ cascadeDisabledCount: number;
+}
+
+/**
+ * Complexity-based pre-execution router
+ *
+ * Makes routing decisions before cascade execution starts.
+ * Routes based on detected query complexity:
+ * - Simple queries โ cascade for cost savings
+ * - Complex queries โ direct to best model for quality
+ *
+ * Features:
+ * - Automatic complexity detection
+ * - Configurable complexity thresholds
+ * - Statistics tracking by complexity and strategy
+ * - Confidence scoring for decisions
+ *
+ * Future Enhancements:
+ * - User tier integration (premium โ direct)
+ * - Budget constraints (low budget โ cascade)
+ * - Historical performance learning
+ * - Domain-specific routing rules
+ *
+ * @example
+ * ```typescript
+ * const router = new PreRouter({
+ * enableCascade: true,
+ * cascadeComplexities: ['trivial', 'simple', 'moderate'],
+ * });
+ *
+ * const decision = await router.route('What is 2+2?');
+ * console.log(decision.strategy); // 'cascade'
+ *
+ * const decision2 = await router.route('Explain quantum field theory');
+ * console.log(decision2.strategy); // 'direct_best'
+ * ```
+ */
+export class PreRouter extends Router {
+ private enableCascade: boolean;
+ private detector: ComplexityDetector;
+ private cascadeComplexities: Set;
+ private verbose: boolean;
+ private stats: {
+ totalQueries: number;
+ byComplexity: Map;
+ byStrategy: Map;
+ forcedDirect: number;
+ cascadeDisabled: number;
+ };
+
+ constructor(config: PreRouterConfig = {}) {
+ super();
+
+ this.enableCascade = config.enableCascade ?? true;
+ this.detector = config.complexityDetector || new ComplexityDetector();
+ this.verbose = config.verbose ?? false;
+
+ // Default: cascade for simple queries, direct for complex
+ const defaultCascadeComplexities: QueryComplexity[] = [
+ 'trivial',
+ 'simple',
+ 'moderate',
+ ];
+
+ this.cascadeComplexities = new Set(
+ config.cascadeComplexities || defaultCascadeComplexities
+ );
+
+ // Initialize statistics
+ this.stats = {
+ totalQueries: 0,
+ byComplexity: new Map(),
+ byStrategy: new Map(),
+ forcedDirect: 0,
+ cascadeDisabled: 0,
+ };
+
+ if (this.verbose) {
+ console.log('PreRouter initialized:');
+ console.log(` Cascade enabled: ${this.enableCascade}`);
+ console.log(` Cascade complexities: ${Array.from(this.cascadeComplexities).join(', ')}`);
+ const directComplexities = (['trivial', 'simple', 'moderate', 'hard', 'expert'] as QueryComplexity[])
+ .filter((c) => !this.cascadeComplexities.has(c));
+ console.log(` Direct complexities: ${directComplexities.join(', ')}`);
+ }
+ }
+
+ /**
+ * Route query based on complexity
+ *
+ * Context keys (optional):
+ * - 'complexity': Override auto-detection (QueryComplexity string)
+ * - 'complexityHint': String hint for complexity
+ * - 'forceDirect': Force direct routing
+ * - 'userTier': User tier (for future premium routing)
+ * - 'budget': Budget constraint (for future cost-aware routing)
+ *
+ * @param query - User query text
+ * @param context - Optional context dict
+ * @returns RoutingDecision with strategy and metadata
+ *
+ * @example
+ * ```typescript
+ * // Auto-detect complexity
+ * const decision1 = await router.route('What is 2+2?');
+ *
+ * // Override complexity
+ * const decision2 = await router.route('Complex query', {
+ * complexity: 'expert'
+ * });
+ *
+ * // Force direct routing
+ * const decision3 = await router.route('Any query', {
+ * forceDirect: true
+ * });
+ * ```
+ */
+ async route(
+ query: string,
+ context: Record = {}
+ ): Promise {
+ // Update stats
+ this.stats.totalQueries++;
+
+ // === STEP 1: Detect Complexity ===
+ let complexity: QueryComplexity;
+ let complexityConfidence: number;
+
+ if ('complexity' in context) {
+ // Pre-detected complexity passed in
+ complexity = context.complexity as QueryComplexity;
+ complexityConfidence = context.complexityConfidence ?? 1.0;
+ } else if ('complexityHint' in context) {
+ // String hint provided
+ const hint = context.complexityHint.toLowerCase();
+ if (this.isValidComplexity(hint)) {
+ complexity = hint as QueryComplexity;
+ complexityConfidence = 1.0;
+ } else {
+ // Invalid hint, auto-detect
+ const result = this.detector.detect(query);
+ complexity = result.complexity;
+ complexityConfidence = result.confidence;
+ }
+ } else {
+ // Auto-detect complexity
+ const result = this.detector.detect(query);
+ complexity = result.complexity;
+ complexityConfidence = result.confidence;
+ }
+
+ // Track complexity
+ const complexityCount = this.stats.byComplexity.get(complexity) || 0;
+ this.stats.byComplexity.set(complexity, complexityCount + 1);
+
+ // === STEP 2: Make Routing Decision ===
+ const forceDirect = context.forceDirect === true;
+
+ let strategy: RoutingStrategy;
+ let reason: string;
+ let confidence: number;
+
+ if (forceDirect) {
+ // Forced direct routing
+ strategy = RoutingStrategy.DIRECT_BEST;
+ reason = 'Forced direct routing (bypass cascade)';
+ confidence = 1.0;
+ this.stats.forcedDirect++;
+ } else if (!this.enableCascade) {
+ // Cascade system disabled
+ strategy = RoutingStrategy.DIRECT_BEST;
+ reason = 'Cascade disabled, routing to best model';
+ confidence = 1.0;
+ this.stats.cascadeDisabled++;
+ } else if (this.cascadeComplexities.has(complexity)) {
+ // Simple query โ cascade for cost optimization
+ strategy = RoutingStrategy.CASCADE;
+ reason = `${complexity} query suitable for cascade optimization`;
+ confidence = complexityConfidence;
+ } else {
+ // Complex query โ direct for quality
+ strategy = RoutingStrategy.DIRECT_BEST;
+ reason = `${complexity} query requires best model for quality`;
+ confidence = complexityConfidence;
+ }
+
+ // Track strategy
+ const strategyCount = this.stats.byStrategy.get(strategy) || 0;
+ this.stats.byStrategy.set(strategy, strategyCount + 1);
+
+ // === STEP 3: Build Decision ===
+ const decision = RoutingDecisionHelper.create(
+ strategy,
+ reason,
+ confidence,
+ {
+ complexity,
+ complexityConfidence,
+ router: 'pre',
+ routerType: 'complexity_based',
+ forceDirect,
+ cascadeEnabled: this.enableCascade,
+ }
+ );
+
+ if (this.verbose) {
+ console.log(
+ `[PreRouter] ${query.substring(0, 50)}... โ ${strategy}\n` +
+ ` Complexity: ${complexity} (conf: ${complexityConfidence.toFixed(2)})\n` +
+ ` Reason: ${reason}`
+ );
+ }
+
+ return decision;
+ }
+
+ /**
+ * Get routing statistics
+ *
+ * @returns Dictionary with routing stats including:
+ * - total_queries: Total queries routed
+ * - by_complexity: Distribution by complexity
+ * - by_strategy: Distribution by strategy
+ * - cascade_rate: % of queries using cascade
+ * - direct_rate: % of queries using direct
+ *
+ * @example
+ * ```typescript
+ * const stats = router.getStats();
+ * console.log(`Cascade rate: ${stats.cascadeRate}`);
+ * console.log(`Complexity distribution:`, stats.byComplexity);
+ * ```
+ */
+ getStats(): PreRouterStats {
+ const total = this.stats.totalQueries;
+ if (total === 0) {
+ return {
+ totalQueries: 0,
+ byComplexity: {},
+ byStrategy: {},
+ cascadeRate: '0.0%',
+ directRate: '0.0%',
+ forcedDirect: 0,
+ cascadeDisabledCount: 0,
+ };
+ }
+
+ const cascadeCount = this.stats.byStrategy.get(RoutingStrategy.CASCADE) || 0;
+ const directCount = Array.from(this.stats.byStrategy.entries())
+ .filter(([strategy]) => strategy.startsWith('direct'))
+ .reduce((sum, [, count]) => sum + count, 0);
+
+ return {
+ totalQueries: total,
+ byComplexity: Object.fromEntries(this.stats.byComplexity),
+ byStrategy: Object.fromEntries(this.stats.byStrategy),
+ cascadeRate: `${((cascadeCount / total) * 100).toFixed(1)}%`,
+ directRate: `${((directCount / total) * 100).toFixed(1)}%`,
+ forcedDirect: this.stats.forcedDirect,
+ cascadeDisabledCount: this.stats.cascadeDisabled,
+ };
+ }
+
+ /**
+ * Reset all routing statistics
+ */
+ resetStats(): void {
+ this.stats = {
+ totalQueries: 0,
+ byComplexity: new Map(),
+ byStrategy: new Map(),
+ forcedDirect: 0,
+ cascadeDisabled: 0,
+ };
+ }
+
+ /**
+ * Print formatted routing statistics
+ */
+ printStats(): void {
+ const stats = this.getStats();
+
+ if (stats.totalQueries === 0) {
+ console.log('No routing statistics available');
+ return;
+ }
+
+ console.log('\n' + '='.repeat(60));
+ console.log('PRE-ROUTER STATISTICS');
+ console.log('='.repeat(60));
+ console.log(`Total Queries Routed: ${stats.totalQueries}`);
+ console.log(`Cascade Rate: ${stats.cascadeRate}`);
+ console.log(`Direct Rate: ${stats.directRate}`);
+ console.log(`Forced Direct: ${stats.forcedDirect}`);
+ console.log();
+ console.log('BY COMPLEXITY:');
+ for (const [complexity, count] of Object.entries(stats.byComplexity)) {
+ const pct = ((count / stats.totalQueries) * 100).toFixed(1);
+ console.log(` ${complexity.padEnd(12)}: ${String(count).padStart(4)} (${pct.padStart(5)}%)`);
+ }
+ console.log();
+ console.log('BY STRATEGY:');
+ for (const [strategy, count] of Object.entries(stats.byStrategy)) {
+ const pct = ((count / stats.totalQueries) * 100).toFixed(1);
+ console.log(` ${strategy.padEnd(15)}: ${String(count).padStart(4)} (${pct.padStart(5)}%)`);
+ }
+ console.log('='.repeat(60) + '\n');
+ }
+
+ /**
+ * Check if string is valid complexity
+ */
+ private isValidComplexity(str: string): boolean {
+ return ['trivial', 'simple', 'moderate', 'hard', 'expert'].includes(str);
+ }
+}
+
+/**
+ * Create a PreRouter with configuration
+ *
+ * @param config - PreRouter configuration
+ * @returns Configured PreRouter instance
+ *
+ * @example
+ * ```typescript
+ * import { createPreRouter } from '@cascadeflow/langchain';
+ *
+ * const router = createPreRouter({
+ * enableCascade: true,
+ * verbose: true,
+ * });
+ * ```
+ */
+export function createPreRouter(config?: PreRouterConfig): PreRouter {
+ return new PreRouter(config);
+}
diff --git a/packages/langchain-cascadeflow/src/types.ts b/packages/langchain-cascadeflow/src/types.ts
new file mode 100644
index 00000000..93db4305
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/types.ts
@@ -0,0 +1,134 @@
+import type { PreRouter } from './routers/pre-router.js';
+import type { QueryComplexity } from './complexity.js';
+
+/**
+ * Configuration for the CascadeFlow wrapper
+ */
+export interface CascadeConfig {
+ /**
+ * The drafter model (cheap, fast) - tries first
+ */
+ drafter: any; // BaseChatModel from @langchain/core
+
+ /**
+ * The verifier model (expensive, accurate) - used when quality is insufficient
+ */
+ verifier: any; // BaseChatModel from @langchain/core
+
+ /**
+ * Quality threshold for accepting drafter responses (0-1)
+ * @default 0.7
+ */
+ qualityThreshold?: number;
+
+ /**
+ * Enable automatic cost tracking
+ * @default true
+ */
+ enableCostTracking?: boolean;
+
+ /**
+ * Cost tracking provider
+ * - 'langsmith': Use LangSmith's server-side cost calculation (default, requires LANGSMITH_API_KEY)
+ * - 'cascadeflow': Use CascadeFlow's built-in pricing table (no external dependencies)
+ * @default 'langsmith'
+ */
+ costTrackingProvider?: 'langsmith' | 'cascadeflow';
+
+ /**
+ * Custom quality validator function
+ * Returns confidence score between 0-1
+ */
+ qualityValidator?: (response: any) => Promise | number;
+
+ /**
+ * Enable pre-routing based on query complexity
+ * When enabled, 'hard' and 'expert' queries skip the drafter and go directly to the verifier
+ * @default false
+ */
+ enablePreRouter?: boolean;
+
+ /**
+ * Custom PreRouter instance for advanced routing control
+ * If not provided, a default PreRouter will be created when enablePreRouter is true
+ */
+ preRouter?: PreRouter;
+
+ /**
+ * Complexity levels that should use cascade (try drafter first)
+ * Queries with other complexity levels go directly to verifier
+ * @default ['trivial', 'simple', 'moderate']
+ */
+ cascadeComplexities?: QueryComplexity[];
+}
+
+/**
+ * Cascade execution result with cost metadata
+ */
+export interface CascadeResult {
+ /**
+ * The final response content
+ */
+ content: string;
+
+ /**
+ * Model that provided the final response ('drafter' | 'verifier')
+ */
+ modelUsed: 'drafter' | 'verifier';
+
+ /**
+ * Quality score of the drafter response (0-1)
+ */
+ drafterQuality?: number;
+
+ /**
+ * Whether the drafter response was accepted
+ */
+ accepted: boolean;
+
+ /**
+ * Cost of the drafter call
+ */
+ drafterCost: number;
+
+ /**
+ * Cost of the verifier call (0 if not used)
+ */
+ verifierCost: number;
+
+ /**
+ * Total cost of the cascade
+ */
+ totalCost: number;
+
+ /**
+ * Cost savings percentage (0-100)
+ */
+ savingsPercentage: number;
+
+ /**
+ * Latency in milliseconds
+ */
+ latencyMs: number;
+}
+
+/**
+ * Internal cost calculation metadata
+ */
+export interface CostMetadata {
+ drafterTokens: {
+ input: number;
+ output: number;
+ };
+ verifierTokens?: {
+ input: number;
+ output: number;
+ };
+ drafterCost: number;
+ verifierCost: number;
+ totalCost: number;
+ savingsPercentage: number;
+ modelUsed: 'drafter' | 'verifier';
+ accepted: boolean;
+ drafterQuality?: number;
+}
diff --git a/packages/langchain-cascadeflow/src/utils.test.ts b/packages/langchain-cascadeflow/src/utils.test.ts
new file mode 100644
index 00000000..f350918c
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/utils.test.ts
@@ -0,0 +1,399 @@
+import { describe, it, expect } from 'vitest';
+import {
+ extractTokenUsage,
+ calculateQuality,
+ calculateCost,
+ calculateSavings,
+ createCostMetadata,
+} from './utils.js';
+
+describe('extractTokenUsage', () => {
+ it('should extract OpenAI format with snake_case', () => {
+ const response = {
+ llmOutput: {
+ tokenUsage: {
+ prompt_tokens: 100,
+ completion_tokens: 50,
+ },
+ },
+ };
+
+ const result = extractTokenUsage(response);
+ expect(result).toEqual({ input: 100, output: 50 });
+ });
+
+ it('should extract OpenAI format with camelCase (LangChain)', () => {
+ const response = {
+ llmOutput: {
+ tokenUsage: {
+ promptTokens: 100,
+ completionTokens: 50,
+ },
+ },
+ };
+
+ const result = extractTokenUsage(response);
+ expect(result).toEqual({ input: 100, output: 50 });
+ });
+
+ it('should extract Anthropic format', () => {
+ const response = {
+ llmOutput: {
+ tokenUsage: {
+ input_tokens: 100,
+ output_tokens: 50,
+ },
+ },
+ };
+
+ const result = extractTokenUsage(response);
+ expect(result).toEqual({ input: 100, output: 50 });
+ });
+
+ it('should handle usage nested under llmOutput.usage', () => {
+ const response = {
+ llmOutput: {
+ usage: {
+ promptTokens: 100,
+ completionTokens: 50,
+ },
+ },
+ };
+
+ const result = extractTokenUsage(response);
+ expect(result).toEqual({ input: 100, output: 50 });
+ });
+
+ it('should return zeros for missing token usage', () => {
+ const response = {
+ llmOutput: {},
+ };
+
+ const result = extractTokenUsage(response);
+ expect(result).toEqual({ input: 0, output: 0 });
+ });
+
+ it('should handle partial token counts', () => {
+ const response = {
+ llmOutput: {
+ tokenUsage: {
+ promptTokens: 100,
+ // missing completionTokens
+ },
+ },
+ };
+
+ const result = extractTokenUsage(response);
+ expect(result).toEqual({ input: 100, output: 0 });
+ });
+});
+
+describe('calculateQuality', () => {
+ it('should return low quality for empty text', () => {
+ const response = {
+ generations: [
+ {
+ text: '',
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ expect(result).toBe(0.2);
+ });
+
+ it('should return low quality for very short text', () => {
+ const response = {
+ generations: [
+ {
+ text: 'Hi',
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ expect(result).toBe(0.2);
+ });
+
+ it('should calculate quality for simple complete answer', () => {
+ const response = {
+ generations: [
+ {
+ text: '2 + 2 equals 4.',
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ // Base 0.6 + length>20 (0.1) + has punctuation (0.05) + starts capital (0.05) + ends punctuation (0.1) = 0.9
+ expect(result).toBeGreaterThanOrEqual(0.7);
+ expect(result).toBeLessThanOrEqual(1.0);
+ });
+
+ it('should calculate quality for complex answer', () => {
+ const response = {
+ generations: [
+ {
+ text: 'Quantum entanglement is a fundamental phenomenon in quantum mechanics where two or more particles become interconnected.',
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ // Base 0.6 + length>20 (0.1) + length>100 (0.1) + punctuation (0.05) + capital (0.05) + ends (0.1) = 1.0
+ expect(result).toBeGreaterThanOrEqual(0.9);
+ expect(result).toBe(1.0);
+ });
+
+ it('should penalize hedging phrases', () => {
+ const response = {
+ generations: [
+ {
+ text: "I don't know the answer to that question.",
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ // Base 0.6 + length>20 (0.1) + punct (0.05) + capital (0.05) + ends (0.1) - hedge (0.1) = 0.8
+ expect(result).toBeLessThanOrEqual(0.8);
+ });
+
+ it('should extract text from message.content', () => {
+ const response = {
+ generations: [
+ {
+ message: {
+ content: 'TypeScript is a typed superset of JavaScript.',
+ },
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ expect(result).toBeGreaterThan(0.7);
+ });
+
+ it('should handle logprobs-based confidence (OpenAI)', () => {
+ const response = {
+ generations: [
+ {
+ text: 'Test answer',
+ generationInfo: {
+ logprobs: {
+ content: [
+ { token: 'Test', logprob: -0.1 },
+ { token: ' answer', logprob: -0.2 },
+ ],
+ },
+ },
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ // Should use logprobs instead of heuristics
+ expect(result).toBeGreaterThan(0);
+ expect(result).toBeLessThanOrEqual(1);
+ });
+
+ it('should cap quality at 1.0', () => {
+ const response = {
+ generations: [
+ {
+ text: 'A very long and detailed answer that has excellent structure, perfect capitalization, proper punctuation, and comprehensive content that demonstrates high quality throughout the entire response.',
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ expect(result).toBeLessThanOrEqual(1.0);
+ });
+
+ it('should floor quality at 0.1', () => {
+ const response = {
+ generations: [
+ {
+ text: "i don't know i'm not sure i cannot i can't",
+ },
+ ],
+ };
+
+ const result = calculateQuality(response);
+ expect(result).toBeGreaterThanOrEqual(0.1);
+ });
+});
+
+describe('calculateCost', () => {
+ it('should calculate cost for GPT-4o-mini', () => {
+ const cost = calculateCost('gpt-4o-mini', 100_000, 50_000);
+ // Input: (100k / 1M) * 0.150 = 0.015
+ // Output: (50k / 1M) * 0.600 = 0.030
+ // Total: 0.045
+ expect(cost).toBeCloseTo(0.045, 3);
+ });
+
+ it('should calculate cost for GPT-4o', () => {
+ const cost = calculateCost('gpt-4o', 100_000, 50_000);
+ // Input: (100k / 1M) * 2.50 = 0.250
+ // Output: (50k / 1M) * 10.00 = 0.500
+ // Total: 0.750
+ expect(cost).toBeCloseTo(0.750, 3);
+ });
+
+ it('should calculate cost for Claude Sonnet', () => {
+ const cost = calculateCost('claude-3-5-sonnet-20241022', 100_000, 50_000);
+ // Input: (100k / 1M) * 3.00 = 0.300
+ // Output: (50k / 1M) * 15.00 = 0.750
+ // Total: 1.050
+ expect(cost).toBeCloseTo(1.050, 3);
+ });
+
+ it('should return 0 for unknown model', () => {
+ const cost = calculateCost('unknown-model', 100_000, 50_000);
+ expect(cost).toBe(0);
+ });
+
+ it('should handle zero tokens', () => {
+ const cost = calculateCost('gpt-4o-mini', 0, 0);
+ expect(cost).toBe(0);
+ });
+
+ it('should calculate cost for small token counts', () => {
+ const cost = calculateCost('gpt-4o-mini', 14, 8);
+ // Input: (14 / 1M) * 0.150 = 0.0000021
+ // Output: (8 / 1M) * 0.600 = 0.0000048
+ // Total: 0.0000069
+ expect(cost).toBeCloseTo(0.0000069, 7);
+ });
+});
+
+describe('calculateSavings', () => {
+ it('should calculate savings percentage', () => {
+ const savings = calculateSavings(0.01, 0.10);
+ // Total cost: 0.11
+ // Potential cost (verifier only): 0.10
+ // Savings: ((0.10 - 0.11) / 0.10) * 100 = -10%
+ expect(savings).toBeCloseTo(-10, 1);
+ });
+
+ it('should show positive savings when drafter is accepted', () => {
+ const savings = calculateSavings(0.01, 0.00);
+ // Total cost: 0.01
+ // Potential cost: 0.00
+ // When verifier cost is 0, it means drafter was accepted, so no savings
+ expect(savings).toBe(0);
+ });
+
+ it('should return 0 when verifier cost is 0', () => {
+ const savings = calculateSavings(0.05, 0);
+ expect(savings).toBe(0);
+ });
+
+ it('should handle equal costs', () => {
+ const savings = calculateSavings(0.05, 0.05);
+ // Total: 0.10, Potential: 0.05
+ // Savings: ((0.05 - 0.10) / 0.05) * 100 = -100%
+ expect(savings).toBeCloseTo(-100, 1);
+ });
+});
+
+describe('createCostMetadata', () => {
+ it('should create metadata for accepted drafter response', () => {
+ const drafterResponse = {
+ generations: [{ text: '4' }],
+ llmOutput: {
+ tokenUsage: {
+ promptTokens: 14,
+ completionTokens: 8,
+ },
+ },
+ };
+
+ const metadata = createCostMetadata(
+ drafterResponse,
+ null,
+ 'gpt-4o-mini',
+ 'gpt-4o',
+ true,
+ 0.8,
+ 'cascadeflow'
+ );
+
+ expect(metadata.drafterTokens).toEqual({ input: 14, output: 8 });
+ expect(metadata.verifierTokens).toBeUndefined();
+ expect(metadata.drafterCost).toBeCloseTo(0.0000069, 7);
+ expect(metadata.verifierCost).toBe(0);
+ expect(metadata.totalCost).toBeCloseTo(0.0000069, 7);
+ expect(metadata.savingsPercentage).toBe(0);
+ expect(metadata.modelUsed).toBe('drafter');
+ expect(metadata.accepted).toBe(true);
+ expect(metadata.drafterQuality).toBe(0.8);
+ });
+
+ it('should create metadata for rejected drafter response', () => {
+ const drafterResponse = {
+ generations: [{ text: 'I don\'t know' }],
+ llmOutput: {
+ tokenUsage: {
+ promptTokens: 14,
+ completionTokens: 5,
+ },
+ },
+ };
+
+ const verifierResponse = {
+ generations: [{ text: 'The answer is 4' }],
+ llmOutput: {
+ tokenUsage: {
+ promptTokens: 14,
+ completionTokens: 8,
+ },
+ },
+ };
+
+ const metadata = createCostMetadata(
+ drafterResponse,
+ verifierResponse,
+ 'gpt-4o-mini',
+ 'gpt-4o',
+ false,
+ 0.3,
+ 'cascadeflow'
+ );
+
+ expect(metadata.drafterTokens).toEqual({ input: 14, output: 5 });
+ expect(metadata.verifierTokens).toEqual({ input: 14, output: 8 });
+ expect(metadata.drafterCost).toBeGreaterThan(0);
+ expect(metadata.verifierCost).toBeGreaterThan(0);
+ expect(metadata.totalCost).toBeGreaterThan(metadata.drafterCost);
+ expect(metadata.modelUsed).toBe('verifier');
+ expect(metadata.accepted).toBe(false);
+ expect(metadata.drafterQuality).toBe(0.3);
+ });
+
+ it('should handle unknown models gracefully', () => {
+ const drafterResponse = {
+ generations: [{ text: 'Test' }],
+ llmOutput: {
+ tokenUsage: {
+ promptTokens: 10,
+ completionTokens: 5,
+ },
+ },
+ };
+
+ const metadata = createCostMetadata(
+ drafterResponse,
+ null,
+ 'unknown-model-1',
+ 'unknown-model-2',
+ true,
+ 0.9
+ );
+
+ expect(metadata.drafterCost).toBe(0);
+ expect(metadata.verifierCost).toBe(0);
+ expect(metadata.totalCost).toBe(0);
+ });
+});
diff --git a/packages/langchain-cascadeflow/src/utils.ts b/packages/langchain-cascadeflow/src/utils.ts
new file mode 100644
index 00000000..9795bde9
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/utils.ts
@@ -0,0 +1,203 @@
+import type { CostMetadata } from './types.js';
+
+/**
+ * Model pricing per 1M tokens (input/output)
+ * TODO: Import from @cascadeflow/core or make configurable
+ */
+const MODEL_PRICING: Record = {
+ // OpenAI
+ 'gpt-4o-mini': { input: 0.150, output: 0.600 },
+ 'gpt-4o': { input: 2.50, output: 10.00 },
+ 'gpt-4-turbo': { input: 10.00, output: 30.00 },
+ 'gpt-3.5-turbo': { input: 0.50, output: 1.50 },
+
+ // Anthropic
+ 'claude-3-5-sonnet-20241022': { input: 3.00, output: 15.00 },
+ 'claude-3-5-haiku-20241022': { input: 0.80, output: 4.00 },
+ 'claude-3-opus-20240229': { input: 15.00, output: 75.00 },
+ 'claude-3-sonnet-20240229': { input: 3.00, output: 15.00 },
+ 'claude-3-haiku-20240307': { input: 0.25, output: 1.25 },
+};
+
+/**
+ * Calculate cost based on token usage and model
+ */
+export function calculateCost(
+ modelName: string,
+ inputTokens: number,
+ outputTokens: number
+): number {
+ const pricing = MODEL_PRICING[modelName];
+
+ if (!pricing) {
+ console.warn(`Unknown model for pricing: ${modelName}, using default`);
+ return 0;
+ }
+
+ const inputCost = (inputTokens / 1_000_000) * pricing.input;
+ const outputCost = (outputTokens / 1_000_000) * pricing.output;
+
+ return inputCost + outputCost;
+}
+
+/**
+ * Extract token usage from LangChain response
+ */
+export function extractTokenUsage(response: any): { input: number; output: number } {
+ // LangChain ChatResult structure
+ const llmOutput = response?.llmOutput || {};
+ const usage = llmOutput?.tokenUsage || llmOutput?.usage || {};
+
+ // OpenAI format (snake_case)
+ if (usage.prompt_tokens || usage.completion_tokens) {
+ return {
+ input: usage.prompt_tokens || 0,
+ output: usage.completion_tokens || 0,
+ };
+ }
+
+ // OpenAI format (camelCase - LangChain uses this)
+ if (usage.promptTokens || usage.completionTokens) {
+ return {
+ input: usage.promptTokens || 0,
+ output: usage.completionTokens || 0,
+ };
+ }
+
+ // Anthropic format
+ if (usage.input_tokens || usage.output_tokens) {
+ return {
+ input: usage.input_tokens || 0,
+ output: usage.output_tokens || 0,
+ };
+ }
+
+ // Default
+ return { input: 0, output: 0 };
+}
+
+/**
+ * Calculate quality score from LangChain response
+ * Uses logprobs if available, otherwise heuristics
+ */
+export function calculateQuality(response: any): number {
+ // 1. Try logprobs-based confidence (OpenAI)
+ const generationInfo = response?.generations?.[0]?.generationInfo;
+ if (generationInfo?.logprobs?.content) {
+ // OpenAI format: content is array of {token, logprob}
+ const logprobs = generationInfo.logprobs.content
+ .map((item: any) => item.logprob)
+ .filter((lp: any) => lp !== null && lp !== undefined);
+
+ if (logprobs.length > 0) {
+ const avgLogprob = logprobs.reduce((a: number, b: number) => a + b, 0) / logprobs.length;
+ const confidence = Math.exp(avgLogprob); // Convert log probability to probability
+ return Math.max(0.1, Math.min(1, confidence * 1.5)); // Boost slightly
+ }
+ }
+
+ // 2. Heuristic-based quality scoring
+ // LangChain ChatResult has generations as a flat array, not nested
+ const generation = response?.generations?.[0];
+ const text = generation?.text || generation?.message?.content || '';
+
+ if (!text || text.length < 5) {
+ return 0.2; // Low quality for empty/very short responses
+ }
+
+ // Check for common quality indicators
+ let score = 0.6; // Base score (increased from 0.5)
+
+ // Length bonus (reasonable response)
+ if (text.length > 20) score += 0.1;
+ if (text.length > 100) score += 0.1;
+
+ // Structure bonus (has punctuation, capitalization)
+ if (/[.!?]/.test(text)) score += 0.05;
+ if (/^[A-Z]/.test(text)) score += 0.05;
+
+ // Completeness bonus (ends with punctuation)
+ if (/[.!?]$/.test(text.trim())) score += 0.1;
+
+ // Penalize hedging phrases (but less harshly)
+ const hedgingPhrases = [
+ 'i don\'t know', 'i\'m not sure', 'i cannot', 'i can\'t'
+ ];
+ const lowerText = text.toLowerCase();
+ const hedgeCount = hedgingPhrases.filter(phrase => lowerText.includes(phrase)).length;
+ score -= hedgeCount * 0.1;
+
+ return Math.max(0.1, Math.min(1, score));
+}
+
+/**
+ * Calculate savings percentage
+ */
+export function calculateSavings(drafterCost: number, verifierCost: number): number {
+ if (verifierCost === 0) return 0;
+
+ const totalCost = drafterCost + verifierCost;
+ const potentialCost = verifierCost; // If we had used verifier directly
+
+ return ((potentialCost - totalCost) / potentialCost) * 100;
+}
+
+/**
+ * Create cost metadata with configurable provider
+ * @param costProvider - 'langsmith' (server-side) or 'cascadeflow' (local calculation)
+ */
+export function createCostMetadata(
+ drafterResponse: any,
+ verifierResponse: any | null,
+ drafterModel: string,
+ verifierModel: string,
+ accepted: boolean,
+ drafterQuality: number,
+ costProvider: 'langsmith' | 'cascadeflow' = 'langsmith'
+): CostMetadata {
+ const drafterTokens = extractTokenUsage(drafterResponse);
+
+ let drafterCost: number;
+ let verifierCost: number;
+ let verifierTokens: { input: number; output: number } | undefined;
+
+ if (costProvider === 'cascadeflow') {
+ // Use CascadeFlow's built-in pricing calculation
+ drafterCost = calculateCost(drafterModel, drafterTokens.input, drafterTokens.output);
+
+ if (verifierResponse) {
+ verifierTokens = extractTokenUsage(verifierResponse);
+ verifierCost = calculateCost(verifierModel, verifierTokens.input, verifierTokens.output);
+ } else {
+ verifierTokens = undefined;
+ verifierCost = 0;
+ }
+ } else {
+ // LangSmith provider - costs calculated server-side
+ // We still track tokens for metadata, but costs are 0 (calculated by LangSmith)
+ drafterCost = 0;
+
+ if (verifierResponse) {
+ verifierTokens = extractTokenUsage(verifierResponse);
+ verifierCost = 0;
+ } else {
+ verifierTokens = undefined;
+ verifierCost = 0;
+ }
+ }
+
+ const totalCost = drafterCost + verifierCost;
+ const savingsPercentage = calculateSavings(drafterCost, verifierCost);
+
+ return {
+ drafterTokens,
+ verifierTokens,
+ drafterCost,
+ verifierCost,
+ totalCost,
+ savingsPercentage,
+ modelUsed: accepted ? 'drafter' : 'verifier',
+ accepted,
+ drafterQuality,
+ };
+}
diff --git a/packages/langchain-cascadeflow/src/wrapper.test.ts b/packages/langchain-cascadeflow/src/wrapper.test.ts
new file mode 100644
index 00000000..d8efbcf3
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/wrapper.test.ts
@@ -0,0 +1,535 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { BaseChatModel } from '@langchain/core/language_models/chat_models';
+import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
+import { ChatResult, ChatGeneration } from '@langchain/core/outputs';
+import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
+import { CascadeFlow } from './wrapper.js';
+
+/**
+ * Mock chat model for testing
+ */
+class MockChatModel extends BaseChatModel {
+ modelName: string;
+ responses: ChatResult[];
+ callCount = 0;
+
+ constructor(modelName: string, responses: ChatResult[] = []) {
+ super({});
+ this.modelName = modelName;
+ this.responses = responses;
+ }
+
+ _llmType(): string {
+ return 'mock';
+ }
+
+ async _generate(
+ messages: BaseMessage[],
+ options: this['ParsedCallOptions'],
+ runManager?: CallbackManagerForLLMRun
+ ): Promise {
+ const index = Math.min(this.callCount, this.responses.length - 1);
+ const response = this.responses[index];
+ this.callCount++;
+ if (!response) {
+ throw new Error('No mock response configured');
+ }
+ return response;
+ }
+
+ get model() {
+ return this.modelName;
+ }
+}
+
+/**
+ * Helper to create a ChatResult
+ */
+function createChatResult(
+ text: string,
+ promptTokens: number,
+ completionTokens: number
+): ChatResult {
+ return {
+ generations: [
+ {
+ text,
+ message: new AIMessage(text),
+ },
+ ],
+ llmOutput: {
+ tokenUsage: {
+ promptTokens,
+ completionTokens,
+ totalTokens: promptTokens + completionTokens,
+ },
+ },
+ };
+}
+
+describe('CascadeFlow', () => {
+ describe('Constructor and Configuration', () => {
+ it('should initialize with default configuration', () => {
+ const drafter = new MockChatModel('gpt-4o-mini');
+ const verifier = new MockChatModel('gpt-4o');
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ expect(cascade.drafter).toBe(drafter);
+ expect(cascade.verifier).toBe(verifier);
+ expect(cascade._llmType()).toBe('cascadeflow');
+ });
+
+ it('should accept custom quality threshold', () => {
+ const drafter = new MockChatModel('gpt-4o-mini');
+ const verifier = new MockChatModel('gpt-4o');
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.9,
+ });
+
+ // Quality threshold is stored internally, we'll verify via behavior
+ expect(cascade).toBeDefined();
+ });
+
+ it('should accept custom quality validator', () => {
+ const drafter = new MockChatModel('gpt-4o-mini');
+ const verifier = new MockChatModel('gpt-4o');
+ const customValidator = vi.fn(() => 0.5);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityValidator: customValidator,
+ });
+
+ expect(cascade).toBeDefined();
+ });
+ });
+
+ describe('Cascade Logic - High Quality Drafter', () => {
+ it('should use drafter response when quality is above threshold', async () => {
+ const drafterResponse = createChatResult('The answer is 4.', 14, 8);
+ const verifierResponse = createChatResult('2 + 2 equals 4.', 14, 10);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [verifierResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ });
+
+ const messages = [new HumanMessage('What is 2+2?')];
+ const result = await cascade._generate(messages, {});
+
+ // Should use drafter response
+ expect(result.generations[0].text).toBe('The answer is 4.');
+ expect(drafter.callCount).toBe(1);
+ expect(verifier.callCount).toBe(0); // Verifier not called
+
+ // Check cascade stats
+ const stats = cascade.getLastCascadeResult();
+ expect(stats).toBeDefined();
+ expect(stats!.modelUsed).toBe('drafter');
+ expect(stats!.accepted).toBe(true);
+ expect(stats!.drafterQuality).toBeGreaterThan(0.7);
+ expect(stats!.verifierCost).toBe(0);
+ });
+ });
+
+ describe('Cascade Logic - Low Quality Drafter', () => {
+ it('should use verifier response when quality is below threshold', async () => {
+ // Use a very short, low-quality response that will score below 0.7
+ const drafterResponse = createChatResult("no", 14, 1);
+ const verifierResponse = createChatResult('The answer is 4.', 14, 10);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [verifierResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ costTrackingProvider: 'cascadeflow',
+ });
+
+ const messages = [new HumanMessage('What is 2+2?')];
+ const result = await cascade._generate(messages, {});
+
+ // Should use verifier response
+ expect(result.generations[0].text).toBe('The answer is 4.');
+ expect(drafter.callCount).toBe(1);
+ expect(verifier.callCount).toBe(1); // Verifier WAS called
+
+ // Check cascade stats
+ const stats = cascade.getLastCascadeResult();
+ expect(stats).toBeDefined();
+ expect(stats!.modelUsed).toBe('verifier');
+ expect(stats!.accepted).toBe(false);
+ expect(stats!.drafterQuality).toBeLessThan(0.7);
+ expect(stats!.verifierCost).toBeGreaterThan(0);
+ });
+ });
+
+ describe('Custom Quality Validator', () => {
+ it('should use custom quality validator', async () => {
+ const drafterResponse = createChatResult('Test answer', 14, 8);
+ const verifierResponse = createChatResult('Better answer', 14, 10);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [verifierResponse]);
+
+ // Custom validator that always returns low quality
+ const customValidator = vi.fn(() => 0.3);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ qualityValidator: customValidator,
+ });
+
+ const messages = [new HumanMessage('Test')];
+ await cascade._generate(messages, {});
+
+ // Custom validator should have been called
+ expect(customValidator).toHaveBeenCalled();
+ expect(verifier.callCount).toBe(1); // Should cascade to verifier
+ });
+
+ it('should support async quality validator', async () => {
+ const drafterResponse = createChatResult('Test answer', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [drafterResponse]);
+
+ const asyncValidator = vi.fn(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ return 0.9;
+ });
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ qualityValidator: asyncValidator,
+ });
+
+ const messages = [new HumanMessage('Test')];
+ const result = await cascade._generate(messages, {});
+
+ expect(asyncValidator).toHaveBeenCalled();
+ expect(verifier.callCount).toBe(0); // Quality is high, no cascade
+ });
+ });
+
+ describe('Cost Tracking', () => {
+ it('should calculate costs correctly for accepted drafter', async () => {
+ const drafterResponse = createChatResult('The answer is 4.', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', []);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ costTrackingProvider: 'cascadeflow',
+ });
+
+ const messages = [new HumanMessage('What is 2+2?')];
+ await cascade._generate(messages, {});
+
+ const stats = cascade.getLastCascadeResult();
+ expect(stats!.drafterCost).toBeGreaterThan(0);
+ expect(stats!.verifierCost).toBe(0);
+ expect(stats!.totalCost).toBe(stats!.drafterCost);
+ });
+
+ it('should calculate costs correctly for rejected drafter', async () => {
+ const drafterResponse = createChatResult("no", 14, 1);
+ const verifierResponse = createChatResult('The answer is 4.', 14, 10);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [verifierResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ costTrackingProvider: 'cascadeflow',
+ });
+
+ const messages = [new HumanMessage('What is 2+2?')];
+ await cascade._generate(messages, {});
+
+ const stats = cascade.getLastCascadeResult();
+ expect(stats!.drafterCost).toBeGreaterThan(0);
+ expect(stats!.verifierCost).toBeGreaterThan(0);
+ expect(stats!.totalCost).toBe(stats!.drafterCost + stats!.verifierCost);
+ });
+
+ it('should track latency', async () => {
+ const drafterResponse = createChatResult('Answer', 14, 8);
+ const dummyResponse = createChatResult('Dummy', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const messages = [new HumanMessage('Test')];
+ await cascade._generate(messages, {});
+
+ const stats = cascade.getLastCascadeResult();
+ expect(stats!.latencyMs).toBeGreaterThanOrEqual(0);
+ });
+ });
+
+ describe('Chainable Methods - bind()', () => {
+ it('should support bind() and return new CascadeFlow', () => {
+ const drafter = new MockChatModel('gpt-4o-mini');
+ const verifier = new MockChatModel('gpt-4o');
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const boundCascade = cascade.bind({ temperature: 0.5 });
+
+ expect(boundCascade).toBeInstanceOf(CascadeFlow);
+ expect(boundCascade).not.toBe(cascade); // New instance
+ });
+
+ it('should merge bind kwargs correctly', async () => {
+ const drafterResponse = createChatResult('Answer', 14, 8);
+ const dummyResponse = createChatResult('Dummy', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const boundCascade = cascade.bind({ temperature: 0.5 });
+
+ const messages = [new HumanMessage('Test')];
+ const result = await boundCascade._generate(messages, {});
+
+ expect(result).toBeDefined();
+ expect(drafter.callCount).toBe(1);
+ });
+
+ it('should chain multiple bind() calls', () => {
+ const drafter = new MockChatModel('gpt-4o-mini');
+ const verifier = new MockChatModel('gpt-4o');
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const bound1 = cascade.bind({ temperature: 0.5 });
+ const bound2 = bound1.bind({ maxTokens: 100 });
+
+ expect(bound2).toBeInstanceOf(CascadeFlow);
+ });
+ });
+
+ describe('Metadata Injection', () => {
+ it('should inject cascade metadata into llmOutput', async () => {
+ const drafterResponse = createChatResult('The answer is correct.', 14, 8);
+ const dummyResponse = createChatResult('Dummy', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ enableCostTracking: true,
+ costTrackingProvider: 'cascadeflow',
+ });
+
+ const messages = [new HumanMessage('Test')];
+ const result = await cascade._generate(messages, {});
+
+ // Metadata is injected even without runManager (changed in improvement)
+ expect(result.llmOutput).toBeDefined();
+ expect(result.llmOutput?.cascade).toBeDefined(); // Now always injected when tracking enabled
+
+ // Verify cascade metadata structure
+ expect(result.llmOutput?.cascade.modelUsed).toBe('drafter');
+ expect(result.llmOutput?.cascade.drafterCost).toBeGreaterThan(0);
+
+ // We can still get stats via getLastCascadeResult()
+ const stats = cascade.getLastCascadeResult();
+ expect(stats).toBeDefined();
+ expect(stats!.drafterCost).toBeGreaterThan(0);
+ });
+
+ it('should not inject metadata when enableCostTracking is false', async () => {
+ const drafterResponse = createChatResult('Answer', 14, 8);
+ const dummyResponse = createChatResult('Dummy', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ enableCostTracking: false,
+ });
+
+ const messages = [new HumanMessage('Test')];
+ const result = await cascade._generate(messages, {});
+
+ // Metadata still won't be injected without runManager
+ expect(result.llmOutput?.cascade).toBeUndefined();
+ });
+ });
+
+ describe('Edge Cases', () => {
+ it('should handle empty message array', async () => {
+ const drafterResponse = createChatResult('Answer', 0, 5);
+ const dummyResponse = createChatResult('Dummy', 0, 5);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const result = await cascade._generate([], {});
+ expect(result).toBeDefined();
+ });
+
+ it('should handle quality exactly at threshold', async () => {
+ const drafterResponse = createChatResult('Test answer.', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', []);
+
+ // Custom validator that returns exactly the threshold
+ const exactValidator = vi.fn(() => 0.7);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ qualityThreshold: 0.7,
+ qualityValidator: exactValidator,
+ });
+
+ const messages = [new HumanMessage('Test')];
+ await cascade._generate(messages, {});
+
+ const stats = cascade.getLastCascadeResult();
+ // Quality >= threshold should be accepted
+ expect(stats!.accepted).toBe(true);
+ expect(verifier.callCount).toBe(0);
+ });
+
+ it('should handle missing token usage gracefully', async () => {
+ const responseWithoutTokens: ChatResult = {
+ generations: [
+ {
+ text: 'Answer',
+ message: new AIMessage('Answer'),
+ },
+ ],
+ llmOutput: {},
+ };
+
+ const dummyResponse = createChatResult('Dummy', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [responseWithoutTokens]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const messages = [new HumanMessage('Test')];
+ const result = await cascade._generate(messages, {});
+
+ const stats = cascade.getLastCascadeResult();
+ expect(stats!.drafterCost).toBe(0); // No tokens = no cost
+ });
+ });
+
+ describe('getLastCascadeResult', () => {
+ it('should return undefined before first call', () => {
+ const drafter = new MockChatModel('gpt-4o-mini');
+ const verifier = new MockChatModel('gpt-4o');
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const stats = cascade.getLastCascadeResult();
+ expect(stats).toBeUndefined();
+ });
+
+ it('should return stats after successful call', async () => {
+ const drafterResponse = createChatResult('The answer is correct.', 14, 8);
+ const dummyResponse = createChatResult('Dummy', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [drafterResponse]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const messages = [new HumanMessage('Test')];
+ await cascade._generate(messages, {});
+
+ const stats = cascade.getLastCascadeResult();
+ expect(stats).toBeDefined();
+ expect(stats!.content).toBe('The answer is correct.');
+ expect(stats!.modelUsed).toBe('drafter');
+ });
+
+ it('should update stats on each call', async () => {
+ const response1 = createChatResult('The first answer is good.', 14, 8);
+ const response2 = createChatResult('The second answer is better.', 20, 10);
+ const dummyResponse = createChatResult('Dummy', 14, 8);
+
+ const drafter = new MockChatModel('gpt-4o-mini', [response1, response2]);
+ const verifier = new MockChatModel('gpt-4o', [dummyResponse]);
+
+ const cascade = new CascadeFlow({
+ drafter,
+ verifier,
+ });
+
+ const messages = [new HumanMessage('Test')];
+
+ await cascade._generate(messages, {});
+ const stats1 = cascade.getLastCascadeResult();
+ expect(stats1!.content).toBe('The first answer is good.');
+
+ await cascade._generate(messages, {});
+ const stats2 = cascade.getLastCascadeResult();
+ expect(stats2!.content).toBe('The second answer is better.');
+ });
+ });
+});
diff --git a/packages/langchain-cascadeflow/src/wrapper.ts b/packages/langchain-cascadeflow/src/wrapper.ts
new file mode 100644
index 00000000..367c357f
--- /dev/null
+++ b/packages/langchain-cascadeflow/src/wrapper.ts
@@ -0,0 +1,569 @@
+import { BaseChatModel } from '@langchain/core/language_models/chat_models';
+import { BaseMessage, AIMessage, ChatMessage, HumanMessage } from '@langchain/core/messages';
+import { ChatResult, ChatGeneration, ChatGenerationChunk } from '@langchain/core/outputs';
+import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
+import type { CascadeConfig, CascadeResult } from './types.js';
+import { calculateQuality, createCostMetadata } from './utils.js';
+import { PreRouter } from './routers/pre-router.js';
+import { RoutingStrategy } from './routers/base.js';
+import type { QueryComplexity } from './complexity.js';
+
+/**
+ * CascadeFlow - Transparent wrapper for LangChain chat models
+ *
+ * Preserves all LangChain model functionality while adding intelligent
+ * cascade logic for cost optimization.
+ *
+ * @example
+ * ```typescript
+ * const drafter = new ChatOpenAI({ model: 'gpt-4o-mini' });
+ * const verifier = new ChatOpenAI({ model: 'gpt-4o' });
+ *
+ * const cascade = new CascadeFlow({
+ * drafter,
+ * verifier,
+ * qualityThreshold: 0.7
+ * });
+ *
+ * const result = await cascade.invoke("What is TypeScript?");
+ * ```
+ */
+export class CascadeFlow extends BaseChatModel {
+ private config: Required> & { preRouter?: PreRouter };
+ public drafter: BaseChatModel;
+ public verifier: BaseChatModel;
+
+ // Store last cascade result for metadata
+ private lastCascadeResult?: CascadeResult;
+
+ // Store bind kwargs to merge during _generate
+ private bindKwargs: any = {};
+
+ // PreRouter for complexity-based routing
+ private preRouter?: PreRouter;
+
+ constructor(config: CascadeConfig, bindKwargs: any = {}) {
+ super({});
+
+ this.drafter = config.drafter;
+ this.verifier = config.verifier;
+ this.bindKwargs = bindKwargs;
+
+ // Set defaults
+ this.config = {
+ drafter: config.drafter,
+ verifier: config.verifier,
+ qualityThreshold: config.qualityThreshold ?? 0.7,
+ enableCostTracking: config.enableCostTracking ?? true,
+ costTrackingProvider: config.costTrackingProvider ?? 'langsmith',
+ qualityValidator: config.qualityValidator ?? calculateQuality,
+ enablePreRouter: config.enablePreRouter ?? false,
+ preRouter: config.preRouter,
+ cascadeComplexities: config.cascadeComplexities ?? ['trivial', 'simple', 'moderate'],
+ };
+
+ // Initialize PreRouter if enabled
+ if (this.config.enablePreRouter) {
+ this.preRouter = this.config.preRouter ?? new PreRouter({
+ cascadeComplexities: this.config.cascadeComplexities,
+ });
+ }
+
+ // Return a Proxy for method delegation
+ return new Proxy(this, {
+ get(target, prop, receiver) {
+ // Check if method exists on target (CascadeWrapper) first
+ if (prop in target || typeof prop === 'symbol') {
+ return Reflect.get(target, prop, receiver);
+ }
+
+ // Delegate to drafter for unknown methods/properties
+ const drafterValue = Reflect.get(target.drafter, prop);
+
+ // If it's a method, bind it to drafter
+ if (typeof drafterValue === 'function') {
+ return (...args: any[]) => drafterValue.apply(target.drafter, args);
+ }
+
+ return drafterValue;
+ },
+
+ set(target, prop, value, receiver) {
+ // Set on both drafter and verifier to keep them in sync
+ if (prop in target.drafter && prop in target.verifier) {
+ Reflect.set(target.drafter, prop, value);
+ Reflect.set(target.verifier, prop, value);
+ return true;
+ }
+
+ return Reflect.set(target, prop, value, receiver);
+ },
+ });
+ }
+
+ /**
+ * Required LangChain method - returns the LLM type identifier
+ */
+ _llmType(): string {
+ return 'cascadeflow';
+ }
+
+ /**
+ * Override invoke to add agent metadata to messages
+ * The agent role is stored in metadata instead of as a message role
+ */
+ override async invoke(
+ input: BaseMessage[] | string,
+ options?: any
+ ): Promise {
+ // Convert string input to HumanMessage (standard LangChain approach)
+ // We'll add agent metadata in the options instead
+ let processedInput: BaseMessage[];
+
+ if (typeof input === 'string') {
+ processedInput = [new HumanMessage({ content: input })];
+ } else if (Array.isArray(input)) {
+ processedInput = input;
+ } else {
+ // Single message object
+ processedInput = [input as BaseMessage];
+ }
+
+ // Add agent role to metadata in options
+ const enrichedOptions = {
+ ...options,
+ metadata: {
+ ...options?.metadata,
+ agent_role: 'cascade_agent',
+ },
+ };
+
+ return super.invoke(processedInput, enrichedOptions);
+ }
+
+ /**
+ * Core cascade generation logic
+ * Implements the speculative execution pattern
+ */
+ async _generate(
+ messages: BaseMessage[],
+ options: this['ParsedCallOptions'],
+ runManager?: CallbackManagerForLLMRun
+ ): Promise {
+ const startTime = Date.now();
+
+ // Merge bind kwargs with options
+ const mergedOptions = { ...this.bindKwargs, ...options };
+
+ // STEP 0: PreRouter - Check if we should bypass cascade
+ let useCascade = true;
+ if (this.preRouter) {
+ // Extract query text from messages
+ const queryText = messages
+ .map((msg) => {
+ if (typeof msg.content === 'string') {
+ return msg.content;
+ } else if (Array.isArray(msg.content)) {
+ return msg.content
+ .map((part: any) => (typeof part === 'string' ? part : part.text || ''))
+ .join(' ');
+ }
+ return '';
+ })
+ .join('\n');
+
+ // Route based on complexity
+ const routingDecision = await this.preRouter.route(queryText);
+ useCascade = routingDecision.strategy === RoutingStrategy.CASCADE;
+
+ // If direct routing, skip drafter and go straight to verifier
+ if (!useCascade) {
+ const verifierMessage = await this.verifier.invoke(messages, mergedOptions);
+ const verifierResult: ChatResult = {
+ generations: [
+ {
+ text: typeof verifierMessage.content === 'string'
+ ? verifierMessage.content
+ : JSON.stringify(verifierMessage.content),
+ message: verifierMessage,
+ },
+ ],
+ llmOutput: (verifierMessage as any).response_metadata || {},
+ };
+
+ const latencyMs = Date.now() - startTime;
+ const verifierModelName = (this.verifier as any).model || (this.verifier as any).modelName ||
+ (typeof this.verifier._llmType === 'function' ? this.verifier._llmType() : 'unknown');
+
+ // Store cascade result (direct to verifier)
+ this.lastCascadeResult = {
+ content: verifierResult.generations[0].text,
+ modelUsed: 'verifier',
+ drafterQuality: undefined,
+ accepted: false,
+ drafterCost: 0,
+ verifierCost: 0, // LangSmith will calculate this
+ totalCost: 0,
+ savingsPercentage: 0,
+ latencyMs,
+ };
+
+ // Inject metadata if cost tracking enabled
+ if (this.config.enableCostTracking) {
+ try {
+ const metadata = {
+ cascade_decision: 'direct',
+ model_used: 'verifier',
+ routing_reason: routingDecision.reason,
+ complexity: routingDecision.metadata.complexity,
+ };
+
+ verifierResult.llmOutput = {
+ ...verifierResult.llmOutput,
+ cascade: metadata,
+ };
+
+ if (verifierResult.generations[0]?.message) {
+ const message = verifierResult.generations[0].message;
+ if ('response_metadata' in message) {
+ (message as any).response_metadata = {
+ ...(message as any).response_metadata,
+ cascade: metadata,
+ };
+ }
+ (message as any).llmOutput = {
+ ...(message as any).llmOutput,
+ cascade: metadata,
+ };
+ }
+ } catch (error) {
+ console.warn('Failed to inject cascade metadata:', error);
+ }
+ }
+
+ return verifierResult;
+ }
+ }
+
+ // STEP 1: Execute drafter (cheap, fast model)
+ // Use invoke() to ensure LangSmith captures the model trace
+ const drafterMessage = await this.drafter.invoke(messages, mergedOptions);
+ const drafterResult: ChatResult = {
+ generations: [
+ {
+ text: typeof drafterMessage.content === 'string'
+ ? drafterMessage.content
+ : JSON.stringify(drafterMessage.content),
+ message: drafterMessage,
+ },
+ ],
+ llmOutput: (drafterMessage as any).response_metadata || {},
+ };
+
+ const drafterQuality = this.config.qualityValidator
+ ? await this.config.qualityValidator(drafterResult)
+ : calculateQuality(drafterResult);
+
+ // STEP 2: Check quality threshold
+ const accepted = drafterQuality >= this.config.qualityThreshold;
+
+ let finalResult: ChatResult;
+ let verifierResult: ChatResult | null = null;
+
+ if (accepted) {
+ // Quality is sufficient - use drafter response
+ finalResult = drafterResult;
+ } else {
+ // Quality insufficient - execute verifier (expensive, accurate model)
+ // Use invoke() to ensure LangSmith captures the model trace
+ const verifierMessage = await this.verifier.invoke(messages, mergedOptions);
+ const vResult: ChatResult = {
+ generations: [
+ {
+ text: typeof verifierMessage.content === 'string'
+ ? verifierMessage.content
+ : JSON.stringify(verifierMessage.content),
+ message: verifierMessage,
+ },
+ ],
+ llmOutput: (verifierMessage as any).response_metadata || {},
+ };
+ verifierResult = vResult;
+ finalResult = vResult;
+ }
+
+ // STEP 3: Calculate costs and metadata
+ const latencyMs = Date.now() - startTime;
+ const drafterModelName = (this.drafter as any).model || (this.drafter as any).modelName ||
+ (typeof this.drafter._llmType === 'function' ? this.drafter._llmType() : 'unknown');
+ const verifierModelName = (this.verifier as any).model || (this.verifier as any).modelName ||
+ (typeof this.verifier._llmType === 'function' ? this.verifier._llmType() : 'unknown');
+ const costMetadata = createCostMetadata(
+ drafterResult,
+ verifierResult,
+ drafterModelName,
+ verifierModelName,
+ accepted,
+ drafterQuality,
+ this.config.costTrackingProvider
+ );
+
+ // Store cascade result
+ this.lastCascadeResult = {
+ content: finalResult.generations[0].text,
+ modelUsed: accepted ? 'drafter' : 'verifier',
+ drafterQuality,
+ accepted,
+ drafterCost: costMetadata.drafterCost,
+ verifierCost: costMetadata.verifierCost,
+ totalCost: costMetadata.totalCost,
+ savingsPercentage: costMetadata.savingsPercentage,
+ latencyMs,
+ };
+
+ // STEP 4: Inject cost metadata into llmOutput (if enabled)
+ // LangSmith will automatically capture this metadata in traces
+ if (this.config.enableCostTracking) {
+ try {
+ // Inject into llmOutput
+ finalResult.llmOutput = {
+ ...finalResult.llmOutput,
+ cascade: costMetadata,
+ };
+
+ // Also inject into message's response_metadata for invoke() results
+ if (finalResult.generations[0]?.message) {
+ const message = finalResult.generations[0].message;
+ if ('response_metadata' in message) {
+ (message as any).response_metadata = {
+ ...(message as any).response_metadata,
+ cascade: costMetadata,
+ };
+ }
+ // Also set as llmOutput property for backward compatibility
+ (message as any).llmOutput = {
+ ...(message as any).llmOutput,
+ cascade: costMetadata,
+ };
+ }
+ } catch (error) {
+ console.warn('Failed to inject cascade metadata:', error);
+ }
+ }
+
+ return finalResult;
+ }
+
+ /**
+ * Get the last cascade execution result
+ */
+ getLastCascadeResult(): CascadeResult | undefined {
+ return this.lastCascadeResult;
+ }
+
+ /**
+ * Stream responses with optimistic drafter execution
+ *
+ * Uses the proven cascade streaming pattern:
+ * 1. Stream drafter optimistically (user sees real-time output)
+ * 2. Collect chunks and check quality after completion
+ * 3. If quality insufficient: show switch message + stream verifier
+ *
+ * @param messages - Input messages
+ * @param options - Streaming options
+ * @returns AsyncGenerator yielding chunks
+ */
+ override async *_streamResponseChunks(
+ messages: BaseMessage[],
+ options: this['ParsedCallOptions'],
+ runManager?: CallbackManagerForLLMRun
+ ): AsyncGenerator {
+ const startTime = Date.now();
+
+ // Merge bind kwargs with options
+ const mergedOptions = { ...this.bindKwargs, ...options };
+
+ // STEP 0: PreRouter - Check if we should bypass cascade
+ let useCascade = true;
+ if (this.preRouter) {
+ const queryText = messages
+ .map((msg) => {
+ if (typeof msg.content === 'string') {
+ return msg.content;
+ } else if (Array.isArray(msg.content)) {
+ return msg.content
+ .map((part: any) => (typeof part === 'string' ? part : part.text || ''))
+ .join(' ');
+ }
+ return '';
+ })
+ .join('\n');
+
+ const routingDecision = await this.preRouter.route(queryText);
+ useCascade = routingDecision.strategy === RoutingStrategy.CASCADE;
+
+ // If direct routing, stream verifier only
+ if (!useCascade) {
+ for await (const chunk of this.verifier._streamResponseChunks(
+ messages,
+ mergedOptions,
+ runManager
+ )) {
+ yield chunk;
+ }
+ return;
+ }
+ }
+
+ // STEP 1: Stream drafter optimistically
+ const drafterChunks: ChatGenerationChunk[] = [];
+ let drafterContent = '';
+
+ // Stream from drafter in real-time
+ for await (const chunk of this.drafter._streamResponseChunks(
+ messages,
+ mergedOptions,
+ runManager
+ )) {
+ drafterChunks.push(chunk);
+
+ // Extract text content from chunk
+ const chunkText = typeof chunk.message.content === 'string'
+ ? chunk.message.content
+ : '';
+ drafterContent += chunkText;
+
+ // Yield chunk immediately for real-time streaming
+ yield chunk;
+ }
+
+ // STEP 2: Quality check after drafter completes
+ const drafterResult: ChatResult = {
+ generations: drafterChunks.map(chunk => ({
+ text: typeof chunk.message.content === 'string' ? chunk.message.content : '',
+ message: chunk.message,
+ })),
+ llmOutput: {},
+ };
+
+ const drafterQuality = this.config.qualityValidator
+ ? await this.config.qualityValidator(drafterResult)
+ : calculateQuality(drafterResult);
+
+ const accepted = drafterQuality >= this.config.qualityThreshold;
+
+ // STEP 3: If quality insufficient, cascade to verifier
+ if (!accepted) {
+ // Import ChatGenerationChunk and AIMessageChunk for switch message
+ const { ChatGenerationChunk } = await import('@langchain/core/outputs');
+ const { AIMessageChunk } = await import('@langchain/core/messages');
+
+ // Emit switch notification
+ const verifierModelName = (this.verifier as any).model ||
+ (this.verifier as any).modelName || 'verifier';
+ const switchMessage = `\n\nโคด Cascading to ${verifierModelName} (quality: ${drafterQuality.toFixed(2)} < ${this.config.qualityThreshold})\n\n`;
+
+ yield new ChatGenerationChunk({
+ text: switchMessage,
+ message: new AIMessageChunk({ content: switchMessage }),
+ });
+
+ // Stream from verifier
+ for await (const chunk of this.verifier._streamResponseChunks(
+ messages,
+ mergedOptions,
+ runManager
+ )) {
+ yield chunk;
+ }
+ }
+
+ // Store cascade result (simplified for streaming)
+ const latencyMs = Date.now() - startTime;
+ this.lastCascadeResult = {
+ content: drafterContent,
+ modelUsed: accepted ? 'drafter' : 'verifier',
+ drafterQuality,
+ accepted,
+ drafterCost: 0,
+ verifierCost: 0,
+ totalCost: 0,
+ savingsPercentage: accepted ? 50 : 0,
+ latencyMs,
+ };
+ }
+
+ /**
+ * Handle chainable methods - bind()
+ * Creates a new CascadeFlow with bound parameters
+ */
+ override bind(kwargs: any): CascadeFlow {
+ // Merge new kwargs with existing ones
+ const mergedKwargs = { ...this.bindKwargs, ...kwargs };
+
+ return new CascadeFlow(
+ {
+ drafter: this.drafter,
+ verifier: this.verifier,
+ qualityThreshold: this.config.qualityThreshold,
+ enableCostTracking: this.config.enableCostTracking,
+ costTrackingProvider: this.config.costTrackingProvider,
+ qualityValidator: this.config.qualityValidator,
+ enablePreRouter: this.config.enablePreRouter,
+ preRouter: this.config.preRouter,
+ cascadeComplexities: this.config.cascadeComplexities,
+ },
+ mergedKwargs
+ );
+ }
+
+ /**
+ * Handle chainable methods - bindTools()
+ * Creates a new CascadeFlow with bound tools
+ */
+ bindTools(tools: any[], kwargs?: any): any {
+ if (typeof (this.drafter as any).bindTools !== 'function') {
+ throw new Error('Drafter model does not support bindTools()');
+ }
+
+ const boundDrafter = (this.drafter as any).bindTools(tools, kwargs);
+ const boundVerifier = (this.verifier as any).bindTools(tools, kwargs);
+
+ return new CascadeFlow({
+ drafter: boundDrafter,
+ verifier: boundVerifier,
+ qualityThreshold: this.config.qualityThreshold,
+ enableCostTracking: this.config.enableCostTracking,
+ costTrackingProvider: this.config.costTrackingProvider,
+ qualityValidator: this.config.qualityValidator,
+ enablePreRouter: this.config.enablePreRouter,
+ preRouter: this.config.preRouter,
+ cascadeComplexities: this.config.cascadeComplexities,
+ });
+ }
+
+ /**
+ * Handle chainable methods - withStructuredOutput()
+ * Creates a new CascadeFlow with structured output
+ */
+ withStructuredOutput(outputSchema: any, config?: any): any {
+ if (typeof (this.drafter as any).withStructuredOutput !== 'function') {
+ throw new Error('Drafter model does not support withStructuredOutput()');
+ }
+
+ const boundDrafter = (this.drafter as any).withStructuredOutput(outputSchema, config);
+ const boundVerifier = (this.verifier as any).withStructuredOutput(outputSchema, config);
+
+ return new CascadeFlow({
+ drafter: boundDrafter,
+ verifier: boundVerifier,
+ qualityThreshold: this.config.qualityThreshold,
+ enableCostTracking: this.config.enableCostTracking,
+ costTrackingProvider: this.config.costTrackingProvider,
+ qualityValidator: this.config.qualityValidator,
+ enablePreRouter: this.config.enablePreRouter,
+ preRouter: this.config.preRouter,
+ cascadeComplexities: this.config.cascadeComplexities,
+ });
+ }
+}
diff --git a/packages/langchain-cascadeflow/tsconfig.json b/packages/langchain-cascadeflow/tsconfig.json
new file mode 100644
index 00000000..f6a2d1ba
--- /dev/null
+++ b/packages/langchain-cascadeflow/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "lib": ["ES2020"],
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "allowJs": false,
+ "checkJs": false,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "removeComments": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "strict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist", "tests"]
+}
diff --git a/packages/langchain-cascadeflow/vitest.config.ts b/packages/langchain-cascadeflow/vitest.config.ts
new file mode 100644
index 00000000..51b8fab9
--- /dev/null
+++ b/packages/langchain-cascadeflow/vitest.config.ts
@@ -0,0 +1,20 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'node',
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ exclude: [
+ 'node_modules/',
+ 'dist/',
+ 'examples/',
+ '**/*.d.ts',
+ '**/*.config.*',
+ '**/types.ts',
+ ],
+ },
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8e2a5ce9..4ead3f6f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -84,7 +84,7 @@ importers:
version: 0.5.0
openai:
specifier: ^4.73.1
- version: 4.104.0(zod@4.1.12)
+ version: 4.104.0(zod@3.25.67)
devDependencies:
tsx:
specifier: ^4.7.0
@@ -96,11 +96,11 @@ importers:
packages/integrations/n8n:
dependencies:
'@cascadeflow/core':
- specifier: ^5.0.3
- version: 5.0.3
+ specifier: ^0.5.0
+ version: link:../../core
'@langchain/core':
specifier: ^0.3.0
- version: 0.3.79
+ version: 0.3.79(openai@4.104.0)
devDependencies:
'@types/node':
specifier: ^20.10.0
@@ -127,6 +127,55 @@ importers:
specifier: ^5.1.6
version: 5.9.3
+ packages/langchain-cascadeflow:
+ dependencies:
+ '@cascadeflow/core':
+ specifier: workspace:^
+ version: link:../core
+ '@cascadeflow/ml':
+ specifier: workspace:^
+ version: link:../ml
+ '@langchain/anthropic':
+ specifier: ^1.0.1
+ version: 1.0.1(@langchain/core@0.3.79)(zod@3.25.67)
+ '@langchain/google-genai':
+ specifier: ^1.0.1
+ version: 1.0.1(@langchain/core@0.3.79)
+ devDependencies:
+ '@langchain/core':
+ specifier: ^0.3.24
+ version: 0.3.79(openai@4.104.0)
+ '@langchain/openai':
+ specifier: ^0.3.17
+ version: 0.3.17(@langchain/core@0.3.79)
+ '@types/node':
+ specifier: ^20.10.0
+ version: 20.19.23
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.15.0
+ version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^6.15.0
+ version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ eslint:
+ specifier: ^8.55.0
+ version: 8.57.1
+ langchain:
+ specifier: ^0.3.13
+ version: 0.3.36(@langchain/anthropic@1.0.1)(@langchain/core@0.3.79)(@langchain/google-genai@1.0.1)(openai@4.104.0)
+ openai:
+ specifier: ^4.73.1
+ version: 4.104.0(zod@3.25.67)
+ tsup:
+ specifier: ^8.0.1
+ version: 8.5.0(tsx@4.20.6)(typescript@5.9.3)
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+ vitest:
+ specifier: ^1.0.4
+ version: 1.6.1(@types/node@20.19.23)
+
packages/ml:
dependencies:
'@xenova/transformers':
@@ -170,48 +219,24 @@ packages:
transitivePeerDependencies:
- encoding
- /@cascadeflow/core@5.0.3:
- resolution: {integrity: sha512-1pnZydT01b7fbPpaHpu96ayMVzp8CosDYoG2pcGwroskR5TClaIbVdRJ7mMckj0LT2XNQDQvihrQoaFu6CKHRQ==}
- engines: {node: '>=18.0.0'}
+ /@anthropic-ai/sdk@0.65.0(zod@3.25.67):
+ resolution: {integrity: sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw==}
+ hasBin: true
peerDependencies:
- '@anthropic-ai/sdk': ^0.30.0
- '@huggingface/inference': ^2.8.0
- '@xenova/transformers': ^2.17.2
- groq-sdk: ^0.5.0
- openai: ^4.0.0
+ zod: ^3.25.0 || ^4.0.0
peerDependenciesMeta:
- '@anthropic-ai/sdk':
- optional: true
- '@huggingface/inference':
- optional: true
- '@xenova/transformers':
- optional: true
- groq-sdk:
- optional: true
- openai:
+ zod:
optional: true
dependencies:
- '@cascadeflow/ml': 5.0.3
- transitivePeerDependencies:
- - bare-abort-controller
- - bare-buffer
- - react-native-b4a
- dev: false
+ json-schema-to-ts: 3.1.1
+ zod: 3.25.67
- /@cascadeflow/ml@5.0.3:
- resolution: {integrity: sha512-Nj0CZrrZI44F7CpF7PbsPmx711Tfku2oLWvhvny/cCKCjwguw1/EdBXDkSOhhT3bnjVmsnX96ZfhLXtDxtc/Gw==}
- engines: {node: '>=18.0.0'}
- dependencies:
- '@xenova/transformers': 2.17.2
- transitivePeerDependencies:
- - bare-abort-controller
- - bare-buffer
- - react-native-b4a
- dev: false
+ /@babel/runtime@7.28.4:
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
/@cfworker/json-schema@4.1.1:
resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
- dev: false
/@esbuild/aix-ppc64@0.21.5:
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
@@ -675,6 +700,10 @@ packages:
'@shikijs/vscode-textmate': 10.0.2
dev: true
+ /@google/generative-ai@0.24.1:
+ resolution: {integrity: sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==}
+ engines: {node: '>=18.0.0'}
+
/@huggingface/inference@2.8.1:
resolution: {integrity: sha512-EfsNtY9OR6JCNaUa5bZu2mrs48iqeTz0Gutwf+fU0Kypx33xFQB4DKMhp8u4Ee6qVbLbNWvTHuWwlppLQl4p4Q==}
engines: {node: '>=18'}
@@ -755,7 +784,18 @@ packages:
'@jridgewell/sourcemap-codec': 1.5.5
dev: true
- /@langchain/core@0.3.79:
+ /@langchain/anthropic@1.0.1(@langchain/core@0.3.79)(zod@3.25.67):
+ resolution: {integrity: sha512-yVKePAT+nNHtybyyPlWqiq6lqcoDlIuMgL9B4WMEU5gbmzL170iodiqcgcZNFQLOC1V2wCOzywq6Zr0kB24AFg==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ '@langchain/core': ^1.0.0
+ dependencies:
+ '@anthropic-ai/sdk': 0.65.0(zod@3.25.67)
+ '@langchain/core': 0.3.79(openai@4.104.0)
+ transitivePeerDependencies:
+ - zod
+
+ /@langchain/core@0.3.79(openai@4.104.0):
resolution: {integrity: sha512-ZLAs5YMM5N2UXN3kExMglltJrKKoW7hs3KMZFlXUnD7a5DFKBYxPFMeXA4rT+uvTxuJRZPCYX0JKI5BhyAWx4A==}
engines: {node: '>=18'}
dependencies:
@@ -764,7 +804,7 @@ packages:
camelcase: 6.3.0
decamelize: 1.2.0
js-tiktoken: 1.0.21
- langsmith: 0.3.78
+ langsmith: 0.3.78(openai@4.104.0)
mustache: 4.2.0
p-queue: 6.6.2
p-retry: 4.6.2
@@ -776,7 +816,42 @@ packages:
- '@opentelemetry/exporter-trace-otlp-proto'
- '@opentelemetry/sdk-trace-base'
- openai
- dev: false
+
+ /@langchain/google-genai@1.0.1(@langchain/core@0.3.79):
+ resolution: {integrity: sha512-a9Bzaswp1P+eA2V8hAWSBypqjxmH+/zhOY1TBdalQuPQBTRH35jBMVgX3CTTAheAzBUGQtlDD4/dR9tyemDbhw==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ '@langchain/core': ^1.0.0
+ dependencies:
+ '@google/generative-ai': 0.24.1
+ '@langchain/core': 0.3.79(openai@4.104.0)
+ uuid: 11.1.0
+
+ /@langchain/openai@0.3.17(@langchain/core@0.3.79):
+ resolution: {integrity: sha512-uw4po32OKptVjq+CYHrumgbfh4NuD7LqyE+ZgqY9I/LrLc6bHLMc+sisHmI17vgek0K/yqtarI0alPJbzrwyag==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@langchain/core': '>=0.3.29 <0.4.0'
+ dependencies:
+ '@langchain/core': 0.3.79(openai@4.104.0)
+ js-tiktoken: 1.0.21
+ openai: 4.104.0(zod@3.25.67)
+ zod: 3.25.67
+ zod-to-json-schema: 3.24.6(zod@3.25.67)
+ transitivePeerDependencies:
+ - encoding
+ - ws
+ dev: true
+
+ /@langchain/textsplitters@0.1.0(@langchain/core@0.3.79):
+ resolution: {integrity: sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@langchain/core': '>=0.2.21 <0.4.0'
+ dependencies:
+ '@langchain/core': 0.3.79(openai@4.104.0)
+ js-tiktoken: 1.0.21
+ dev: true
/@n8n/errors@0.5.0:
resolution: {integrity: sha512-0Vk1Eb3Uor+zeF/WVnuhFgJc51wEBTZNBlVQy3mvyr3sGmW86bP1jA7wmRsd0DZbswPwN0vNOl/TmkDTEopOtQ==}
@@ -1163,7 +1238,6 @@ packages:
/@types/retry@0.12.0:
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
- dev: false
/@types/semver@7.7.1:
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
@@ -1196,7 +1270,6 @@ packages:
/@types/uuid@10.0.0:
resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
- dev: false
/@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3):
resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
@@ -1760,7 +1833,6 @@ packages:
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
- dev: false
/base@0.11.2:
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
@@ -1947,7 +2019,6 @@ packages:
/camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
- dev: false
/chai@4.5.0:
resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
@@ -2152,7 +2223,6 @@ packages:
resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==}
dependencies:
simple-wcswidth: 1.1.2
- dev: false
/content-disposition@1.0.0:
resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==}
@@ -2727,7 +2797,6 @@ packages:
/eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
- dev: false
/events-universal@1.0.1:
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
@@ -3852,7 +3921,6 @@ packages:
resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==}
dependencies:
base64-js: 1.5.1
- dev: false
/js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
@@ -3869,6 +3937,13 @@ packages:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
dev: true
+ /json-schema-to-ts@3.1.1:
+ resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
+ engines: {node: '>=16'}
+ dependencies:
+ '@babel/runtime': 7.28.4
+ ts-algebra: 2.0.0
+
/json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
dev: true
@@ -3877,6 +3952,11 @@ packages:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true
+ /jsonpointer@5.0.1:
+ resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/jssha@3.3.1:
resolution: {integrity: sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==}
dev: true
@@ -3915,7 +3995,88 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /langsmith@0.3.78:
+ /langchain@0.3.36(@langchain/anthropic@1.0.1)(@langchain/core@0.3.79)(@langchain/google-genai@1.0.1)(openai@4.104.0):
+ resolution: {integrity: sha512-PqC19KChFF0QlTtYDFgfEbIg+SCnCXox29G8tY62QWfj9bOW7ew2kgWmPw5qoHLOTKOdQPvXET20/1Pdq8vAtQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@langchain/anthropic': '*'
+ '@langchain/aws': '*'
+ '@langchain/cerebras': '*'
+ '@langchain/cohere': '*'
+ '@langchain/core': '>=0.3.58 <0.4.0'
+ '@langchain/deepseek': '*'
+ '@langchain/google-genai': '*'
+ '@langchain/google-vertexai': '*'
+ '@langchain/google-vertexai-web': '*'
+ '@langchain/groq': '*'
+ '@langchain/mistralai': '*'
+ '@langchain/ollama': '*'
+ '@langchain/xai': '*'
+ axios: '*'
+ cheerio: '*'
+ handlebars: ^4.7.8
+ peggy: ^3.0.2
+ typeorm: '*'
+ peerDependenciesMeta:
+ '@langchain/anthropic':
+ optional: true
+ '@langchain/aws':
+ optional: true
+ '@langchain/cerebras':
+ optional: true
+ '@langchain/cohere':
+ optional: true
+ '@langchain/deepseek':
+ optional: true
+ '@langchain/google-genai':
+ optional: true
+ '@langchain/google-vertexai':
+ optional: true
+ '@langchain/google-vertexai-web':
+ optional: true
+ '@langchain/groq':
+ optional: true
+ '@langchain/mistralai':
+ optional: true
+ '@langchain/ollama':
+ optional: true
+ '@langchain/xai':
+ optional: true
+ axios:
+ optional: true
+ cheerio:
+ optional: true
+ handlebars:
+ optional: true
+ peggy:
+ optional: true
+ typeorm:
+ optional: true
+ dependencies:
+ '@langchain/anthropic': 1.0.1(@langchain/core@0.3.79)(zod@3.25.67)
+ '@langchain/core': 0.3.79(openai@4.104.0)
+ '@langchain/google-genai': 1.0.1(@langchain/core@0.3.79)
+ '@langchain/openai': 0.3.17(@langchain/core@0.3.79)
+ '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.79)
+ js-tiktoken: 1.0.21
+ js-yaml: 4.1.0
+ jsonpointer: 5.0.1
+ langsmith: 0.3.78(openai@4.104.0)
+ openapi-types: 12.1.3
+ p-retry: 4.6.2
+ uuid: 10.0.0
+ yaml: 2.8.1
+ zod: 3.25.67
+ transitivePeerDependencies:
+ - '@opentelemetry/api'
+ - '@opentelemetry/exporter-trace-otlp-proto'
+ - '@opentelemetry/sdk-trace-base'
+ - encoding
+ - openai
+ - ws
+ dev: true
+
+ /langsmith@0.3.78(openai@4.104.0):
resolution: {integrity: sha512-PVrog/DiTsiyOQ38GeZEIVadgk55/dfE3axagQksT3dt6KhFuRxhNaZrC0rp3dNW9RQJCm/c3tn+PiybwQNY0Q==}
peerDependencies:
'@opentelemetry/api': '*'
@@ -3935,11 +4096,11 @@ packages:
'@types/uuid': 10.0.0
chalk: 4.1.2
console-table-printer: 2.15.0
+ openai: 4.104.0(zod@3.25.67)
p-queue: 6.6.2
p-retry: 4.6.2
semver: 7.7.3
uuid: 10.0.0
- dev: false
/last-run@1.1.1:
resolution: {integrity: sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==}
@@ -4286,7 +4447,6 @@ packages:
/mustache@4.2.0:
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
hasBin: true
- dev: false
/mute-stdout@1.0.1:
resolution: {integrity: sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==}
@@ -4578,6 +4738,29 @@ packages:
platform: 1.3.6
dev: false
+ /openai@4.104.0(zod@3.25.67):
+ resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==}
+ hasBin: true
+ peerDependencies:
+ ws: ^8.18.0
+ zod: ^3.23.8
+ peerDependenciesMeta:
+ ws:
+ optional: true
+ zod:
+ optional: true
+ dependencies:
+ '@types/node': 18.19.130
+ '@types/node-fetch': 2.6.13
+ abort-controller: 3.0.0
+ agentkeepalive: 4.6.0
+ form-data-encoder: 1.7.2
+ formdata-node: 4.4.1
+ node-fetch: 2.7.0
+ zod: 3.25.67
+ transitivePeerDependencies:
+ - encoding
+
/openai@4.104.0(zod@4.1.12):
resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==}
hasBin: true
@@ -4600,6 +4783,11 @@ packages:
zod: 4.1.12
transitivePeerDependencies:
- encoding
+ dev: true
+
+ /openapi-types@12.1.3:
+ resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
+ dev: true
/optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
@@ -4629,7 +4817,6 @@ packages:
/p-finally@1.0.0:
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
engines: {node: '>=4'}
- dev: false
/p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
@@ -4658,7 +4845,6 @@ packages:
dependencies:
eventemitter3: 4.0.7
p-timeout: 3.2.0
- dev: false
/p-retry@4.6.2:
resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
@@ -4666,14 +4852,12 @@ packages:
dependencies:
'@types/retry': 0.12.0
retry: 0.13.1
- dev: false
/p-timeout@3.2.0:
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
engines: {node: '>=8'}
dependencies:
p-finally: 1.0.0
- dev: false
/package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
@@ -5241,7 +5425,6 @@ packages:
/retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
engines: {node: '>= 4'}
- dev: false
/reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
@@ -5522,7 +5705,6 @@ packages:
/simple-wcswidth@1.1.2:
resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==}
- dev: false
/slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
@@ -5991,6 +6173,9 @@ packages:
hasBin: true
dev: true
+ /ts-algebra@2.0.0:
+ resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
+
/ts-api-utils@1.4.3(typescript@5.9.3):
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'}
@@ -6296,7 +6481,10 @@ packages:
/uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
- dev: false
+
+ /uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
/v8flags@3.2.0:
resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==}
@@ -6684,10 +6872,10 @@ packages:
zod: ^3.24.1
dependencies:
zod: 3.25.67
- dev: false
/zod@3.25.67:
resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
/zod@4.1.12:
resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==}
+ dev: true