@natyapp/langchain
v1.3.1
Published
TypeScript client for Naty LangChain API with async file uploads, real-time progress tracking, and Chat support
Readme
Naty LangChain Client
TypeScript client for the Naty LangChain API - Updated for OpenAPI 3.1.0 specification with full support for RAG, Chat, and File Upload features.
🚀 New Features
V2 API (latest — recommended)
- V2 Query API (
/v2/query): Unified endpoint supporting 5 pipeline modes (rag, direct, chat, multi_hop, summary), streaming SSE, multimodal (images), and per-request temperature override - V2 Config API (
/v2/config): Read and update per-tenant LLM and pipeline configuration at runtime - V2 Upload API (
/v2/documents/upload): Single unified upload endpoint supporting all file types (PDF, DOCX, XLSX, CSV, TXT, MD)
V1 API (stable)
- Chat API with Streaming: Professional chat with hybrid RAG (history + vector search)
- File Upload APIs: Upload and process Excel, PDF, Word, and CSV files
- Health Check: Monitor system status and dependencies
- Enhanced Types: Comprehensive TypeScript interfaces for all endpoints
- Streaming Support: Real-time responses with Server-Sent Events (SSE)
Installation
npm install @natyapp/langchain
# or
yarn add @natyapp/langchainQuick Start
Initialize Client
import { NatyLangChainClient } from '@natyapp/langchain';
const client = new NatyLangChainClient({
baseURL: 'http://localhost:8000',
apiKey: 'nlck-key_prod_1', // X-API-Key authentication
tenantId: 'demo-tenant', // X-Tenant-ID header (for multi-tenancy)
requestId: 'custom-request-id', // Optional: Custom X-Request-ID
});Basic QA (V1)
const response = await client.qa.ask({
question: 'Como funciona o RAG?',
k: 3,
});
console.log(response.answer);V2 Query — Recommended
// RAG mode (default): retrieves from vector store + generates answer
const response = await client.v2.query({
question: 'Quais são as políticas de férias?',
mode: 'rag',
});
console.log(response.answer);
console.log(response.sources); // [{file_name, chunk_index, ...}]
// Direct mode: sends question straight to LLM (no retrieval)
const direct = await client.v2.query({
question: 'Escreva um slogan criativo para nosso produto.',
mode: 'direct',
temperature: 1.2, // override per request (0.0–2.0)
});
// Chat mode: multi-turn conversation with session history
const chat = await client.v2.query({
question: 'O que ficou pendente na reunião anterior?',
mode: 'chat',
session_id: 'session-user-001',
});
// Streaming (any mode)
const stream = await client.v2.stream({
question: 'Explique a arquitetura do sistema.',
mode: 'rag',
});
for await (const chunk of stream) {
if (chunk.delta) process.stdout.write(chunk.delta);
if (chunk.done) console.log('\nFontes:', chunk.sources);
}🆕 V2 API
V2 Query API
import { V2QueryRequest, V2QueryResponse } from '@natyapp/langchain';
// All available options
const request: V2QueryRequest = {
question: 'Quais documentos mencionam LGPD?',
mode: 'rag', // 'rag' | 'direct' | 'chat' | 'multi_hop' | 'summary'
k: 6, // number of documents to retrieve (default: 4)
filters: { category: 'legal' }, // vector store filters (optional)
session_id: 'sess-001', // required for mode='chat'
stream: false, // set true to receive SSE stream
temperature: 0.3, // 0.0–2.0, overrides tenant config (optional)
images: [ // multimodal — supported in rag, direct, chat (optional)
{ url: 'https://example.com/diagram.png', mime_type: 'image/png' }
],
};
// Non-streaming
const response: V2QueryResponse = await client.v2.query(request);
// response.answer — generated text
// response.mode — pipeline mode used
// response.sources — [{file_name, chunk_index, ...}]
// Streaming (SSE async iterator)
for await (const frame of client.v2.stream(request)) {
if (frame.delta) process.stdout.write(frame.delta);
if (frame.done) console.log('\nDone. Sources:', frame.sources);
}
// Override tenant ID per request
const resp = await client.v2.query(request, 'other-tenant-id');Query Modes
| Mode | Description | Use case |
|------|-------------|----------|
| rag | Retrieval-Augmented Generation | Most document Q&A |
| direct | Direct LLM call (no retrieval) | Creative text, formatting |
| chat | Multi-turn conversation with history | Support, iterative analysis |
| multi_hop | Decomposes complex questions | Cross-document research |
| summary | Summarization of retrieved docs | Executive briefings |
Temperature Priority
request.temperature > tenant_config.temperature > global default (0.2)
V2 Config API
import { V2TenantConfig, V2ConfigUpdateRequest } from '@natyapp/langchain';
// Get current tenant configuration
const config: V2TenantConfig = await client.v2Config.get();
console.log(config.pipeline_default); // 'rag'
console.log(config.llm_model); // 'gpt-4o-mini'
console.log(config.temperature); // 0.2
// Update configuration (patch semantics — omitted fields are preserved)
const updated = await client.v2Config.update({
llm_model: 'gpt-4o',
temperature: 0.5,
reranker_enabled: true,
system_prompt_default: 'You are a helpful assistant. Answer in Portuguese.',
});
// Override tenant ID per request
const otherConfig = await client.v2Config.get('other-tenant-id');Available Configuration Fields
interface V2ConfigUpdateRequest {
pipeline_default?: 'rag' | 'direct' | 'chat' | 'multi_hop' | 'summary';
llm_provider?: 'openai' | 'ollama';
llm_model?: string; // e.g. 'gpt-4o', 'gpt-4o-mini', 'llama3'
max_tokens?: number; // max response tokens
temperature?: number; // 0.0 – 2.0
system_prompt_default?: string;
retriever_mode?: 'vector' | 'hybrid';
reranker_enabled?: boolean;
tools_enabled?: string[];
}V2 Upload API
import { V2UploadOptions, V2UploadResponse } from '@natyapp/langchain';
// Upload any supported file type (single unified endpoint)
const files = [pdfFile, excelFile, docxFile]; // File objects (browser) or Buffers (Node)
const options: V2UploadOptions = {
source: 'legal-docs', // tag for filtering later (optional)
chunk_size: 1000, // tokens per chunk (optional)
chunk_overlap: 200, // overlap between chunks (optional)
on_conflict: 'replace', // 'error' | 'replace' | 'version' (optional)
};
const result: V2UploadResponse = await client.v2Upload.upload(files, options);
console.log(result.job_id); // track processing progress
console.log(result.status_url); // URL to poll for status
// Override tenant ID
const res = await client.v2Upload.upload(files, options, 'other-tenant-id');Supported File Types
| Extension | Type |
|-----------|------|
| .pdf | PDF |
| .docx, .doc | Word |
| .xlsx | Excel |
| .csv | CSV |
| .txt | Plain text |
| .md | Markdown |
Track Upload Progress
After uploading, poll the job status endpoint:
// Using V1 Jobs API (also available for V2 uploads)
const status = await client.jobs.getStatus(result.job_id);
// status.state: 'pending' | 'processing' | 'completed' | 'failed'
// status.progress: 0–100💬 Chat API with Streaming
Professional Chat with Hybrid RAG
import { ChatMessage, ChatQARequest } from '@natyapp/langchain';
const chatHistory: ChatMessage[] = [
{ role: 'user', content: 'Olá!' },
{ role: 'assistant', content: 'Oi! Como posso ajudá-lo?' }
];
const request: ChatQARequest = {
question: 'Como funciona o sistema de RAG?',
system_prompt: 'Você é um especialista em IA',
chat_history: chatHistory,
rag_enabled: true,
k: 5,
max_history_messages: 10,
strict_context: true,
include_metadata: false
};
// Non-streaming response (NEW)
const response = await client.chat.chat(request);
console.log(response.answer);
console.log(`Used ${response.rag_documents_used} documents`);
// Streaming response
const stream = await client.chat.stream(request);
const reader = stream.getReader();
// Or use async iterator
for await (const chunk of client.chat.streamIterator(request)) {
if (chunk.type === 'content') {
console.log(chunk.data.text);
}
}📁 File Upload APIs
Upload Excel Files
import { ExcelUploadOptions } from '@natyapp/langchain';
const files = [excelFile1, excelFile2]; // File objects
const options: ExcelUploadOptions = {
chunk_size: 1000,
text_format: 'structured',
include_sheets: 'Sheet1,Sheet2',
max_rows: 1000
};
const result = await client.fileUpload.uploadExcel(files, options);
console.log(`Processed ${result.sheets_processed.length} sheets`);Preview Excel Content
const preview = await client.fileUpload.previewExcel(excelFile, {
max_rows: 10,
text_format: 'csv'
});Upload PDF Files
import { PDFUploadOptions } from '@natyapp/langchain';
const options: PDFUploadOptions = {
extract_by_page: true,
chunk_size: 800,
page_range_start: 1,
page_range_end: 10
};
const result = await client.fileUpload.uploadPDF(pdfFiles, options);Upload Word Documents
import { WordUploadOptions } from '@natyapp/langchain';
const options: WordUploadOptions = {
extract_by_paragraph: false,
include_tables: true,
include_headers_footers: true,
preserve_formatting: false
};
const result = await client.fileUpload.uploadWord(wordFiles, options);Upload CSV Files
import { CSVUploadOptions } from '@natyapp/langchain';
const options: CSVUploadOptions = {
delimiter: ',',
encoding: 'utf-8',
output_format: 'structured',
row_chunk_size: 100,
skip_empty_rows: true
};
const result = await client.fileUpload.uploadCSV(csvFiles, options);🏥 Health Check
const health = await client.health.check();
console.log(`Status: ${health.status}`);
console.log(`Checks:`, health.checks);⚡ Custom QA with Streaming
import { CustomQAStreamRequest } from '@natyapp/langchain';
const request: CustomQAStreamRequest = {
system_prompt: 'Responda como especialista técnico',
question: 'Explique machine learning',
contexts_text: 'Machine Learning é...', // Optional explicit context
strict_context: true,
k: 4
};
const stream = await client.customQA.stream(request);
// Process streaming response...Backward Compatibility
import { NatyClient } from '@natyapp/langchain';
// Legacy client still works (shows deprecation warning)
const legacyClient = new NatyClient({
baseURL: 'http://localhost:8000',
token: 'legacy-token', // Will be used as X-API-Key
tenantId: 'demo-tenant',
});cURL Equivalent
V2 Query (recommended)
await client.v2.query({ question: 'O que é RAG?', mode: 'rag' });curl -X POST http://localhost:8000/v2/query \
-H "Content-Type: application/json" \
-H "X-API-Key: nlck-key_prod_1" \
-H "X-Tenant-ID: demo-tenant" \
-d '{"question": "O que é RAG?", "mode": "rag"}'V2 Config
# GET
curl http://localhost:8000/v2/config \
-H "X-API-Key: nlck-key_prod_1" \
-H "X-Tenant-ID: demo-tenant"
# PUT
curl -X PUT http://localhost:8000/v2/config \
-H "Content-Type: application/json" \
-H "X-API-Key: nlck-key_prod_1" \
-H "X-Tenant-ID: demo-tenant" \
-d '{"temperature": 0.5, "llm_model": "gpt-4o"}'V1 QA (legacy)
The following client usage:
const client = new NatyLangChainClient({
baseURL: 'http://localhost:8000',
apiKey: 'nlck-key_prod_1',
tenantId: 'demo-tenant',
requestId: 'demo-request-id',
});
await client.qa.ask({ question: 'Olá?', k: 3 });Is equivalent to this cURL command:
curl -X POST http://localhost:8000/v1/qa \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: demo-tenant" \
-H "X-API-Key: nlck-key_prod_1" \
-H "X-Request-ID: demo-request-id" \
-d '{"question": "Olá?", "k": 3}'API Reference
Configuration
interface NatyLangChainClientConfig {
baseURL: string;
apiKey?: string; // API key for X-API-Key header
tenantId?: string; // Tenant ID for X-Tenant-ID header
requestId?: string; // Custom request ID (auto-generated if not provided)
}
// Backward compatibility
interface NatyClientConfig extends NatyLangChainClientConfig {
token?: string; // Legacy token (deprecated, use apiKey)
}Authentication & Headers
The client automatically manages the following headers:
Content-Type: application/json- Always includedX-API-Key- API key authentication (replaces Bearer tokens)X-Tenant-ID- Multi-tenant support (when tenantId provided)X-Request-ID- Request tracking (auto-generated UUID if not provided)
Available APIs
V2QueryAPI (client.v2)
client.v2.query(request: V2QueryRequest, tenantId?: string): Promise<V2QueryResponse>
client.v2.stream(request: V2QueryRequest, tenantId?: string): AsyncIterable<V2StreamFrame>V2ConfigAPI (client.v2Config)
client.v2Config.get(tenantId?: string): Promise<V2TenantConfig>
client.v2Config.update(config: V2ConfigUpdateRequest, tenantId?: string): Promise<V2TenantConfig>V2UploadAPI (client.v2Upload)
client.v2Upload.upload(files: File[], options?: V2UploadOptions, tenantId?: string): Promise<V2UploadResponse>V2 Types
type V2QueryMode = 'rag' | 'direct' | 'chat' | 'multi_hop' | 'summary';
interface V2QueryRequest {
question: string;
mode?: V2QueryMode; // default: 'rag'
k?: number; // default: 4
filters?: Record<string, unknown>;
session_id?: string; // required for chat mode
stream?: boolean;
temperature?: number; // 0.0–2.0, overrides tenant config
images?: ImagePayload[]; // for multimodal queries
}
interface ImagePayload {
url?: string; // public HTTPS URL
base64?: string; // base64-encoded image data
mime_type: string; // 'image/png', 'image/jpeg', etc.
}
interface V2QueryResponse {
answer: string;
mode: V2QueryMode;
sources?: Array<{ file_name: string; chunk_index: number; [key: string]: unknown }>;
session_id?: string;
}
interface V2TenantConfig {
tenant_id: string;
pipeline_default: V2QueryMode;
llm_provider: 'openai' | 'ollama';
llm_model: string;
max_tokens: number;
temperature: number; // 0.0–2.0
system_prompt_default?: string;
retriever_mode: 'vector' | 'hybrid';
reranker_enabled: boolean;
tools_enabled: string[];
}
interface V2UploadOptions {
source?: string;
chunk_size?: number;
chunk_overlap?: number;
on_conflict?: 'error' | 'replace' | 'version';
}
interface V2UploadResponse {
job_id: string;
status_url: string;
files_accepted: number;
}QA API (V1)
// Simple QA request
const qaResponse = await client.qa.ask({
question: 'What is LangChain?',
k: 4, // Optional: number of documents to retrieve
filters: { // Optional: vector store filters
category: { $eq: 'tech' }
}
});
// Override tenant ID per request
const response = await client.qa.ask(request, 'different-tenant-id');Custom QA API
// Custom QA with system prompt
const customResponse = await client.customQA.ask({
system_prompt: 'You are a helpful assistant that responds in Portuguese.',
question: 'Como posso ajudar você hoje?',
contexts_text: 'Optional explicit context', // Optional
filters: { tenant_id: { $eq: 'demo-tenant' } }, // Optional
k: 4, // Optional
strict_context: true, // Optional
});
// Streaming custom QA
const stream = await client.customQA.stream({
system_prompt: 'You are a helpful assistant.',
question: 'Tell me about AI',
strict_context: false,
});
// Process stream
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}Documents API
// Create documents
const createResponse = await client.documents.create([
{
text: 'Document content here',
metadata: {
title: 'Document Title',
category: 'technology',
source: 'internal',
},
},
]);
// Create documents in batch (async operation)
const batchResponse = await client.documents.createBatch([...documents]);
// List documents
const listResponse = await client.documents.list();
// Search documents
const searchResponse = await client.documents.search({
query: 'search term',
filters: { category: { $eq: 'technology' } },
k: 5,
});
// Delete documents by IDs
const deleteResponse = await client.documents.delete(['doc-id-1', 'doc-id-2']);Error Handling
The client uses enhanced error handling with NatyLangChainError:
import { NatyLangChainError } from '@natyapp/langchain';
try {
const response = await client.qa.ask({ question: 'Test' });
} catch (error) {
if (error instanceof NatyLangChainError) {
console.error('API Error:', error.message);
console.error('Status:', error.status); // HTTP status code
console.error('Code:', error.code); // Error code
console.error('Details:', error.details); // Additional error details
} else {
console.error('Unknown error:', error);
}
}Logging
The client automatically logs request headers (with API key masking for security):
// Example log output:
// Request headers: {
// 'X-Tenant-ID': 'demo-tenant',
// 'X-Request-ID': 'a90e-fb3b-4931-8dc4-51cc8adabf4e',
// 'X-API-Key': 'nlck...od_1' // Masked for security
// }Utility Functions
import { generateRequestId, maskApiKey } from '@natyapp/langchain';
// Generate UUID-like request ID
const requestId = generateRequestId();
console.log(requestId); // "a90e-fb3b-4931-8dc4-51cc8adabf4e"
// Mask API key for logging
const maskedKey = maskApiKey('nlck-key_prod_1234567890');
console.log(maskedKey); // "nlck...7890"Migration Guide
From v1.x to v2.x (V2 API)
Migrate from the V1 endpoints to the new unified V2 API:
Use
client.v2.query()instead ofclient.qa.ask():// Old (V1) const res = await client.qa.ask({ question: 'Q?', k: 4 }); // New (V2) const res = await client.v2.query({ question: 'Q?', mode: 'rag', k: 4 });Use
client.v2Upload.upload()for all file uploads:// Old (V1 — separate endpoints per type) await client.fileUpload.uploadPDF(files, options); await client.fileUpload.uploadExcel(files, options); // New (V2 — single endpoint for all types) await client.v2Upload.upload([...pdfFiles, ...excelFiles], options);Manage LLM config via
client.v2Config:// New in V2 — not available in V1 await client.v2Config.update({ temperature: 0.7, llm_model: 'gpt-4o' });
From NatyClient to NatyLangChainClient
Update client initialization:
// Old const client = new NatyClient({ baseURL: 'http://localhost:8000', token: 'bearer-token', tenantId: 'tenant-id', }); // New const client = new NatyLangChainClient({ baseURL: 'http://localhost:8000', apiKey: 'nlck-key_prod_1', // Changed from token tenantId: 'tenant-id', });Update API URLs:
- Documents:
/documents→/v1/documents - QA:
/v1/qa(unchanged) - Custom QA:
/v1/qa/custom(unchanged)
- Documents:
Remove query parameters:
- No longer need to pass
llm,embeddings,retrieverquery parameters - These are now handled by the API internally
- No longer need to pass
Update error handling:
// Old catch (error) { console.error(error.message); } // New catch (error) { if (error instanceof NatyLangChainError) { console.error('Status:', error.status); console.error('Code:', error.code); console.error('Message:', error.message); } }
Development
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Run integration tests only
npm test -- tests/integration.test.ts
# Lint
npm run lint
# Format code
npm run formatExamples
See the examples/ directory for complete usage examples:
examples/usage-example.ts- Comprehensive usage demonstration
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License
