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
|
# 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)
|
## 2025-09-28 - 0.6.1 - fix(provider.anthropic)
|
||||||
Fix Anthropic research tool identifier and add tests + local Claude permissions
|
Fix Anthropic research tool identifier and add tests + local Claude permissions
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartai",
|
"name": "@push.rocks/smartai",
|
||||||
"version": "0.6.1",
|
"version": "0.7.2",
|
||||||
"private": false,
|
"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.",
|
"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",
|
"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:
|
Choose the right provider for your use case:
|
||||||
|
|
||||||
| Provider | Chat | Streaming | TTS | Vision | Documents | Highlights |
|
| Provider | Chat | Streaming | TTS | Vision | Documents | Research | Images | Highlights |
|
||||||
|----------|:----:|:---------:|:---:|:------:|:---------:|------------|
|
|----------|:----:|:---------:|:---:|:------:|:---------:|:--------:|:------:|------------|
|
||||||
| **OpenAI** | ✅ | ✅ | ✅ | ✅ | ✅ | • GPT-4, DALL-E 3<br>• Industry standard<br>• Most features |
|
| **OpenAI** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | • gpt-image-1<br>• DALL-E 3<br>• Deep research API |
|
||||||
| **Anthropic** | ✅ | ✅ | ❌ | ✅ | ✅ | • Claude 3 Opus<br>• Superior reasoning<br>• 200k context |
|
| **Anthropic** | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | • Claude Sonnet 4.5<br>• Superior reasoning<br>• Web search API |
|
||||||
| **Ollama** | ✅ | ✅ | ❌ | ✅ | ✅ | • 100% local<br>• Privacy-first<br>• No API costs |
|
| **Ollama** | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | • 100% local<br>• Privacy-first<br>• No API costs |
|
||||||
| **XAI** | ✅ | ✅ | ❌ | ❌ | ✅ | • Grok models<br>• Real-time data<br>• Uncensored |
|
| **XAI** | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | • Grok models<br>• Real-time data<br>• Uncensored |
|
||||||
| **Perplexity** | ✅ | ✅ | ❌ | ❌ | ❌ | • Web-aware<br>• Research-focused<br>• Citations |
|
| **Perplexity** | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | • Web-aware<br>• Research-focused<br>• Sonar Pro models |
|
||||||
| **Groq** | ✅ | ✅ | ❌ | ❌ | ❌ | • 10x faster<br>• LPU inference<br>• Low latency |
|
| **Groq** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | • 10x faster<br>• LPU inference<br>• Low latency |
|
||||||
| **Exo** | ✅ | ✅ | ❌ | ❌ | ❌ | • Distributed<br>• P2P compute<br>• Decentralized |
|
| **Exo** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | • Distributed<br>• P2P compute<br>• Decentralized |
|
||||||
|
|
||||||
## 🎮 Core Features
|
## 🎮 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
|
### 🔄 Persistent Conversations
|
||||||
|
|
||||||
Maintain context across interactions:
|
Maintain context across interactions:
|
||||||
@@ -447,6 +573,7 @@ export PERPLEXITY_API_KEY=pplx-...
|
|||||||
| **General Purpose** | OpenAI | Most features, stable, well-documented |
|
| **General Purpose** | OpenAI | Most features, stable, well-documented |
|
||||||
| **Complex Reasoning** | Anthropic | Superior logical thinking, safer outputs |
|
| **Complex Reasoning** | Anthropic | Superior logical thinking, safer outputs |
|
||||||
| **Research & Facts** | Perplexity | Web-aware, provides citations |
|
| **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 |
|
| **Speed Critical** | Groq | 10x faster inference, sub-second responses |
|
||||||
| **Privacy Critical** | Ollama | 100% local, no data leaves your servers |
|
| **Privacy Critical** | Ollama | 100% local, no data leaves your servers |
|
||||||
| **Real-time Data** | XAI | Access to current information |
|
| **Real-time Data** | XAI | Access to current information |
|
||||||
@@ -454,8 +581,9 @@ export PERPLEXITY_API_KEY=pplx-...
|
|||||||
|
|
||||||
## 📈 Roadmap
|
## 📈 Roadmap
|
||||||
|
|
||||||
|
- [x] Research & Web Search API
|
||||||
|
- [x] Image generation support (gpt-image-1, DALL-E 3, DALL-E 2)
|
||||||
- [ ] Streaming function calls
|
- [ ] Streaming function calls
|
||||||
- [ ] Image generation support
|
|
||||||
- [ ] Voice input processing
|
- [ ] Voice input processing
|
||||||
- [ ] Fine-tuning integration
|
- [ ] Fine-tuning integration
|
||||||
- [ ] Embedding support
|
- [ ] 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');
|
expect(response.message.toLowerCase()).toInclude('claude test');
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Anthropic: should process vision tasks', async () => {
|
tap.test('Anthropic: should analyze coffee image with latte art', async () => {
|
||||||
// Create a simple test image (1x1 red pixel JPEG)
|
// Test 1: Coffee image from Unsplash by Dani
|
||||||
// This is a valid 1x1 JPEG image
|
const imagePath = './test/testimages/coffee-dani/coffee.jpg';
|
||||||
const redPixelBase64 = '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCwAA8A/9k=';
|
console.log(`Loading coffee image from: ${imagePath}`);
|
||||||
const imageBuffer = Buffer.from(redPixelBase64, 'base64');
|
|
||||||
|
const imageBuffer = await smartfile.fs.toBuffer(imagePath);
|
||||||
|
console.log(`Image loaded, size: ${imageBuffer.length} bytes`);
|
||||||
|
|
||||||
const result = await anthropicProvider.vision({
|
const result = await anthropicProvider.vision({
|
||||||
image: imageBuffer,
|
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(result).toBeTruthy();
|
||||||
expect(typeof result).toEqual('string');
|
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 () => {
|
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 { expect, tap } from '@push.rocks/tapbundle';
|
||||||
import * as qenv from '@push.rocks/qenv';
|
import * as qenv from '@push.rocks/qenv';
|
||||||
import * as smartai from '../ts/index.js';
|
import * as smartai from '../ts/index.js';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const testQenv = new qenv.Qenv('./', './.nogit/');
|
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;
|
let anthropicProvider: smartai.AnthropicProvider;
|
||||||
|
|
||||||
tap.test('Anthropic Research: should initialize provider with web search', async () => {
|
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('- Sources found:', result.sources.length);
|
||||||
console.log('- First 200 chars:', result.answer.substring(0, 200));
|
console.log('- First 200 chars:', result.answer.substring(0, 200));
|
||||||
|
|
||||||
|
await saveResearchResult('basic_research_machine_learning', result);
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
expect(result.answer).toBeTruthy();
|
expect(result.answer).toBeTruthy();
|
||||||
expect(result.answer.toLowerCase()).toInclude('machine learning');
|
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);
|
console.log('- Search queries:', result.searchQueries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await saveResearchResult('web_search_renewable_energy', result);
|
||||||
|
|
||||||
expect(result.answer).toBeTruthy();
|
expect(result.answer).toBeTruthy();
|
||||||
expect(result.answer.toLowerCase()).toInclude('renewable');
|
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('- Answer length:', result.answer.length);
|
||||||
console.log('- Token usage:', result.metadata?.tokensUsed);
|
console.log('- Token usage:', result.metadata?.tokensUsed);
|
||||||
|
|
||||||
|
await saveResearchResult('deep_research_rest_vs_graphql', result);
|
||||||
|
|
||||||
expect(result.answer).toBeTruthy();
|
expect(result.answer).toBeTruthy();
|
||||||
expect(result.answer.length).toBeGreaterThan(300);
|
expect(result.answer.length).toBeGreaterThan(300);
|
||||||
expect(result.answer.toLowerCase()).toInclude('rest');
|
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('- Sources found:', result.sources.length);
|
||||||
console.log('- Answer includes Docker:', result.answer.toLowerCase().includes('docker'));
|
console.log('- Answer includes Docker:', result.answer.toLowerCase().includes('docker'));
|
||||||
|
|
||||||
|
await saveResearchResult('citation_extraction_docker', result);
|
||||||
|
|
||||||
expect(result.answer).toInclude('Docker');
|
expect(result.answer).toInclude('Docker');
|
||||||
|
|
||||||
// Check for URL extraction (both markdown and plain URLs)
|
// 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('- Answer length:', result.answer.length);
|
||||||
console.log('- Applied domain filters (allow: wikipedia, docs.microsoft)');
|
console.log('- Applied domain filters (allow: wikipedia, docs.microsoft)');
|
||||||
|
|
||||||
|
await saveResearchResult('domain_filtering_javascript', result);
|
||||||
|
|
||||||
expect(result.answer).toBeTruthy();
|
expect(result.answer).toBeTruthy();
|
||||||
expect(result.answer.toLowerCase()).toInclude('javascript');
|
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('- Basic tokens:', basicResult.metadata?.tokensUsed);
|
||||||
console.log('- Advanced tokens:', advancedResult.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(basicResult.answer).toBeTruthy();
|
||||||
expect(advancedResult.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');
|
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 () => {
|
tap.test('Anthropic Research: should clean up provider', async () => {
|
||||||
await anthropicProvider.stop();
|
await anthropicProvider.stop();
|
||||||
console.log('Anthropic research provider stopped successfully');
|
console.log('Anthropic research provider stopped successfully');
|
||||||
|
@@ -1,9 +1,24 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@push.rocks/tapbundle';
|
||||||
import * as qenv from '@push.rocks/qenv';
|
import * as qenv from '@push.rocks/qenv';
|
||||||
import * as smartai from '../ts/index.js';
|
import * as smartai from '../ts/index.js';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const testQenv = new qenv.Qenv('./', './.nogit/');
|
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;
|
let openaiProvider: smartai.OpenAiProvider;
|
||||||
|
|
||||||
tap.test('OpenAI Research: should initialize provider with research capabilities', async () => {
|
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('- Sources found:', result.sources.length);
|
||||||
console.log('- First 200 chars:', result.answer.substring(0, 200));
|
console.log('- First 200 chars:', result.answer.substring(0, 200));
|
||||||
|
|
||||||
|
await saveResearchResult('basic_research_typescript', result);
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
expect(result.answer).toBeTruthy();
|
expect(result.answer).toBeTruthy();
|
||||||
expect(result.answer.toLowerCase()).toInclude('typescript');
|
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);
|
console.log('- Search queries used:', result.searchQueries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await saveResearchResult('web_search_ecmascript', result);
|
||||||
|
|
||||||
expect(result.answer).toBeTruthy();
|
expect(result.answer).toBeTruthy();
|
||||||
expect(result.answer.toLowerCase()).toInclude('ecmascript');
|
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('OpenAI Source Extraction:');
|
||||||
console.log('- Sources found:', result.sources.length);
|
console.log('- Sources found:', result.sources.length);
|
||||||
|
|
||||||
|
await saveResearchResult('source_extraction_nodejs', result);
|
||||||
|
|
||||||
if (result.sources.length > 0) {
|
if (result.sources.length > 0) {
|
||||||
console.log('- Example source:', result.sources[0]);
|
console.log('- Example source:', result.sources[0]);
|
||||||
expect(result.sources[0].url).toBeTruthy();
|
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 = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartai',
|
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.'
|
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;
|
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.
|
* Abstract base class for multi-modal AI models.
|
||||||
* Provides a common interface for different AI providers (OpenAI, Anthropic, Perplexity, Ollama)
|
* 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
|
* @throws Error if the provider doesn't support research capabilities
|
||||||
*/
|
*/
|
||||||
public abstract research(optionsArg: ResearchOptions): Promise<ResearchResponse>;
|
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 plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { MultiModalModel } from './abstract.classes.multimodal.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';
|
import type { ImageBlockParam, TextBlockParam } from '@anthropic-ai/sdk/resources/messages';
|
||||||
|
|
||||||
type ContentBlock = ImageBlockParam | TextBlockParam;
|
type ContentBlock = ImageBlockParam | TextBlockParam;
|
||||||
@@ -68,7 +77,7 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
// If we have a complete message, send it to Anthropic
|
// If we have a complete message, send it to Anthropic
|
||||||
if (currentMessage) {
|
if (currentMessage) {
|
||||||
const stream = await this.anthropicApiClient.messages.create({
|
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 }],
|
messages: [{ role: currentMessage.role, content: currentMessage.content }],
|
||||||
system: '',
|
system: '',
|
||||||
stream: true,
|
stream: true,
|
||||||
@@ -112,7 +121,7 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const result = await this.anthropicApiClient.messages.create({
|
const result = await this.anthropicApiClient.messages.create({
|
||||||
model: 'claude-3-opus-20240229',
|
model: 'claude-sonnet-4-5-20250929',
|
||||||
system: optionsArg.systemMessage,
|
system: optionsArg.systemMessage,
|
||||||
messages: [
|
messages: [
|
||||||
...messages,
|
...messages,
|
||||||
@@ -159,7 +168,7 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const result = await this.anthropicApiClient.messages.create({
|
const result = await this.anthropicApiClient.messages.create({
|
||||||
model: 'claude-3-opus-20240229',
|
model: 'claude-sonnet-4-5-20250929',
|
||||||
messages: [{
|
messages: [{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content
|
content
|
||||||
@@ -218,7 +227,7 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.anthropicApiClient.messages.create({
|
const result = await this.anthropicApiClient.messages.create({
|
||||||
model: 'claude-3-opus-20240229',
|
model: 'claude-sonnet-4-5-20250929',
|
||||||
system: optionsArg.systemMessage,
|
system: optionsArg.systemMessage,
|
||||||
messages: [
|
messages: [
|
||||||
...messages,
|
...messages,
|
||||||
@@ -251,23 +260,27 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Build the tool configuration for web search
|
// Build the tool configuration for web search
|
||||||
const tools = this.options.enableWebSearch ? [
|
const tools: any[] = [];
|
||||||
{
|
|
||||||
type: 'web_search_20250305' as const,
|
if (this.options.enableWebSearch) {
|
||||||
name: 'web_search',
|
const webSearchTool: any = {
|
||||||
description: 'Search the web for current information',
|
type: 'web_search_20250305',
|
||||||
input_schema: {
|
name: 'web_search'
|
||||||
type: 'object' as const,
|
};
|
||||||
properties: {
|
|
||||||
query: {
|
// Add optional parameters
|
||||||
type: 'string',
|
if (optionsArg.maxSources) {
|
||||||
description: 'The search query'
|
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
|
// Configure the request based on search depth
|
||||||
const maxTokens = optionsArg.searchDepth === 'deep' ? 8192 :
|
const maxTokens = optionsArg.searchDepth === 'deep' ? 8192 :
|
||||||
@@ -275,7 +288,7 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
|
|
||||||
// Create the research request
|
// Create the research request
|
||||||
const requestParams: any = {
|
const requestParams: any = {
|
||||||
model: 'claude-3-opus-20240229',
|
model: 'claude-sonnet-4-5-20250929',
|
||||||
system: systemMessage,
|
system: systemMessage,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
@@ -290,7 +303,6 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
// Add tools if web search is enabled
|
// Add tools if web search is enabled
|
||||||
if (tools.length > 0) {
|
if (tools.length > 0) {
|
||||||
requestParams.tools = tools;
|
requestParams.tools = tools;
|
||||||
requestParams.tool_choice = { type: 'auto' };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the research request
|
// Execute the research request
|
||||||
@@ -304,11 +316,47 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
// Process content blocks
|
// Process content blocks
|
||||||
for (const block of result.content) {
|
for (const block of result.content) {
|
||||||
if ('text' in block) {
|
if ('text' in block) {
|
||||||
|
// Accumulate text content
|
||||||
answer += block.text;
|
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;
|
const urlRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
||||||
let match: RegExpExecArray | null;
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
@@ -319,39 +367,20 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
snippet: ''
|
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
|
// Check if web search was used based on usage info
|
||||||
if ('tool_use' in result && Array.isArray(result.tool_use)) {
|
const webSearchCount = result.usage?.server_tool_use?.web_search_requests || 0;
|
||||||
for (const toolUse of result.tool_use) {
|
|
||||||
if (toolUse.name === 'web_search' && toolUse.input?.query) {
|
|
||||||
searchQueries.push(toolUse.input.query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
answer,
|
answer,
|
||||||
sources,
|
sources,
|
||||||
searchQueries: searchQueries.length > 0 ? searchQueries : undefined,
|
searchQueries: searchQueries.length > 0 ? searchQueries : undefined,
|
||||||
metadata: {
|
metadata: {
|
||||||
model: 'claude-3-opus-20240229',
|
model: 'claude-sonnet-4-5-20250929',
|
||||||
searchDepth: optionsArg.searchDepth || 'basic',
|
searchDepth: optionsArg.searchDepth || 'basic',
|
||||||
tokensUsed: result.usage?.output_tokens
|
tokensUsed: result.usage?.output_tokens,
|
||||||
|
webSearchesPerformed: webSearchCount
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -359,4 +388,18 @@ export class AnthropicProvider extends MultiModalModel {
|
|||||||
throw new Error(`Failed to perform research: ${error.message}`);
|
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 plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { MultiModalModel } from './abstract.classes.multimodal.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';
|
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
|
||||||
|
|
||||||
export interface IExoProviderOptions {
|
export interface IExoProviderOptions {
|
||||||
@@ -129,4 +138,18 @@ export class ExoProvider extends MultiModalModel {
|
|||||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||||
throw new Error('Research capabilities are not yet supported by Exo provider.');
|
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 plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { MultiModalModel } from './abstract.classes.multimodal.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 {
|
export interface IGroqProviderOptions {
|
||||||
groqToken: string;
|
groqToken: string;
|
||||||
@@ -193,4 +202,18 @@ export class GroqProvider extends MultiModalModel {
|
|||||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||||
throw new Error('Research capabilities are not yet supported by Groq provider.');
|
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 plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { MultiModalModel } from './abstract.classes.multimodal.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 {
|
export interface IOllamaProviderOptions {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
@@ -255,4 +264,18 @@ export class OllamaProvider extends MultiModalModel {
|
|||||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||||
throw new Error('Research capabilities are not yet supported by Ollama provider.');
|
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 { 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 {
|
export interface IOpenaiProviderOptions {
|
||||||
openaiToken: string;
|
openaiToken: string;
|
||||||
@@ -17,6 +23,7 @@ export interface IOpenaiProviderOptions {
|
|||||||
audioModel?: string;
|
audioModel?: string;
|
||||||
visionModel?: string;
|
visionModel?: string;
|
||||||
researchModel?: string;
|
researchModel?: string;
|
||||||
|
imageModel?: string;
|
||||||
enableWebSearch?: boolean;
|
enableWebSearch?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,52 +240,37 @@ export class OpenAiProvider extends MultiModalModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
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;
|
let model: string;
|
||||||
if (optionsArg.searchDepth === 'deep') {
|
if (optionsArg.searchDepth === 'deep') {
|
||||||
model = this.options.researchModel || 'o4-mini-deep-research-2025-06-26';
|
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 {
|
} else {
|
||||||
model = this.options.chatModel || 'gpt-5-mini';
|
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 = {
|
const requestParams: any = {
|
||||||
model,
|
model,
|
||||||
messages: [
|
instructions: systemMessage,
|
||||||
{
|
input: optionsArg.query
|
||||||
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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add web search tools if requested
|
// Add web search tool if requested
|
||||||
if (optionsArg.includeWebSearch || optionsArg.searchDepth === 'deep') {
|
if (optionsArg.includeWebSearch || optionsArg.searchDepth === 'deep') {
|
||||||
requestParams.tools = [
|
requestParams.tools = [
|
||||||
{
|
{
|
||||||
type: 'function',
|
type: 'web_search_preview',
|
||||||
function: {
|
search_context_size: optionsArg.searchDepth === 'deep' ? 'high' :
|
||||||
name: 'web_search',
|
optionsArg.searchDepth === 'advanced' ? 'medium' : 'low'
|
||||||
description: 'Search the web for information',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
query: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'The search query'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ['query']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
requestParams.tool_choice = 'auto';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add background flag for deep research
|
// Add background flag for deep research
|
||||||
@@ -287,14 +279,36 @@ export class OpenAiProvider extends MultiModalModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Execute the research request
|
// Execute the research request using Deep Research API
|
||||||
const result = await this.openAiApiClient.chat.completions.create(requestParams);
|
const result = await this.openAiApiClient.responses.create(requestParams);
|
||||||
|
|
||||||
// Extract the answer
|
// Extract the answer from output items
|
||||||
const answer = result.choices[0].message.content || '';
|
let answer = '';
|
||||||
|
|
||||||
// Parse sources from the response (OpenAI often includes URLs in markdown format)
|
|
||||||
const sources: Array<{ url: string; title: string; snippet: string }> = [];
|
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;
|
const urlRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
||||||
let match: RegExpExecArray | null;
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
@@ -302,27 +316,10 @@ export class OpenAiProvider extends MultiModalModel {
|
|||||||
sources.push({
|
sources.push({
|
||||||
title: match[1],
|
title: match[1],
|
||||||
url: match[2],
|
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 {
|
return {
|
||||||
answer,
|
answer,
|
||||||
sources,
|
sources,
|
||||||
@@ -338,4 +335,121 @@ export class OpenAiProvider extends MultiModalModel {
|
|||||||
throw new Error(`Failed to perform research: ${error.message}`);
|
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 plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { MultiModalModel } from './abstract.classes.multimodal.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 {
|
export interface IPerplexityProviderOptions {
|
||||||
perplexityToken: string;
|
perplexityToken: string;
|
||||||
@@ -233,4 +242,18 @@ export class PerplexityProvider extends MultiModalModel {
|
|||||||
throw new Error(`Failed to perform research: ${error.message}`);
|
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 plugins from './plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from './paths.js';
|
||||||
import { MultiModalModel } from './abstract.classes.multimodal.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';
|
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
|
||||||
|
|
||||||
export interface IXAIProviderOptions {
|
export interface IXAIProviderOptions {
|
||||||
@@ -185,4 +194,18 @@ export class XAIProvider extends MultiModalModel {
|
|||||||
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
|
||||||
throw new Error('Research capabilities are not yet supported by xAI provider.');
|
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