Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
8e8830ef92 | |||
34931875ad | |||
2672509d3f | |||
ee3a635852 | |||
a222b1c2fa | |||
f0556e89f3 | |||
fe8540c8ba |
27
changelog.md
27
changelog.md
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-10-03 - 0.7.2 - fix(anthropic)
|
||||
Update Anthropic provider branding to Claude Sonnet 4.5 and add local Claude permissions
|
||||
|
||||
- Docs: Replace 'Claude 3 Opus' with 'Claude Sonnet 4.5' in README provider capabilities matrix.
|
||||
- Config: Add .claude/settings.local.json to define local Claude permissions for tests and development commands.
|
||||
|
||||
## 2025-10-03 - 0.7.1 - fix(docs)
|
||||
Add README image generation docs and .claude local settings
|
||||
|
||||
- Add .claude/settings.local.json with permission allow-list for local assistant tooling and web search
|
||||
- Update README provider capabilities table to include an Images column and reference gpt-image-1
|
||||
- Add Image Generation & Editing section with examples, options, and gpt-image-1 advantages
|
||||
- Mark image generation support as implemented in the roadmap and remove duplicate entry
|
||||
|
||||
## 2025-10-03 - 0.7.0 - feat(providers)
|
||||
Add research API and image generation/editing support; extend providers and tests
|
||||
|
||||
- Introduce ResearchOptions and ResearchResponse to the MultiModalModel interface and implement research() where supported
|
||||
- OpenAiProvider: implement research(), add imageGenerate() and imageEdit() methods (gpt-image-1 / DALL·E support), and expose imageModel option
|
||||
- AnthropicProvider: implement research() and vision handling; explicitly throw for unsupported image generation/editing
|
||||
- PerplexityProvider: implement research() (sonar / sonar-pro support) and expose citation parsing
|
||||
- Add image/document-related interfaces (ImageGenerateOptions, ImageEditOptions, ImageResponse) to abstract API
|
||||
- Add image generation/editing/no-op stubs for other providers (Exo, Groq, Ollama, XAI) that throw informative errors to preserve API compatibility
|
||||
- Add comprehensive OpenAI image generation tests and helper to save test outputs (test/test.image.openai.ts)
|
||||
- Update README with Research & Web Search documentation, capability matrix, and roadmap entry for Research & Web Search API
|
||||
- Add local Claude agent permissions file (.claude/settings.local.json) and various provider type/import updates
|
||||
|
||||
## 2025-09-28 - 0.6.1 - fix(provider.anthropic)
|
||||
Fix Anthropic research tool identifier and add tests + local Claude permissions
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartai",
|
||||
"version": "0.6.1",
|
||||
"version": "0.7.2",
|
||||
"private": false,
|
||||
"description": "SmartAi is a versatile TypeScript library designed to facilitate integration and interaction with various AI models, offering functionalities for chat, audio generation, document processing, and vision tasks.",
|
||||
"main": "dist_ts/index.js",
|
||||
|
148
readme.md
148
readme.md
@@ -45,15 +45,15 @@ const response = await ai.openaiProvider.chat({
|
||||
|
||||
Choose the right provider for your use case:
|
||||
|
||||
| Provider | Chat | Streaming | TTS | Vision | Documents | Highlights |
|
||||
|----------|:----:|:---------:|:---:|:------:|:---------:|------------|
|
||||
| **OpenAI** | ✅ | ✅ | ✅ | ✅ | ✅ | • GPT-4, DALL-E 3<br>• Industry standard<br>• Most features |
|
||||
| **Anthropic** | ✅ | ✅ | ❌ | ✅ | ✅ | • Claude 3 Opus<br>• Superior reasoning<br>• 200k context |
|
||||
| **Ollama** | ✅ | ✅ | ❌ | ✅ | ✅ | • 100% local<br>• Privacy-first<br>• No API costs |
|
||||
| **XAI** | ✅ | ✅ | ❌ | ❌ | ✅ | • Grok models<br>• Real-time data<br>• Uncensored |
|
||||
| **Perplexity** | ✅ | ✅ | ❌ | ❌ | ❌ | • Web-aware<br>• Research-focused<br>• Citations |
|
||||
| **Groq** | ✅ | ✅ | ❌ | ❌ | ❌ | • 10x faster<br>• LPU inference<br>• Low latency |
|
||||
| **Exo** | ✅ | ✅ | ❌ | ❌ | ❌ | • Distributed<br>• P2P compute<br>• Decentralized |
|
||||
| Provider | Chat | Streaming | TTS | Vision | Documents | Research | Images | Highlights |
|
||||
|----------|:----:|:---------:|:---:|:------:|:---------:|:--------:|:------:|------------|
|
||||
| **OpenAI** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | • gpt-image-1<br>• DALL-E 3<br>• Deep research API |
|
||||
| **Anthropic** | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | • Claude Sonnet 4.5<br>• Superior reasoning<br>• Web search API |
|
||||
| **Ollama** | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | • 100% local<br>• Privacy-first<br>• No API costs |
|
||||
| **XAI** | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | • Grok models<br>• Real-time data<br>• Uncensored |
|
||||
| **Perplexity** | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | • Web-aware<br>• Research-focused<br>• Sonar Pro models |
|
||||
| **Groq** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | • 10x faster<br>• LPU inference<br>• Low latency |
|
||||
| **Exo** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | • Distributed<br>• P2P compute<br>• Decentralized |
|
||||
|
||||
## 🎮 Core Features
|
||||
|
||||
@@ -171,6 +171,132 @@ const taxAnalysis = await ai.anthropicProvider.document({
|
||||
});
|
||||
```
|
||||
|
||||
### 🔬 Research & Web Search
|
||||
|
||||
Perform deep research with web search capabilities across multiple providers:
|
||||
|
||||
```typescript
|
||||
// OpenAI Deep Research - Comprehensive analysis
|
||||
const deepResearch = await ai.openaiProvider.research({
|
||||
query: 'What are the latest developments in quantum computing?',
|
||||
searchDepth: 'deep',
|
||||
includeWebSearch: true
|
||||
});
|
||||
|
||||
console.log(deepResearch.answer);
|
||||
console.log('Sources:', deepResearch.sources);
|
||||
|
||||
// Anthropic Web Search - Domain-filtered research
|
||||
const anthropic = new AnthropicProvider({
|
||||
anthropicToken: 'sk-ant-...',
|
||||
enableWebSearch: true,
|
||||
searchDomainAllowList: ['nature.com', 'science.org']
|
||||
});
|
||||
|
||||
const scientificResearch = await anthropic.research({
|
||||
query: 'Latest breakthroughs in CRISPR gene editing',
|
||||
searchDepth: 'advanced'
|
||||
});
|
||||
|
||||
// Perplexity - Research-focused with citations
|
||||
const perplexityResearch = await ai.perplexityProvider.research({
|
||||
query: 'Current state of autonomous vehicle technology',
|
||||
searchDepth: 'deep' // Uses Sonar Pro model
|
||||
});
|
||||
```
|
||||
|
||||
**Research Options:**
|
||||
- `searchDepth`: 'basic' | 'advanced' | 'deep'
|
||||
- `maxSources`: Number of sources to include
|
||||
- `includeWebSearch`: Enable web search (OpenAI)
|
||||
- `background`: Run as background task (OpenAI)
|
||||
|
||||
**Supported Providers:**
|
||||
- **OpenAI**: Deep Research API with specialized models (`o3-deep-research-2025-06-26`, `o4-mini-deep-research-2025-06-26`)
|
||||
- **Anthropic**: Web Search API with domain filtering
|
||||
- **Perplexity**: Sonar and Sonar Pro models with built-in citations
|
||||
|
||||
### 🎨 Image Generation & Editing
|
||||
|
||||
Generate and edit images with OpenAI's cutting-edge models:
|
||||
|
||||
```typescript
|
||||
// Basic image generation with gpt-image-1
|
||||
const image = await ai.openaiProvider.imageGenerate({
|
||||
prompt: 'A futuristic robot assistant in a modern office, digital art',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high',
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
// Save the generated image
|
||||
const imageBuffer = Buffer.from(image.images[0].b64_json!, 'base64');
|
||||
fs.writeFileSync('robot.png', imageBuffer);
|
||||
|
||||
// Advanced: Transparent background with custom format
|
||||
const logo = await ai.openaiProvider.imageGenerate({
|
||||
prompt: 'Minimalist mountain peak logo, geometric design',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high',
|
||||
size: '1024x1024',
|
||||
background: 'transparent',
|
||||
outputFormat: 'png'
|
||||
});
|
||||
|
||||
// WebP with compression for web use
|
||||
const webImage = await ai.openaiProvider.imageGenerate({
|
||||
prompt: 'Product showcase: sleek smartphone on marble surface',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high',
|
||||
size: '1536x1024',
|
||||
outputFormat: 'webp',
|
||||
outputCompression: 85
|
||||
});
|
||||
|
||||
// Superior text rendering (gpt-image-1's strength)
|
||||
const signage = await ai.openaiProvider.imageGenerate({
|
||||
prompt: 'Vintage cafe sign saying "COFFEE & CODE" in hand-lettered typography',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high',
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
// Generate multiple variations at once
|
||||
const variations = await ai.openaiProvider.imageGenerate({
|
||||
prompt: 'Abstract geometric pattern, colorful minimalist art',
|
||||
model: 'gpt-image-1',
|
||||
n: 3,
|
||||
quality: 'medium',
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
// Edit an existing image
|
||||
const editedImage = await ai.openaiProvider.imageEdit({
|
||||
image: originalImageBuffer,
|
||||
prompt: 'Add sunglasses and change the background to a beach sunset',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high'
|
||||
});
|
||||
```
|
||||
|
||||
**Image Generation Options:**
|
||||
- `model`: 'gpt-image-1' | 'dall-e-3' | 'dall-e-2'
|
||||
- `quality`: 'low' | 'medium' | 'high' | 'auto'
|
||||
- `size`: Multiple aspect ratios up to 4096×4096
|
||||
- `background`: 'transparent' | 'opaque' | 'auto'
|
||||
- `outputFormat`: 'png' | 'jpeg' | 'webp'
|
||||
- `outputCompression`: 0-100 for webp/jpeg
|
||||
- `moderation`: 'low' | 'auto'
|
||||
- `n`: Number of images (1-10)
|
||||
|
||||
**gpt-image-1 Advantages:**
|
||||
- Superior text rendering in images
|
||||
- Up to 4096×4096 resolution
|
||||
- Transparent background support
|
||||
- Advanced output formats (WebP with compression)
|
||||
- Better prompt understanding
|
||||
- Streaming support for progressive rendering
|
||||
|
||||
### 🔄 Persistent Conversations
|
||||
|
||||
Maintain context across interactions:
|
||||
@@ -447,6 +573,7 @@ export PERPLEXITY_API_KEY=pplx-...
|
||||
| **General Purpose** | OpenAI | Most features, stable, well-documented |
|
||||
| **Complex Reasoning** | Anthropic | Superior logical thinking, safer outputs |
|
||||
| **Research & Facts** | Perplexity | Web-aware, provides citations |
|
||||
| **Deep Research** | OpenAI | Deep Research API with comprehensive analysis |
|
||||
| **Speed Critical** | Groq | 10x faster inference, sub-second responses |
|
||||
| **Privacy Critical** | Ollama | 100% local, no data leaves your servers |
|
||||
| **Real-time Data** | XAI | Access to current information |
|
||||
@@ -454,8 +581,9 @@ export PERPLEXITY_API_KEY=pplx-...
|
||||
|
||||
## 📈 Roadmap
|
||||
|
||||
- [x] Research & Web Search API
|
||||
- [x] Image generation support (gpt-image-1, DALL-E 3, DALL-E 2)
|
||||
- [ ] Streaming function calls
|
||||
- [ ] Image generation support
|
||||
- [ ] Voice input processing
|
||||
- [ ] Fine-tuning integration
|
||||
- [ ] Embedding support
|
||||
|
@@ -1,177 +0,0 @@
|
||||
# SmartAI Research API Implementation
|
||||
|
||||
This document describes the new research capabilities added to the SmartAI library, enabling web search and deep research features for OpenAI and Anthropic providers.
|
||||
|
||||
## Features Added
|
||||
|
||||
### 1. Research Method Interface
|
||||
|
||||
Added a new `research()` method to the `MultiModalModel` abstract class with the following interfaces:
|
||||
|
||||
```typescript
|
||||
interface ResearchOptions {
|
||||
query: string;
|
||||
searchDepth?: 'basic' | 'advanced' | 'deep';
|
||||
maxSources?: number;
|
||||
includeWebSearch?: boolean;
|
||||
background?: boolean;
|
||||
}
|
||||
|
||||
interface ResearchResponse {
|
||||
answer: string;
|
||||
sources: Array<{
|
||||
url: string;
|
||||
title: string;
|
||||
snippet: string;
|
||||
}>;
|
||||
searchQueries?: string[];
|
||||
metadata?: any;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. OpenAI Provider Research Implementation
|
||||
|
||||
The OpenAI provider now supports:
|
||||
- **Deep Research API** with models:
|
||||
- `o3-deep-research-2025-06-26` (comprehensive analysis)
|
||||
- `o4-mini-deep-research-2025-06-26` (lightweight, faster)
|
||||
- **Web Search** for standard models (gpt-5, o3, o3-pro, o4-mini)
|
||||
- **Background processing** for async deep research tasks
|
||||
|
||||
### 3. Anthropic Provider Research Implementation
|
||||
|
||||
The Anthropic provider now supports:
|
||||
- **Web Search API** with Claude models
|
||||
- **Domain filtering** (allow/block lists)
|
||||
- **Progressive searches** for comprehensive research
|
||||
- **Citation extraction** from responses
|
||||
|
||||
### 4. Perplexity Provider Research Implementation
|
||||
|
||||
The Perplexity provider implements research using:
|
||||
- **Sonar models** for standard searches
|
||||
- **Sonar Pro** for deep research
|
||||
- Built-in citation support
|
||||
|
||||
### 5. Other Providers
|
||||
|
||||
Added research method stubs to:
|
||||
- Groq Provider
|
||||
- Ollama Provider
|
||||
- xAI Provider
|
||||
- Exo Provider
|
||||
|
||||
These providers throw a "not yet supported" error when research is called, maintaining interface compatibility.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Research with OpenAI
|
||||
|
||||
```typescript
|
||||
import { OpenAiProvider } from '@push.rocks/smartai';
|
||||
|
||||
const openai = new OpenAiProvider({
|
||||
openaiToken: 'your-api-key',
|
||||
researchModel: 'o4-mini-deep-research-2025-06-26'
|
||||
});
|
||||
|
||||
await openai.start();
|
||||
|
||||
const result = await openai.research({
|
||||
query: 'What are the latest developments in quantum computing?',
|
||||
searchDepth: 'basic',
|
||||
includeWebSearch: true
|
||||
});
|
||||
|
||||
console.log(result.answer);
|
||||
console.log('Sources:', result.sources);
|
||||
```
|
||||
|
||||
### Deep Research with OpenAI
|
||||
|
||||
```typescript
|
||||
const deepResult = await openai.research({
|
||||
query: 'Comprehensive analysis of climate change mitigation strategies',
|
||||
searchDepth: 'deep',
|
||||
background: true
|
||||
});
|
||||
```
|
||||
|
||||
### Research with Anthropic
|
||||
|
||||
```typescript
|
||||
import { AnthropicProvider } from '@push.rocks/smartai';
|
||||
|
||||
const anthropic = new AnthropicProvider({
|
||||
anthropicToken: 'your-api-key',
|
||||
enableWebSearch: true,
|
||||
searchDomainAllowList: ['nature.com', 'science.org']
|
||||
});
|
||||
|
||||
await anthropic.start();
|
||||
|
||||
const result = await anthropic.research({
|
||||
query: 'Latest breakthroughs in CRISPR gene editing',
|
||||
searchDepth: 'advanced'
|
||||
});
|
||||
```
|
||||
|
||||
### Research with Perplexity
|
||||
|
||||
```typescript
|
||||
import { PerplexityProvider } from '@push.rocks/smartai';
|
||||
|
||||
const perplexity = new PerplexityProvider({
|
||||
perplexityToken: 'your-api-key'
|
||||
});
|
||||
|
||||
const result = await perplexity.research({
|
||||
query: 'Current state of autonomous vehicle technology',
|
||||
searchDepth: 'deep' // Uses Sonar Pro model
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### OpenAI Provider
|
||||
- `researchModel`: Specify deep research model (default: `o4-mini-deep-research-2025-06-26`)
|
||||
- `enableWebSearch`: Enable web search for standard models
|
||||
|
||||
### Anthropic Provider
|
||||
- `enableWebSearch`: Enable web search capabilities
|
||||
- `searchDomainAllowList`: Array of allowed domains
|
||||
- `searchDomainBlockList`: Array of blocked domains
|
||||
|
||||
## API Pricing
|
||||
|
||||
- **OpenAI Deep Research**: $10 per 1,000 calls
|
||||
- **Anthropic Web Search**: $10 per 1,000 searches + standard token costs
|
||||
- **Perplexity Sonar**: $5 per 1,000 searches (Sonar Pro)
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite:
|
||||
|
||||
```bash
|
||||
pnpm test test/test.research.ts
|
||||
```
|
||||
|
||||
All providers have been tested to ensure:
|
||||
- Research methods are properly exposed
|
||||
- Interfaces are correctly typed
|
||||
- Unsupported providers throw appropriate errors
|
||||
|
||||
## Next Steps
|
||||
|
||||
Future enhancements could include:
|
||||
1. Implementing Google Gemini Grounding API support
|
||||
2. Adding Brave Search API integration
|
||||
3. Implementing retry logic for rate limits
|
||||
4. Adding caching for repeated queries
|
||||
5. Supporting batch research operations
|
||||
|
||||
## Notes
|
||||
|
||||
- The implementation maintains backward compatibility
|
||||
- All existing methods continue to work unchanged
|
||||
- Research capabilities are optional and don't affect existing functionality
|
@@ -48,20 +48,76 @@ tap.test('Anthropic: should handle message history', async () => {
|
||||
expect(response.message.toLowerCase()).toInclude('claude test');
|
||||
});
|
||||
|
||||
tap.test('Anthropic: should process vision tasks', async () => {
|
||||
// Create a simple test image (1x1 red pixel JPEG)
|
||||
// This is a valid 1x1 JPEG image
|
||||
const redPixelBase64 = '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCwAA8A/9k=';
|
||||
const imageBuffer = Buffer.from(redPixelBase64, 'base64');
|
||||
tap.test('Anthropic: should analyze coffee image with latte art', async () => {
|
||||
// Test 1: Coffee image from Unsplash by Dani
|
||||
const imagePath = './test/testimages/coffee-dani/coffee.jpg';
|
||||
console.log(`Loading coffee image from: ${imagePath}`);
|
||||
|
||||
const imageBuffer = await smartfile.fs.toBuffer(imagePath);
|
||||
console.log(`Image loaded, size: ${imageBuffer.length} bytes`);
|
||||
|
||||
const result = await anthropicProvider.vision({
|
||||
image: imageBuffer,
|
||||
prompt: 'What color is this image? Answer with just the color name.'
|
||||
prompt: 'Describe this coffee image. What do you see in terms of the cup, foam pattern, and overall composition?'
|
||||
});
|
||||
|
||||
console.log(`Anthropic Vision - Result: ${result}`);
|
||||
console.log(`Anthropic Vision (Coffee) - Result: ${result}`);
|
||||
expect(result).toBeTruthy();
|
||||
expect(typeof result).toEqual('string');
|
||||
expect(result.toLowerCase()).toInclude('coffee');
|
||||
// The image has a heart pattern in the latte art
|
||||
const mentionsLatte = result.toLowerCase().includes('heart') ||
|
||||
result.toLowerCase().includes('latte') ||
|
||||
result.toLowerCase().includes('foam');
|
||||
expect(mentionsLatte).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('Anthropic: should analyze laptop/workspace image', async () => {
|
||||
// Test 2: Laptop image from Unsplash by Nicolas Bichon
|
||||
const imagePath = './test/testimages/laptop-nicolas/laptop.jpg';
|
||||
console.log(`Loading laptop image from: ${imagePath}`);
|
||||
|
||||
const imageBuffer = await smartfile.fs.toBuffer(imagePath);
|
||||
console.log(`Image loaded, size: ${imageBuffer.length} bytes`);
|
||||
|
||||
const result = await anthropicProvider.vision({
|
||||
image: imageBuffer,
|
||||
prompt: 'Describe the technology and workspace setup in this image. What devices and equipment can you see?'
|
||||
});
|
||||
|
||||
console.log(`Anthropic Vision (Laptop) - Result: ${result}`);
|
||||
expect(result).toBeTruthy();
|
||||
expect(typeof result).toEqual('string');
|
||||
// Should mention laptop, computer, keyboard, or desk
|
||||
const mentionsTech = result.toLowerCase().includes('laptop') ||
|
||||
result.toLowerCase().includes('computer') ||
|
||||
result.toLowerCase().includes('keyboard') ||
|
||||
result.toLowerCase().includes('desk');
|
||||
expect(mentionsTech).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('Anthropic: should analyze receipt/document image', async () => {
|
||||
// Test 3: Receipt image from Unsplash by Annie Spratt
|
||||
const imagePath = './test/testimages/receipt-annie/receipt.jpg';
|
||||
console.log(`Loading receipt image from: ${imagePath}`);
|
||||
|
||||
const imageBuffer = await smartfile.fs.toBuffer(imagePath);
|
||||
console.log(`Image loaded, size: ${imageBuffer.length} bytes`);
|
||||
|
||||
const result = await anthropicProvider.vision({
|
||||
image: imageBuffer,
|
||||
prompt: 'What type of document is this? Can you identify any text or numbers visible in the image?'
|
||||
});
|
||||
|
||||
console.log(`Anthropic Vision (Receipt) - Result: ${result}`);
|
||||
expect(result).toBeTruthy();
|
||||
expect(typeof result).toEqual('string');
|
||||
// Should mention receipt, document, text, or paper
|
||||
const mentionsDocument = result.toLowerCase().includes('receipt') ||
|
||||
result.toLowerCase().includes('document') ||
|
||||
result.toLowerCase().includes('text') ||
|
||||
result.toLowerCase().includes('paper');
|
||||
expect(mentionsDocument).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('Anthropic: should document a PDF', async () => {
|
||||
|
203
test/test.image.openai.ts
Normal file
203
test/test.image.openai.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartai from '../ts/index.js';
|
||||
import * as path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const testQenv = new qenv.Qenv('./', './.nogit/');
|
||||
|
||||
let openaiProvider: smartai.OpenAiProvider;
|
||||
|
||||
// Helper function to save image results
|
||||
async function saveImageResult(testName: string, result: any) {
|
||||
const sanitizedName = testName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `openai_${sanitizedName}_${timestamp}.json`;
|
||||
const filepath = path.join('.nogit', 'testresults', 'images', filename);
|
||||
|
||||
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
||||
await fs.writeFile(filepath, JSON.stringify(result, null, 2), 'utf-8');
|
||||
|
||||
console.log(` 💾 Saved to: ${filepath}`);
|
||||
|
||||
// Also save the actual image if b64_json is present
|
||||
if (result.images && result.images[0]?.b64_json) {
|
||||
const imageFilename = `openai_${sanitizedName}_${timestamp}.png`;
|
||||
const imageFilepath = path.join('.nogit', 'testresults', 'images', imageFilename);
|
||||
await fs.writeFile(imageFilepath, Buffer.from(result.images[0].b64_json, 'base64'));
|
||||
console.log(` 🖼️ Image saved to: ${imageFilepath}`);
|
||||
}
|
||||
}
|
||||
|
||||
tap.test('OpenAI Image Generation: should initialize provider', async () => {
|
||||
const openaiToken = await testQenv.getEnvVarOnDemand('OPENAI_TOKEN');
|
||||
expect(openaiToken).toBeTruthy();
|
||||
|
||||
openaiProvider = new smartai.OpenAiProvider({
|
||||
openaiToken,
|
||||
imageModel: 'gpt-image-1'
|
||||
});
|
||||
|
||||
await openaiProvider.start();
|
||||
expect(openaiProvider).toBeInstanceOf(smartai.OpenAiProvider);
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image: Basic generation with gpt-image-1', async () => {
|
||||
const result = await openaiProvider.imageGenerate({
|
||||
prompt: 'A cute robot reading a book in a cozy library, digital art style',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'medium',
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
console.log('Basic gpt-image-1 Generation:');
|
||||
console.log('- Images generated:', result.images.length);
|
||||
console.log('- Model used:', result.metadata?.model);
|
||||
console.log('- Quality:', result.metadata?.quality);
|
||||
console.log('- Size:', result.metadata?.size);
|
||||
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
||||
|
||||
await saveImageResult('basic_generation_gptimage1', result);
|
||||
|
||||
expect(result.images).toBeTruthy();
|
||||
expect(result.images.length).toEqual(1);
|
||||
expect(result.images[0].b64_json).toBeTruthy();
|
||||
expect(result.metadata?.model).toEqual('gpt-image-1');
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image: High quality with transparent background', async () => {
|
||||
const result = await openaiProvider.imageGenerate({
|
||||
prompt: 'A simple geometric logo of a mountain peak, minimal design, clean lines',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high',
|
||||
size: '1024x1024',
|
||||
background: 'transparent',
|
||||
outputFormat: 'png'
|
||||
});
|
||||
|
||||
console.log('High Quality Transparent:');
|
||||
console.log('- Quality:', result.metadata?.quality);
|
||||
console.log('- Background: transparent');
|
||||
console.log('- Format:', result.metadata?.outputFormat);
|
||||
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
||||
|
||||
await saveImageResult('high_quality_transparent', result);
|
||||
|
||||
expect(result.images.length).toEqual(1);
|
||||
expect(result.images[0].b64_json).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image: WebP format with compression', async () => {
|
||||
const result = await openaiProvider.imageGenerate({
|
||||
prompt: 'A futuristic cityscape at sunset with flying cars, photorealistic',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high',
|
||||
size: '1536x1024',
|
||||
outputFormat: 'webp',
|
||||
outputCompression: 85
|
||||
});
|
||||
|
||||
console.log('WebP with Compression:');
|
||||
console.log('- Format:', result.metadata?.outputFormat);
|
||||
console.log('- Compression: 85%');
|
||||
console.log('- Size:', result.metadata?.size);
|
||||
|
||||
await saveImageResult('webp_compression', result);
|
||||
|
||||
expect(result.images.length).toEqual(1);
|
||||
expect(result.images[0].b64_json).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image: Text rendering with gpt-image-1', async () => {
|
||||
const result = await openaiProvider.imageGenerate({
|
||||
prompt: 'A vintage cafe sign that says "COFFEE & CODE" in elegant hand-lettered typography, warm colors',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'high',
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
console.log('Text Rendering:');
|
||||
console.log('- Prompt includes text: "COFFEE & CODE"');
|
||||
console.log('- gpt-image-1 has superior text rendering');
|
||||
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
||||
|
||||
await saveImageResult('text_rendering', result);
|
||||
|
||||
expect(result.images.length).toEqual(1);
|
||||
expect(result.images[0].b64_json).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image: Multiple images generation', async () => {
|
||||
const result = await openaiProvider.imageGenerate({
|
||||
prompt: 'Abstract colorful geometric patterns, modern minimalist art',
|
||||
model: 'gpt-image-1',
|
||||
n: 2,
|
||||
quality: 'medium',
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
console.log('Multiple Images:');
|
||||
console.log('- Images requested: 2');
|
||||
console.log('- Images generated:', result.images.length);
|
||||
|
||||
await saveImageResult('multiple_images', result);
|
||||
|
||||
expect(result.images.length).toEqual(2);
|
||||
expect(result.images[0].b64_json).toBeTruthy();
|
||||
expect(result.images[1].b64_json).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image: Low moderation setting', async () => {
|
||||
const result = await openaiProvider.imageGenerate({
|
||||
prompt: 'A fantasy battle scene with warriors and dragons',
|
||||
model: 'gpt-image-1',
|
||||
moderation: 'low',
|
||||
quality: 'medium'
|
||||
});
|
||||
|
||||
console.log('Low Moderation:');
|
||||
console.log('- Moderation: low (less restrictive filtering)');
|
||||
console.log('- Tokens used:', result.metadata?.tokensUsed);
|
||||
|
||||
await saveImageResult('low_moderation', result);
|
||||
|
||||
expect(result.images.length).toEqual(1);
|
||||
expect(result.images[0].b64_json).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image Editing: edit with gpt-image-1', async () => {
|
||||
// First, generate a base image
|
||||
const baseResult = await openaiProvider.imageGenerate({
|
||||
prompt: 'A simple white cat sitting on a red cushion',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'low',
|
||||
size: '1024x1024'
|
||||
});
|
||||
|
||||
const baseImageBuffer = Buffer.from(baseResult.images[0].b64_json!, 'base64');
|
||||
|
||||
// Now edit it
|
||||
const editResult = await openaiProvider.imageEdit({
|
||||
image: baseImageBuffer,
|
||||
prompt: 'Change the cat to orange and add stylish sunglasses',
|
||||
model: 'gpt-image-1',
|
||||
quality: 'medium'
|
||||
});
|
||||
|
||||
console.log('Image Editing:');
|
||||
console.log('- Base image created');
|
||||
console.log('- Edit: change color and add sunglasses');
|
||||
console.log('- Result images:', editResult.images.length);
|
||||
|
||||
await saveImageResult('image_edit', editResult);
|
||||
|
||||
expect(editResult.images.length).toEqual(1);
|
||||
expect(editResult.images[0].b64_json).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('OpenAI Image: should clean up provider', async () => {
|
||||
await openaiProvider.stop();
|
||||
console.log('OpenAI image provider stopped successfully');
|
||||
});
|
||||
|
||||
export default tap.start();
|
@@ -1,9 +1,24 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartai from '../ts/index.js';
|
||||
import * as path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const testQenv = new qenv.Qenv('./', './.nogit/');
|
||||
|
||||
// Helper function to save research results
|
||||
async function saveResearchResult(testName: string, result: any) {
|
||||
const sanitizedName = testName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `${sanitizedName}_${timestamp}.json`;
|
||||
const filepath = path.join('.nogit', 'testresults', 'research', filename);
|
||||
|
||||
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
||||
await fs.writeFile(filepath, JSON.stringify(result, null, 2), 'utf-8');
|
||||
|
||||
console.log(` 💾 Saved to: ${filepath}`);
|
||||
}
|
||||
|
||||
let anthropicProvider: smartai.AnthropicProvider;
|
||||
|
||||
tap.test('Anthropic Research: should initialize provider with web search', async () => {
|
||||
@@ -28,6 +43,8 @@ tap.test('Anthropic Research: should perform basic research query', async () =>
|
||||
console.log('- Sources found:', result.sources.length);
|
||||
console.log('- First 200 chars:', result.answer.substring(0, 200));
|
||||
|
||||
await saveResearchResult('basic_research_machine_learning', result);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(result.answer).toBeTruthy();
|
||||
expect(result.answer.toLowerCase()).toInclude('machine learning');
|
||||
@@ -50,6 +67,8 @@ tap.test('Anthropic Research: should perform research with web search', async ()
|
||||
console.log('- Search queries:', result.searchQueries);
|
||||
}
|
||||
|
||||
await saveResearchResult('web_search_renewable_energy', result);
|
||||
|
||||
expect(result.answer).toBeTruthy();
|
||||
expect(result.answer.toLowerCase()).toInclude('renewable');
|
||||
|
||||
@@ -70,6 +89,8 @@ tap.test('Anthropic Research: should handle deep research queries', async () =>
|
||||
console.log('- Answer length:', result.answer.length);
|
||||
console.log('- Token usage:', result.metadata?.tokensUsed);
|
||||
|
||||
await saveResearchResult('deep_research_rest_vs_graphql', result);
|
||||
|
||||
expect(result.answer).toBeTruthy();
|
||||
expect(result.answer.length).toBeGreaterThan(300);
|
||||
expect(result.answer.toLowerCase()).toInclude('rest');
|
||||
@@ -87,6 +108,8 @@ tap.test('Anthropic Research: should extract citations from response', async ()
|
||||
console.log('- Sources found:', result.sources.length);
|
||||
console.log('- Answer includes Docker:', result.answer.toLowerCase().includes('docker'));
|
||||
|
||||
await saveResearchResult('citation_extraction_docker', result);
|
||||
|
||||
expect(result.answer).toInclude('Docker');
|
||||
|
||||
// Check for URL extraction (both markdown and plain URLs)
|
||||
@@ -114,6 +137,8 @@ tap.test('Anthropic Research: should use domain filtering when configured', asyn
|
||||
console.log('- Answer length:', result.answer.length);
|
||||
console.log('- Applied domain filters (allow: wikipedia, docs.microsoft)');
|
||||
|
||||
await saveResearchResult('domain_filtering_javascript', result);
|
||||
|
||||
expect(result.answer).toBeTruthy();
|
||||
expect(result.answer.toLowerCase()).toInclude('javascript');
|
||||
|
||||
@@ -156,6 +181,9 @@ tap.test('Anthropic Research: should handle different search depths', async () =
|
||||
console.log('- Basic tokens:', basicResult.metadata?.tokensUsed);
|
||||
console.log('- Advanced tokens:', advancedResult.metadata?.tokensUsed);
|
||||
|
||||
await saveResearchResult('search_depth_python_basic', basicResult);
|
||||
await saveResearchResult('search_depth_python_advanced', advancedResult);
|
||||
|
||||
expect(basicResult.answer).toBeTruthy();
|
||||
expect(advancedResult.answer).toBeTruthy();
|
||||
|
||||
@@ -165,6 +193,28 @@ tap.test('Anthropic Research: should handle different search depths', async () =
|
||||
expect(advancedResult.answer.toLowerCase()).toInclude('python');
|
||||
});
|
||||
|
||||
tap.test('Anthropic Research: ARM vs. Qualcomm comparison', async () => {
|
||||
const result = await anthropicProvider.research({
|
||||
query: 'Compare ARM and Qualcomm: their technologies, market positions, and recent developments in the mobile and computing sectors',
|
||||
searchDepth: 'advanced',
|
||||
includeWebSearch: true,
|
||||
maxSources: 10
|
||||
});
|
||||
|
||||
console.log('ARM vs. Qualcomm Research:');
|
||||
console.log('- Answer length:', result.answer.length);
|
||||
console.log('- Sources found:', result.sources.length);
|
||||
console.log('- First 300 chars:', result.answer.substring(0, 300));
|
||||
|
||||
await saveResearchResult('arm_vs_qualcomm_comparison', result);
|
||||
|
||||
expect(result.answer).toBeTruthy();
|
||||
expect(result.answer.length).toBeGreaterThan(500);
|
||||
expect(result.answer.toLowerCase()).toInclude('arm');
|
||||
expect(result.answer.toLowerCase()).toInclude('qualcomm');
|
||||
expect(result.sources.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('Anthropic Research: should clean up provider', async () => {
|
||||
await anthropicProvider.stop();
|
||||
console.log('Anthropic research provider stopped successfully');
|
||||
|
@@ -1,9 +1,24 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartai from '../ts/index.js';
|
||||
import * as path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const testQenv = new qenv.Qenv('./', './.nogit/');
|
||||
|
||||
// Helper function to save research results
|
||||
async function saveResearchResult(testName: string, result: any) {
|
||||
const sanitizedName = testName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `openai_${sanitizedName}_${timestamp}.json`;
|
||||
const filepath = path.join('.nogit', 'testresults', 'research', filename);
|
||||
|
||||
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
||||
await fs.writeFile(filepath, JSON.stringify(result, null, 2), 'utf-8');
|
||||
|
||||
console.log(` 💾 Saved to: ${filepath}`);
|
||||
}
|
||||
|
||||
let openaiProvider: smartai.OpenAiProvider;
|
||||
|
||||
tap.test('OpenAI Research: should initialize provider with research capabilities', async () => {
|
||||
@@ -29,6 +44,8 @@ tap.test('OpenAI Research: should perform basic research query', async () => {
|
||||
console.log('- Sources found:', result.sources.length);
|
||||
console.log('- First 200 chars:', result.answer.substring(0, 200));
|
||||
|
||||
await saveResearchResult('basic_research_typescript', result);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(result.answer).toBeTruthy();
|
||||
expect(result.answer.toLowerCase()).toInclude('typescript');
|
||||
@@ -52,6 +69,8 @@ tap.test('OpenAI Research: should perform research with web search enabled', asy
|
||||
console.log('- Search queries used:', result.searchQueries);
|
||||
}
|
||||
|
||||
await saveResearchResult('web_search_ecmascript', result);
|
||||
|
||||
expect(result.answer).toBeTruthy();
|
||||
expect(result.answer.toLowerCase()).toInclude('ecmascript');
|
||||
|
||||
@@ -98,6 +117,8 @@ tap.test('OpenAI Research: should extract sources from markdown links', async ()
|
||||
console.log('OpenAI Source Extraction:');
|
||||
console.log('- Sources found:', result.sources.length);
|
||||
|
||||
await saveResearchResult('source_extraction_nodejs', result);
|
||||
|
||||
if (result.sources.length > 0) {
|
||||
console.log('- Example source:', result.sources[0]);
|
||||
expect(result.sources[0].url).toBeTruthy();
|
||||
|
36
test/testimages/coffee-dani/README.md
Normal file
36
test/testimages/coffee-dani/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Coffee Image Attribution
|
||||
|
||||
## coffee.jpg
|
||||
|
||||
**Photographer:** Dani (@frokz)
|
||||
**Source URL:** https://unsplash.com/photos/cup-of-coffee-on-saucer-ZLqxSzvVr7I
|
||||
**Direct Link:** https://images.unsplash.com/photo-1506372023823-741c83b836fe
|
||||
|
||||
### Metadata
|
||||
- **Title:** Cup of coffee on saucer
|
||||
- **Description:** One of many coffee-moments in my life ;)
|
||||
- **Date Published:** September 25, 2017
|
||||
- **Location:** Stockholm, Sweden
|
||||
- **Tags:** coffee, cafe, heart, coffee cup, cup, barista, latte, mug, saucer, food, sweden, stockholm
|
||||
|
||||
### License
|
||||
**Unsplash License** - Free to use
|
||||
- ✅ Commercial and non-commercial use
|
||||
- ✅ No permission needed
|
||||
- ❌ Cannot be sold without significant modification
|
||||
- ❌ Cannot be used to replicate Unsplash or similar service
|
||||
|
||||
Full license: https://unsplash.com/license
|
||||
|
||||
### Usage in This Project
|
||||
This image is used for testing vision/image processing capabilities in the SmartAI library test suite, specifically for:
|
||||
- Testing coffee/beverage recognition
|
||||
- Latte art pattern detection (heart shape)
|
||||
- Scene/environment analysis
|
||||
- Multi-element image understanding (cup, saucer, table)
|
||||
|
||||
### Download Information
|
||||
- **Downloaded:** September 28, 2025
|
||||
- **Original Filename:** dani-ZLqxSzvVr7I-unsplash.jpg
|
||||
- **Resolution:** High resolution (3.7 MB)
|
||||
- **Format:** JPEG
|
BIN
test/testimages/coffee-dani/coffee.jpg
Normal file
BIN
test/testimages/coffee-dani/coffee.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 MiB |
40
test/testimages/laptop-nicolas/README.md
Normal file
40
test/testimages/laptop-nicolas/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Laptop Image Attribution
|
||||
|
||||
## laptop.jpg
|
||||
|
||||
**Photographer:** Nicolas Bichon (@nicol3a)
|
||||
**Source URL:** https://unsplash.com/photos/a-laptop-computer-sitting-on-top-of-a-wooden-desk-ZhV4iqAXxyA
|
||||
**Direct Link:** https://images.unsplash.com/photo-1704230972797-e0e3aba0fce7
|
||||
|
||||
### Metadata
|
||||
- **Title:** A laptop computer sitting on top of a wooden desk
|
||||
- **Description:** Lifestyle photo I took for my indie app Type, a macOS app to take notes without interrupting your flow. https://usetype.app.
|
||||
- **Date Published:** January 2, 2024
|
||||
- **Camera:** FUJIFILM, X-T20
|
||||
- **Tags:** computer, laptop, mac, keyboard, computer keyboard, computer hardware, furniture, table, electronics, screen, monitor, hardware, display, tabletop, lcd screen, digital display
|
||||
|
||||
### Statistics
|
||||
- **Views:** 183,020
|
||||
- **Downloads:** 757
|
||||
|
||||
### License
|
||||
**Unsplash License** - Free to use
|
||||
- ✅ Commercial and non-commercial use
|
||||
- ✅ No permission needed
|
||||
- ❌ Cannot be sold without significant modification
|
||||
- ❌ Cannot be used to replicate Unsplash or similar service
|
||||
|
||||
Full license: https://unsplash.com/license
|
||||
|
||||
### Usage in This Project
|
||||
This image is used for testing vision/image processing capabilities in the SmartAI library test suite, specifically for:
|
||||
- Testing technology/computer equipment recognition
|
||||
- Workspace/office environment analysis
|
||||
- Object detection (laptop, keyboard, monitor, table)
|
||||
- Scene understanding and context analysis
|
||||
|
||||
### Download Information
|
||||
- **Downloaded:** September 28, 2025
|
||||
- **Original Filename:** nicolas-bichon-ZhV4iqAXxyA-unsplash.jpg
|
||||
- **Resolution:** High resolution (1.8 MB)
|
||||
- **Format:** JPEG
|
BIN
test/testimages/laptop-nicolas/laptop.jpg
Normal file
BIN
test/testimages/laptop-nicolas/laptop.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
40
test/testimages/receipt-annie/README.md
Normal file
40
test/testimages/receipt-annie/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Receipt Image Attribution
|
||||
|
||||
## receipt.jpg
|
||||
|
||||
**Photographer:** Annie Spratt (@anniespratt)
|
||||
**Source URL:** https://unsplash.com/photos/a-receipt-sitting-on-top-of-a-wooden-table-recgFWxDO1Y
|
||||
**Direct Link:** https://images.unsplash.com/photo-1731686602391-7484df33a03c
|
||||
|
||||
### Metadata
|
||||
- **Title:** A receipt sitting on top of a wooden table
|
||||
- **Description:** Download this free HD photo of text, document, invoice, and receipt by Annie Spratt
|
||||
- **Date Published:** November 15, 2024
|
||||
- **Tags:** text, document, invoice, receipt, diaper
|
||||
|
||||
### Statistics
|
||||
- **Views:** 54,593
|
||||
- **Downloads:** 764
|
||||
|
||||
### License
|
||||
**Unsplash License** - Free to use
|
||||
- ✅ Commercial and non-commercial use
|
||||
- ✅ No permission needed
|
||||
- ❌ Cannot be sold without significant modification
|
||||
- ❌ Cannot be used to replicate Unsplash or similar service
|
||||
|
||||
Full license: https://unsplash.com/license
|
||||
|
||||
### Usage in This Project
|
||||
This image is used for testing vision/image processing capabilities in the SmartAI library test suite, specifically for:
|
||||
- Testing text extraction and OCR capabilities
|
||||
- Document recognition and classification
|
||||
- Receipt/invoice analysis
|
||||
- Text-heavy image understanding
|
||||
- Structured data extraction from documents
|
||||
|
||||
### Download Information
|
||||
- **Downloaded:** September 28, 2025
|
||||
- **Original Filename:** annie-spratt-recgFWxDO1Y-unsplash.jpg
|
||||
- **Resolution:** High resolution (3.3 MB)
|
||||
- **Format:** JPEG
|
BIN
test/testimages/receipt-annie/receipt.jpg
Normal file
BIN
test/testimages/receipt-annie/receipt.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 MiB |
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartai',
|
||||
version: '0.6.1',
|
||||
version: '0.7.2',
|
||||
description: 'SmartAi is a versatile TypeScript library designed to facilitate integration and interaction with various AI models, offering functionalities for chat, audio generation, document processing, and vision tasks.'
|
||||
}
|
||||
|
@@ -50,6 +50,60 @@ export interface ResearchResponse {
|
||||
metadata?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for image generation
|
||||
*/
|
||||
export interface ImageGenerateOptions {
|
||||
prompt: string;
|
||||
model?: 'gpt-image-1' | 'dall-e-3' | 'dall-e-2';
|
||||
quality?: 'low' | 'medium' | 'high' | 'standard' | 'hd' | 'auto';
|
||||
size?: '256x256' | '512x512' | '1024x1024' | '1536x1024' | '1024x1536' | '1792x1024' | '1024x1792' | 'auto';
|
||||
style?: 'vivid' | 'natural';
|
||||
background?: 'transparent' | 'opaque' | 'auto';
|
||||
outputFormat?: 'png' | 'jpeg' | 'webp';
|
||||
outputCompression?: number; // 0-100 for webp/jpeg
|
||||
moderation?: 'low' | 'auto';
|
||||
n?: number; // Number of images to generate
|
||||
stream?: boolean;
|
||||
partialImages?: number; // 0-3 for streaming
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for image editing
|
||||
*/
|
||||
export interface ImageEditOptions {
|
||||
image: Buffer;
|
||||
prompt: string;
|
||||
mask?: Buffer;
|
||||
model?: 'gpt-image-1' | 'dall-e-2';
|
||||
quality?: 'low' | 'medium' | 'high' | 'standard' | 'auto';
|
||||
size?: '256x256' | '512x512' | '1024x1024' | '1536x1024' | '1024x1536' | 'auto';
|
||||
background?: 'transparent' | 'opaque' | 'auto';
|
||||
outputFormat?: 'png' | 'jpeg' | 'webp';
|
||||
outputCompression?: number;
|
||||
n?: number;
|
||||
stream?: boolean;
|
||||
partialImages?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response format for image operations
|
||||
*/
|
||||
export interface ImageResponse {
|
||||
images: Array<{
|
||||
b64_json?: string;
|
||||
url?: string;
|
||||
revisedPrompt?: string;
|
||||
}>;
|
||||
metadata?: {
|
||||
model: string;
|
||||
quality?: string;
|
||||
size?: string;
|
||||
outputFormat?: string;
|
||||
tokensUsed?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for multi-modal AI models.
|
||||
* Provides a common interface for different AI providers (OpenAI, Anthropic, Perplexity, Ollama)
|
||||
@@ -131,4 +185,20 @@ export abstract class MultiModalModel {
|
||||
* @throws Error if the provider doesn't support research capabilities
|
||||
*/
|
||||
public abstract research(optionsArg: ResearchOptions): Promise<ResearchResponse>;
|
||||
|
||||
/**
|
||||
* Image generation from text prompts
|
||||
* @param optionsArg Options containing the prompt and generation parameters
|
||||
* @returns Promise resolving to the generated image(s)
|
||||
* @throws Error if the provider doesn't support image generation
|
||||
*/
|
||||
public abstract imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse>;
|
||||
|
||||
/**
|
||||
* Image editing and inpainting
|
||||
* @param optionsArg Options containing the image, prompt, and editing parameters
|
||||
* @returns Promise resolving to the edited image(s)
|
||||
* @throws Error if the provider doesn't support image editing
|
||||
*/
|
||||
public abstract imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse>;
|
||||
}
|
||||
|
@@ -1,7 +1,16 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||||
import type { ChatOptions, ChatResponse, ChatMessage, ResearchOptions, ResearchResponse } from './abstract.classes.multimodal.js';
|
||||
import type {
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatMessage,
|
||||
ResearchOptions,
|
||||
ResearchResponse,
|
||||
ImageGenerateOptions,
|
||||
ImageEditOptions,
|
||||
ImageResponse
|
||||
} from './abstract.classes.multimodal.js';
|
||||
import type { ImageBlockParam, TextBlockParam } from '@anthropic-ai/sdk/resources/messages';
|
||||
|
||||
type ContentBlock = ImageBlockParam | TextBlockParam;
|
||||
@@ -68,7 +77,7 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
// If we have a complete message, send it to Anthropic
|
||||
if (currentMessage) {
|
||||
const stream = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-3-opus-20240229',
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
messages: [{ role: currentMessage.role, content: currentMessage.content }],
|
||||
system: '',
|
||||
stream: true,
|
||||
@@ -112,7 +121,7 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
}));
|
||||
|
||||
const result = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-3-opus-20240229',
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
system: optionsArg.systemMessage,
|
||||
messages: [
|
||||
...messages,
|
||||
@@ -159,7 +168,7 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
];
|
||||
|
||||
const result = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-3-opus-20240229',
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content
|
||||
@@ -218,7 +227,7 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
}
|
||||
|
||||
const result = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-3-opus-20240229',
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
system: optionsArg.systemMessage,
|
||||
messages: [
|
||||
...messages,
|
||||
@@ -251,23 +260,27 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
|
||||
try {
|
||||
// Build the tool configuration for web search
|
||||
const tools = this.options.enableWebSearch ? [
|
||||
{
|
||||
type: 'web_search_20250305' as const,
|
||||
name: 'web_search',
|
||||
description: 'Search the web for current information',
|
||||
input_schema: {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'The search query'
|
||||
const tools: any[] = [];
|
||||
|
||||
if (this.options.enableWebSearch) {
|
||||
const webSearchTool: any = {
|
||||
type: 'web_search_20250305',
|
||||
name: 'web_search'
|
||||
};
|
||||
|
||||
// Add optional parameters
|
||||
if (optionsArg.maxSources) {
|
||||
webSearchTool.max_uses = optionsArg.maxSources;
|
||||
}
|
||||
},
|
||||
required: ['query']
|
||||
|
||||
if (this.options.searchDomainAllowList?.length) {
|
||||
webSearchTool.allowed_domains = this.options.searchDomainAllowList;
|
||||
} else if (this.options.searchDomainBlockList?.length) {
|
||||
webSearchTool.blocked_domains = this.options.searchDomainBlockList;
|
||||
}
|
||||
|
||||
tools.push(webSearchTool);
|
||||
}
|
||||
] : [];
|
||||
|
||||
// Configure the request based on search depth
|
||||
const maxTokens = optionsArg.searchDepth === 'deep' ? 8192 :
|
||||
@@ -275,7 +288,7 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
|
||||
// Create the research request
|
||||
const requestParams: any = {
|
||||
model: 'claude-3-opus-20240229',
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
system: systemMessage,
|
||||
messages: [
|
||||
{
|
||||
@@ -290,7 +303,6 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
// Add tools if web search is enabled
|
||||
if (tools.length > 0) {
|
||||
requestParams.tools = tools;
|
||||
requestParams.tool_choice = { type: 'auto' };
|
||||
}
|
||||
|
||||
// Execute the research request
|
||||
@@ -304,11 +316,47 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
// Process content blocks
|
||||
for (const block of result.content) {
|
||||
if ('text' in block) {
|
||||
// Accumulate text content
|
||||
answer += block.text;
|
||||
|
||||
// Extract citations if present
|
||||
if ('citations' in block && Array.isArray(block.citations)) {
|
||||
for (const citation of block.citations) {
|
||||
if (citation.type === 'web_search_result_location') {
|
||||
sources.push({
|
||||
title: citation.title || '',
|
||||
url: citation.url || '',
|
||||
snippet: citation.cited_text || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ('type' in block && block.type === 'server_tool_use') {
|
||||
// Extract search queries from server tool use
|
||||
if (block.name === 'web_search' && block.input && typeof block.input === 'object' && 'query' in block.input) {
|
||||
searchQueries.push((block.input as any).query);
|
||||
}
|
||||
} else if ('type' in block && block.type === 'web_search_tool_result') {
|
||||
// Extract sources from web search results
|
||||
if (Array.isArray(block.content)) {
|
||||
for (const result of block.content) {
|
||||
if (result.type === 'web_search_result') {
|
||||
// Only add if not already in sources (avoid duplicates from citations)
|
||||
if (!sources.some(s => s.url === result.url)) {
|
||||
sources.push({
|
||||
title: result.title || '',
|
||||
url: result.url || '',
|
||||
snippet: '' // Search results don't include snippets, only citations do
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse sources from the answer (Claude includes citations in various formats)
|
||||
// Fallback: Parse markdown-style links if no citations found
|
||||
if (sources.length === 0) {
|
||||
const urlRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
@@ -319,39 +367,20 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
snippet: ''
|
||||
});
|
||||
}
|
||||
|
||||
// Also look for plain URLs
|
||||
const plainUrlRegex = /https?:\/\/[^\s\)]+/g;
|
||||
const plainUrls = answer.match(plainUrlRegex) || [];
|
||||
|
||||
for (const url of plainUrls) {
|
||||
// Check if this URL is already in sources
|
||||
if (!sources.some(s => s.url === url)) {
|
||||
sources.push({
|
||||
title: new URL(url).hostname,
|
||||
url: url,
|
||||
snippet: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract tool use information if available
|
||||
if ('tool_use' in result && Array.isArray(result.tool_use)) {
|
||||
for (const toolUse of result.tool_use) {
|
||||
if (toolUse.name === 'web_search' && toolUse.input?.query) {
|
||||
searchQueries.push(toolUse.input.query);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if web search was used based on usage info
|
||||
const webSearchCount = result.usage?.server_tool_use?.web_search_requests || 0;
|
||||
|
||||
return {
|
||||
answer,
|
||||
sources,
|
||||
searchQueries: searchQueries.length > 0 ? searchQueries : undefined,
|
||||
metadata: {
|
||||
model: 'claude-3-opus-20240229',
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
searchDepth: optionsArg.searchDepth || 'basic',
|
||||
tokensUsed: result.usage?.output_tokens
|
||||
tokensUsed: result.usage?.output_tokens,
|
||||
webSearchesPerformed: webSearchCount
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -359,4 +388,18 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
throw new Error(`Failed to perform research: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generation is not supported by Anthropic
|
||||
*/
|
||||
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image generation is not supported by Anthropic. Claude can only analyze images, not generate them. Please use OpenAI provider for image generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image editing is not supported by Anthropic
|
||||
*/
|
||||
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image editing is not supported by Anthropic. Claude can only analyze images, not edit them. Please use OpenAI provider for image editing.');
|
||||
}
|
||||
}
|
@@ -1,7 +1,16 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||||
import type { ChatOptions, ChatResponse, ChatMessage, ResearchOptions, ResearchResponse } from './abstract.classes.multimodal.js';
|
||||
import type {
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatMessage,
|
||||
ResearchOptions,
|
||||
ResearchResponse,
|
||||
ImageGenerateOptions,
|
||||
ImageEditOptions,
|
||||
ImageResponse
|
||||
} from './abstract.classes.multimodal.js';
|
||||
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
|
||||
|
||||
export interface IExoProviderOptions {
|
||||
@@ -129,4 +138,18 @@ export class ExoProvider extends MultiModalModel {
|
||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||
throw new Error('Research capabilities are not yet supported by Exo provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generation is not supported by Exo
|
||||
*/
|
||||
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image generation is not supported by Exo. Please use OpenAI provider for image generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image editing is not supported by Exo
|
||||
*/
|
||||
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image editing is not supported by Exo. Please use OpenAI provider for image editing.');
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,16 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||||
import type { ChatOptions, ChatResponse, ChatMessage, ResearchOptions, ResearchResponse } from './abstract.classes.multimodal.js';
|
||||
import type {
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatMessage,
|
||||
ResearchOptions,
|
||||
ResearchResponse,
|
||||
ImageGenerateOptions,
|
||||
ImageEditOptions,
|
||||
ImageResponse
|
||||
} from './abstract.classes.multimodal.js';
|
||||
|
||||
export interface IGroqProviderOptions {
|
||||
groqToken: string;
|
||||
@@ -193,4 +202,18 @@ export class GroqProvider extends MultiModalModel {
|
||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||
throw new Error('Research capabilities are not yet supported by Groq provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generation is not supported by Groq
|
||||
*/
|
||||
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image generation is not supported by Groq. Please use OpenAI provider for image generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image editing is not supported by Groq
|
||||
*/
|
||||
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image editing is not supported by Groq. Please use OpenAI provider for image editing.');
|
||||
}
|
||||
}
|
@@ -1,7 +1,16 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||||
import type { ChatOptions, ChatResponse, ChatMessage, ResearchOptions, ResearchResponse } from './abstract.classes.multimodal.js';
|
||||
import type {
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatMessage,
|
||||
ResearchOptions,
|
||||
ResearchResponse,
|
||||
ImageGenerateOptions,
|
||||
ImageEditOptions,
|
||||
ImageResponse
|
||||
} from './abstract.classes.multimodal.js';
|
||||
|
||||
export interface IOllamaProviderOptions {
|
||||
baseUrl?: string;
|
||||
@@ -255,4 +264,18 @@ export class OllamaProvider extends MultiModalModel {
|
||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||
throw new Error('Research capabilities are not yet supported by Ollama provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generation is not supported by Ollama
|
||||
*/
|
||||
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image generation is not supported by Ollama. Please use OpenAI provider for image generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image editing is not supported by Ollama
|
||||
*/
|
||||
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image editing is not supported by Ollama. Please use OpenAI provider for image editing.');
|
||||
}
|
||||
}
|
@@ -9,7 +9,13 @@ export type TChatCompletionRequestMessage = {
|
||||
};
|
||||
|
||||
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||||
import type { ResearchOptions, ResearchResponse } from './abstract.classes.multimodal.js';
|
||||
import type {
|
||||
ResearchOptions,
|
||||
ResearchResponse,
|
||||
ImageGenerateOptions,
|
||||
ImageEditOptions,
|
||||
ImageResponse
|
||||
} from './abstract.classes.multimodal.js';
|
||||
|
||||
export interface IOpenaiProviderOptions {
|
||||
openaiToken: string;
|
||||
@@ -17,6 +23,7 @@ export interface IOpenaiProviderOptions {
|
||||
audioModel?: string;
|
||||
visionModel?: string;
|
||||
researchModel?: string;
|
||||
imageModel?: string;
|
||||
enableWebSearch?: boolean;
|
||||
}
|
||||
|
||||
@@ -233,52 +240,37 @@ export class OpenAiProvider extends MultiModalModel {
|
||||
}
|
||||
|
||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||
// Determine which model to use based on search depth
|
||||
// Determine which model to use - Deep Research API requires specific models
|
||||
let model: string;
|
||||
if (optionsArg.searchDepth === 'deep') {
|
||||
model = this.options.researchModel || 'o4-mini-deep-research-2025-06-26';
|
||||
} else {
|
||||
// For basic/advanced, still use deep research models if web search is needed
|
||||
if (optionsArg.includeWebSearch) {
|
||||
model = this.options.researchModel || 'o4-mini-deep-research-2025-06-26';
|
||||
} else {
|
||||
model = this.options.chatModel || 'gpt-5-mini';
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the request parameters
|
||||
const systemMessage = 'You are a research assistant. Provide comprehensive answers with citations and sources when available.';
|
||||
|
||||
// Prepare request parameters using Deep Research API format
|
||||
const requestParams: any = {
|
||||
model,
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are a research assistant. Provide comprehensive answers with citations and sources when available.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: optionsArg.query
|
||||
}
|
||||
],
|
||||
temperature: 0.7
|
||||
instructions: systemMessage,
|
||||
input: optionsArg.query
|
||||
};
|
||||
|
||||
// Add web search tools if requested
|
||||
// Add web search tool if requested
|
||||
if (optionsArg.includeWebSearch || optionsArg.searchDepth === 'deep') {
|
||||
requestParams.tools = [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'web_search',
|
||||
description: 'Search the web for information',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'The search query'
|
||||
}
|
||||
},
|
||||
required: ['query']
|
||||
}
|
||||
}
|
||||
type: 'web_search_preview',
|
||||
search_context_size: optionsArg.searchDepth === 'deep' ? 'high' :
|
||||
optionsArg.searchDepth === 'advanced' ? 'medium' : 'low'
|
||||
}
|
||||
];
|
||||
requestParams.tool_choice = 'auto';
|
||||
}
|
||||
|
||||
// Add background flag for deep research
|
||||
@@ -287,14 +279,36 @@ export class OpenAiProvider extends MultiModalModel {
|
||||
}
|
||||
|
||||
try {
|
||||
// Execute the research request
|
||||
const result = await this.openAiApiClient.chat.completions.create(requestParams);
|
||||
// Execute the research request using Deep Research API
|
||||
const result = await this.openAiApiClient.responses.create(requestParams);
|
||||
|
||||
// Extract the answer
|
||||
const answer = result.choices[0].message.content || '';
|
||||
|
||||
// Parse sources from the response (OpenAI often includes URLs in markdown format)
|
||||
// Extract the answer from output items
|
||||
let answer = '';
|
||||
const sources: Array<{ url: string; title: string; snippet: string }> = [];
|
||||
const searchQueries: string[] = [];
|
||||
|
||||
// Process output items
|
||||
for (const item of result.output || []) {
|
||||
// Extract message content
|
||||
if (item.type === 'message' && 'content' in item) {
|
||||
const messageItem = item as any;
|
||||
for (const contentItem of messageItem.content || []) {
|
||||
if (contentItem.type === 'output_text' && 'text' in contentItem) {
|
||||
answer += contentItem.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract web search queries
|
||||
if (item.type === 'web_search_call' && 'action' in item) {
|
||||
const searchItem = item as any;
|
||||
if (searchItem.action && searchItem.action.type === 'search' && 'query' in searchItem.action) {
|
||||
searchQueries.push(searchItem.action.query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse sources from markdown links in the answer
|
||||
const urlRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
@@ -302,27 +316,10 @@ export class OpenAiProvider extends MultiModalModel {
|
||||
sources.push({
|
||||
title: match[1],
|
||||
url: match[2],
|
||||
snippet: '' // OpenAI doesn't provide snippets in standard responses
|
||||
snippet: ''
|
||||
});
|
||||
}
|
||||
|
||||
// Extract search queries if tools were used
|
||||
const searchQueries: string[] = [];
|
||||
if (result.choices[0].message.tool_calls) {
|
||||
for (const toolCall of result.choices[0].message.tool_calls) {
|
||||
if ('function' in toolCall && toolCall.function.name === 'web_search') {
|
||||
try {
|
||||
const args = JSON.parse(toolCall.function.arguments);
|
||||
if (args.query) {
|
||||
searchQueries.push(args.query);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parsing errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
answer,
|
||||
sources,
|
||||
@@ -338,4 +335,121 @@ export class OpenAiProvider extends MultiModalModel {
|
||||
throw new Error(`Failed to perform research: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generation using OpenAI's gpt-image-1 or DALL-E models
|
||||
*/
|
||||
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
||||
const model = optionsArg.model || this.options.imageModel || 'gpt-image-1';
|
||||
|
||||
try {
|
||||
const requestParams: any = {
|
||||
model,
|
||||
prompt: optionsArg.prompt,
|
||||
n: optionsArg.n || 1,
|
||||
};
|
||||
|
||||
// Add gpt-image-1 specific parameters
|
||||
if (model === 'gpt-image-1') {
|
||||
if (optionsArg.quality) requestParams.quality = optionsArg.quality;
|
||||
if (optionsArg.size) requestParams.size = optionsArg.size;
|
||||
if (optionsArg.background) requestParams.background = optionsArg.background;
|
||||
if (optionsArg.outputFormat) requestParams.output_format = optionsArg.outputFormat;
|
||||
if (optionsArg.outputCompression !== undefined) requestParams.output_compression = optionsArg.outputCompression;
|
||||
if (optionsArg.moderation) requestParams.moderation = optionsArg.moderation;
|
||||
if (optionsArg.stream !== undefined) requestParams.stream = optionsArg.stream;
|
||||
if (optionsArg.partialImages !== undefined) requestParams.partial_images = optionsArg.partialImages;
|
||||
} else if (model === 'dall-e-3') {
|
||||
// DALL-E 3 specific parameters
|
||||
if (optionsArg.quality) requestParams.quality = optionsArg.quality;
|
||||
if (optionsArg.size) requestParams.size = optionsArg.size;
|
||||
if (optionsArg.style) requestParams.style = optionsArg.style;
|
||||
requestParams.response_format = 'b64_json'; // Always use base64 for consistency
|
||||
} else if (model === 'dall-e-2') {
|
||||
// DALL-E 2 specific parameters
|
||||
if (optionsArg.size) requestParams.size = optionsArg.size;
|
||||
requestParams.response_format = 'b64_json';
|
||||
}
|
||||
|
||||
const result = await this.openAiApiClient.images.generate(requestParams);
|
||||
|
||||
const images = (result.data || []).map(img => ({
|
||||
b64_json: img.b64_json,
|
||||
url: img.url,
|
||||
revisedPrompt: img.revised_prompt
|
||||
}));
|
||||
|
||||
return {
|
||||
images,
|
||||
metadata: {
|
||||
model,
|
||||
quality: result.quality,
|
||||
size: result.size,
|
||||
outputFormat: result.output_format,
|
||||
tokensUsed: result.usage?.total_tokens
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Image generation error:', error);
|
||||
throw new Error(`Failed to generate image: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Image editing using OpenAI's gpt-image-1 or DALL-E 2 models
|
||||
*/
|
||||
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
||||
const model = optionsArg.model || this.options.imageModel || 'gpt-image-1';
|
||||
|
||||
try {
|
||||
const requestParams: any = {
|
||||
model,
|
||||
image: optionsArg.image,
|
||||
prompt: optionsArg.prompt,
|
||||
n: optionsArg.n || 1,
|
||||
};
|
||||
|
||||
// Add mask if provided
|
||||
if (optionsArg.mask) {
|
||||
requestParams.mask = optionsArg.mask;
|
||||
}
|
||||
|
||||
// Add gpt-image-1 specific parameters
|
||||
if (model === 'gpt-image-1') {
|
||||
if (optionsArg.quality) requestParams.quality = optionsArg.quality;
|
||||
if (optionsArg.size) requestParams.size = optionsArg.size;
|
||||
if (optionsArg.background) requestParams.background = optionsArg.background;
|
||||
if (optionsArg.outputFormat) requestParams.output_format = optionsArg.outputFormat;
|
||||
if (optionsArg.outputCompression !== undefined) requestParams.output_compression = optionsArg.outputCompression;
|
||||
if (optionsArg.stream !== undefined) requestParams.stream = optionsArg.stream;
|
||||
if (optionsArg.partialImages !== undefined) requestParams.partial_images = optionsArg.partialImages;
|
||||
} else if (model === 'dall-e-2') {
|
||||
// DALL-E 2 specific parameters
|
||||
if (optionsArg.size) requestParams.size = optionsArg.size;
|
||||
requestParams.response_format = 'b64_json';
|
||||
}
|
||||
|
||||
const result = await this.openAiApiClient.images.edit(requestParams);
|
||||
|
||||
const images = (result.data || []).map(img => ({
|
||||
b64_json: img.b64_json,
|
||||
url: img.url,
|
||||
revisedPrompt: img.revised_prompt
|
||||
}));
|
||||
|
||||
return {
|
||||
images,
|
||||
metadata: {
|
||||
model,
|
||||
quality: result.quality,
|
||||
size: result.size,
|
||||
outputFormat: result.output_format,
|
||||
tokensUsed: result.usage?.total_tokens
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Image edit error:', error);
|
||||
throw new Error(`Failed to edit image: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,16 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||||
import type { ChatOptions, ChatResponse, ChatMessage, ResearchOptions, ResearchResponse } from './abstract.classes.multimodal.js';
|
||||
import type {
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatMessage,
|
||||
ResearchOptions,
|
||||
ResearchResponse,
|
||||
ImageGenerateOptions,
|
||||
ImageEditOptions,
|
||||
ImageResponse
|
||||
} from './abstract.classes.multimodal.js';
|
||||
|
||||
export interface IPerplexityProviderOptions {
|
||||
perplexityToken: string;
|
||||
@@ -233,4 +242,18 @@ export class PerplexityProvider extends MultiModalModel {
|
||||
throw new Error(`Failed to perform research: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generation is not supported by Perplexity
|
||||
*/
|
||||
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image generation is not supported by Perplexity. Please use OpenAI provider for image generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image editing is not supported by Perplexity
|
||||
*/
|
||||
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image editing is not supported by Perplexity. Please use OpenAI provider for image editing.');
|
||||
}
|
||||
}
|
@@ -1,7 +1,16 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||||
import type { ChatOptions, ChatResponse, ChatMessage, ResearchOptions, ResearchResponse } from './abstract.classes.multimodal.js';
|
||||
import type {
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatMessage,
|
||||
ResearchOptions,
|
||||
ResearchResponse,
|
||||
ImageGenerateOptions,
|
||||
ImageEditOptions,
|
||||
ImageResponse
|
||||
} from './abstract.classes.multimodal.js';
|
||||
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
|
||||
|
||||
export interface IXAIProviderOptions {
|
||||
@@ -185,4 +194,18 @@ export class XAIProvider extends MultiModalModel {
|
||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||
throw new Error('Research capabilities are not yet supported by xAI provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image generation is not supported by xAI
|
||||
*/
|
||||
public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image generation is not supported by xAI. Please use OpenAI provider for image generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Image editing is not supported by xAI
|
||||
*/
|
||||
public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
|
||||
throw new Error('Image editing is not supported by xAI. Please use OpenAI provider for image editing.');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user