Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8a2a3ff1b | |||
| cbc9d8d45b | |||
| d52e6ae67d | |||
| b9745a1869 | |||
| af3b61cf74 | |||
| 8666876879 |
1
.serena/.gitignore
vendored
Normal file
1
.serena/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/cache
|
||||
67
.serena/project.yml
Normal file
67
.serena/project.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
|
||||
# * For C, use cpp
|
||||
# * For JavaScript, use typescript
|
||||
# Special requirements:
|
||||
# * csharp: Requires the presence of a .sln file in the project folder.
|
||||
language: typescript
|
||||
|
||||
# whether to use the project's gitignore file to ignore files
|
||||
# Added on 2025-04-07
|
||||
ignore_all_files_in_gitignore: true
|
||||
# list of additional paths to ignore
|
||||
# same syntax as gitignore, so you can use * and **
|
||||
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
||||
# Added (renamed) on 2025-04-07
|
||||
ignored_paths: []
|
||||
|
||||
# whether the project is in read-only mode
|
||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
#
|
||||
# * `activate_project`: Activates a project by name.
|
||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||
# * `delete_lines`: Deletes a range of lines within a file.
|
||||
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||
# * `execute_shell_command`: Executes a shell command.
|
||||
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||
# Should only be used in settings where the system prompt cannot be set,
|
||||
# e.g. in clients you have no control over, like Claude Desktop.
|
||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||
# * `read_file`: Reads a file within the project directory.
|
||||
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||
# * `remove_project`: Removes a project from the Serena configuration.
|
||||
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||
# * `switch_modes`: Activates modes by providing a list of their names
|
||||
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
excluded_tools: []
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
project_name: "smartai"
|
||||
27
changelog.md
27
changelog.md
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-10-30 - 0.8.0 - feat(provider.anthropic)
|
||||
Add extended thinking modes to AnthropicProvider and apply thinking budgets to API calls
|
||||
|
||||
- Introduce IAnthropicProviderOptions.extendedThinking to configure thinking modes: 'quick' | 'normal' | 'deep' | 'off'.
|
||||
- Add getThinkingConfig() helper mapping modes to token budgets (quick=2048, normal=8000, deep=16000, off=0).
|
||||
- Apply thinking configuration to Anthropic API calls (chat, chatStream, vision, document, research) and increase max_tokens where appropriate (up to 20000).
|
||||
- Add comprehensive tests (test/test.thinking.anthropic.ts) and update readme.hints.md with usage examples and recommendations.
|
||||
- Add .claude/settings.local.json for local assistant permissions used in development/testing.
|
||||
|
||||
## 2025-10-10 - 0.7.7 - fix(MultiModalModel)
|
||||
Lazy-load SmartPdf and guard document processing across providers; ensure SmartPdf is initialized only when needed
|
||||
|
||||
- Make SmartPdf lazy-loaded: smartpdfInstance is now nullable and no longer started automatically in start()
|
||||
- Add ensureSmartpdfReady() to initialize and start SmartPdf on demand before document processing
|
||||
- Providers updated (OpenAI, Anthropic, Ollama, xAI) to call ensureSmartpdfReady() and use the smartpdfInstance for PDF -> image conversion
|
||||
- stop() now cleans up and nullifies smartpdfInstance to release resources
|
||||
- Avoids starting a browser/process unless document() is actually used (reduces unnecessary resource usage)
|
||||
- Add local Claude permissions file (.claude/settings.local.json) for tooling/configuration
|
||||
|
||||
## 2025-10-09 - 0.7.6 - fix(provider.elevenlabs)
|
||||
Provide default ElevenLabs TTS voice fallback and add local tool/project configs
|
||||
|
||||
- ElevenLabsProvider: fallback to Samara voice id ('19STyYD15bswVz51nqLf') when no voiceId or defaultVoiceId is provided — avoids throwing an error on TTS calls.
|
||||
- ElevenLabsProvider: continue to use 'eleven_v3' as the default model for TTS.
|
||||
- Add .claude/settings.local.json with expanded allowed permissions for local tooling and web search.
|
||||
- Add .serena/project.yml and .serena/.gitignore to include Serena project configuration and ignore cache.
|
||||
|
||||
## 2025-10-08 - 0.7.5 - fix(provider.elevenlabs)
|
||||
Update ElevenLabs default TTS model to eleven_v3 and add local Claude permissions file
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartai",
|
||||
"version": "0.7.5",
|
||||
"version": "0.8.0",
|
||||
"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",
|
||||
|
||||
184
readme.hints.md
184
readme.hints.md
@@ -1 +1,183 @@
|
||||
|
||||
# SmartAI Project Hints
|
||||
|
||||
## Anthropic Extended Thinking Feature
|
||||
|
||||
### Overview
|
||||
The Anthropic provider now supports extended thinking by default across all methods. Extended thinking enables Claude to spend more time reasoning about complex problems before generating responses, leading to higher quality answers for difficult questions.
|
||||
|
||||
### Configuration
|
||||
|
||||
Extended thinking is configured at the provider level during instantiation:
|
||||
|
||||
```typescript
|
||||
import * as smartai from '@push.rocks/smartai';
|
||||
|
||||
const provider = new smartai.AnthropicProvider({
|
||||
anthropicToken: 'your-token-here',
|
||||
extendedThinking: 'normal', // Options: 'quick' | 'normal' | 'deep' | 'off'
|
||||
});
|
||||
```
|
||||
|
||||
### Thinking Modes
|
||||
|
||||
The `extendedThinking` parameter accepts four modes:
|
||||
|
||||
| Mode | Budget Tokens | Use Case |
|
||||
|------|---------------|----------|
|
||||
| `'quick'` | 2,048 | Lightweight reasoning for simple queries |
|
||||
| `'normal'` | 8,000 | **Default** - Balanced reasoning for most tasks |
|
||||
| `'deep'` | 16,000 | Complex reasoning for difficult problems |
|
||||
| `'off'` | 0 | Disable extended thinking |
|
||||
|
||||
**Default Behavior**: If `extendedThinking` is not specified, it defaults to `'normal'` mode (8,000 tokens).
|
||||
|
||||
### Supported Methods
|
||||
|
||||
Extended thinking is automatically applied to all Anthropic provider methods:
|
||||
- `chat()` - Synchronous chat
|
||||
- `chatStream()` - Streaming chat
|
||||
- `vision()` - Image analysis
|
||||
- `document()` - PDF document processing
|
||||
- `research()` - Web research with citations
|
||||
|
||||
### Token Budget Constraints
|
||||
|
||||
**Important**: The thinking budget must be less than `max_tokens` for the API call. The current `max_tokens` values are:
|
||||
|
||||
- `chatStream()`: 20,000 tokens (sufficient for all modes ✓)
|
||||
- `chat()`: 20,000 tokens (sufficient for all modes ✓)
|
||||
- `vision()`: 10,000 tokens (sufficient for all modes ✓)
|
||||
- `document()`: 20,000 tokens (sufficient for all modes ✓)
|
||||
- `research()`: 20,000 tokens for all searchDepth levels (sufficient ✓)
|
||||
|
||||
### Performance and Cost Implications
|
||||
|
||||
**Token Usage**:
|
||||
- You are charged for the **full thinking tokens** generated, not just the summary
|
||||
- Higher thinking budgets may result in more thorough reasoning but increased costs
|
||||
- The budget is a **target**, not a strict limit - actual usage may vary
|
||||
|
||||
**Response Quality**:
|
||||
- `'quick'`: Fast responses, basic reasoning
|
||||
- `'normal'`: Good balance between quality and speed (recommended for most use cases)
|
||||
- `'deep'`: Highest quality reasoning for complex problems, slower responses
|
||||
|
||||
**Recommendations**:
|
||||
- Start with `'normal'` (default) for general usage
|
||||
- Use `'deep'` for complex analytical tasks, philosophy, mathematics, or research
|
||||
- Use `'quick'` for simple factual queries where deep reasoning isn't needed
|
||||
- Use `'off'` only if you want traditional Claude behavior without extended thinking
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Example 1: Default (Normal Mode)
|
||||
```typescript
|
||||
const provider = new smartai.AnthropicProvider({
|
||||
anthropicToken: process.env.ANTHROPIC_TOKEN,
|
||||
// extendedThinking defaults to 'normal'
|
||||
});
|
||||
|
||||
await provider.start();
|
||||
|
||||
const response = await provider.chat({
|
||||
systemMessage: 'You are a helpful assistant.',
|
||||
userMessage: 'Explain the implications of quantum computing.',
|
||||
messageHistory: [],
|
||||
});
|
||||
```
|
||||
|
||||
#### Example 2: Deep Thinking for Complex Analysis
|
||||
```typescript
|
||||
const provider = new smartai.AnthropicProvider({
|
||||
anthropicToken: process.env.ANTHROPIC_TOKEN,
|
||||
extendedThinking: 'deep', // 16,000 token budget
|
||||
});
|
||||
|
||||
await provider.start();
|
||||
|
||||
const response = await provider.chat({
|
||||
systemMessage: 'You are a philosopher and ethicist.',
|
||||
userMessage: 'Analyze the trolley problem from multiple ethical frameworks.',
|
||||
messageHistory: [],
|
||||
});
|
||||
```
|
||||
|
||||
#### Example 3: Quick Mode for Simple Queries
|
||||
```typescript
|
||||
const provider = new smartai.AnthropicProvider({
|
||||
anthropicToken: process.env.ANTHROPIC_TOKEN,
|
||||
extendedThinking: 'quick', // 2,048 token budget
|
||||
});
|
||||
|
||||
await provider.start();
|
||||
|
||||
const response = await provider.chat({
|
||||
systemMessage: 'You are a helpful assistant.',
|
||||
userMessage: 'What is the capital of France?',
|
||||
messageHistory: [],
|
||||
});
|
||||
```
|
||||
|
||||
#### Example 4: Disable Thinking
|
||||
```typescript
|
||||
const provider = new smartai.AnthropicProvider({
|
||||
anthropicToken: process.env.ANTHROPIC_TOKEN,
|
||||
extendedThinking: 'off', // No extended thinking
|
||||
});
|
||||
|
||||
await provider.start();
|
||||
|
||||
const response = await provider.chat({
|
||||
systemMessage: 'You are a helpful assistant.',
|
||||
userMessage: 'Tell me a joke.',
|
||||
messageHistory: [],
|
||||
});
|
||||
```
|
||||
|
||||
#### Example 5: Extended Thinking with Vision
|
||||
```typescript
|
||||
const provider = new smartai.AnthropicProvider({
|
||||
anthropicToken: process.env.ANTHROPIC_TOKEN,
|
||||
extendedThinking: 'normal',
|
||||
});
|
||||
|
||||
await provider.start();
|
||||
|
||||
const imageBuffer = await fs.promises.readFile('./image.jpg');
|
||||
const analysis = await provider.vision({
|
||||
image: imageBuffer,
|
||||
prompt: 'Analyze this image in detail and explain what you see.',
|
||||
});
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Comprehensive tests for extended thinking are available in:
|
||||
- `test/test.thinking.anthropic.ts` - Tests all thinking modes
|
||||
|
||||
Run tests with:
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Run specific thinking tests:
|
||||
```bash
|
||||
npx tstest test/test.thinking.anthropic.ts --verbose
|
||||
```
|
||||
|
||||
### API Reference
|
||||
|
||||
According to Anthropic's documentation:
|
||||
- Extended thinking is supported on Claude Sonnet 4.5, 4, 3.7, Haiku 4.5, and Opus 4.1, 4
|
||||
- The current model used is `claude-sonnet-4-5-20250929`
|
||||
- Minimum thinking budget is 1,024 tokens
|
||||
- Thinking budget must be less than `max_tokens`
|
||||
|
||||
### Implementation Details
|
||||
|
||||
The extended thinking feature is implemented via:
|
||||
1. **Interface**: `IAnthropicProviderOptions.extendedThinking` property
|
||||
2. **Helper Method**: `getThinkingConfig()` private method that maps modes to token budgets
|
||||
3. **API Parameter**: Adds `thinking: { type: 'enabled', budget_tokens: number }` to all API calls
|
||||
|
||||
The thinking configuration is applied automatically to all API calls when the provider is instantiated.
|
||||
|
||||
151
test/test.thinking.anthropic.ts
Normal file
151
test/test.thinking.anthropic.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
|
||||
const testQenv = new qenv.Qenv('./', './.nogit/');
|
||||
|
||||
import * as smartai from '../ts/index.js';
|
||||
|
||||
let anthropicProviderQuick: smartai.AnthropicProvider;
|
||||
let anthropicProviderNormal: smartai.AnthropicProvider;
|
||||
let anthropicProviderDeep: smartai.AnthropicProvider;
|
||||
let anthropicProviderOff: smartai.AnthropicProvider;
|
||||
|
||||
// Test 'quick' mode
|
||||
tap.test('Extended Thinking: should create Anthropic provider with quick mode', async () => {
|
||||
anthropicProviderQuick = new smartai.AnthropicProvider({
|
||||
anthropicToken: await testQenv.getEnvVarOnDemand('ANTHROPIC_TOKEN'),
|
||||
extendedThinking: 'quick',
|
||||
});
|
||||
await anthropicProviderQuick.start();
|
||||
expect(anthropicProviderQuick).toBeInstanceOf(smartai.AnthropicProvider);
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should chat with quick mode (2048 tokens)', async () => {
|
||||
const userMessage = 'Explain quantum entanglement in simple terms.';
|
||||
const response = await anthropicProviderQuick.chat({
|
||||
systemMessage: 'You are a helpful physics teacher.',
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
});
|
||||
console.log(`Quick Mode - User: ${userMessage}`);
|
||||
console.log(`Quick Mode - Response length: ${response.message.length} chars`);
|
||||
expect(response.role).toEqual('assistant');
|
||||
expect(response.message).toBeTruthy();
|
||||
expect(response.message.toLowerCase()).toInclude('quantum');
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should stop quick mode provider', async () => {
|
||||
await anthropicProviderQuick.stop();
|
||||
});
|
||||
|
||||
// Test 'normal' mode (default)
|
||||
tap.test('Extended Thinking: should create Anthropic provider with normal mode (default)', async () => {
|
||||
anthropicProviderNormal = new smartai.AnthropicProvider({
|
||||
anthropicToken: await testQenv.getEnvVarOnDemand('ANTHROPIC_TOKEN'),
|
||||
// extendedThinking not specified, should default to 'normal'
|
||||
});
|
||||
await anthropicProviderNormal.start();
|
||||
expect(anthropicProviderNormal).toBeInstanceOf(smartai.AnthropicProvider);
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should chat with normal mode (8000 tokens default)', async () => {
|
||||
const userMessage = 'What are the implications of the P vs NP problem?';
|
||||
const response = await anthropicProviderNormal.chat({
|
||||
systemMessage: 'You are a helpful computer science expert.',
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
});
|
||||
console.log(`Normal Mode - User: ${userMessage}`);
|
||||
console.log(`Normal Mode - Response length: ${response.message.length} chars`);
|
||||
expect(response.role).toEqual('assistant');
|
||||
expect(response.message).toBeTruthy();
|
||||
expect(response.message.length).toBeGreaterThan(50);
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should stop normal mode provider', async () => {
|
||||
await anthropicProviderNormal.stop();
|
||||
});
|
||||
|
||||
// Test 'deep' mode
|
||||
tap.test('Extended Thinking: should create Anthropic provider with deep mode', async () => {
|
||||
anthropicProviderDeep = new smartai.AnthropicProvider({
|
||||
anthropicToken: await testQenv.getEnvVarOnDemand('ANTHROPIC_TOKEN'),
|
||||
extendedThinking: 'deep',
|
||||
});
|
||||
await anthropicProviderDeep.start();
|
||||
expect(anthropicProviderDeep).toBeInstanceOf(smartai.AnthropicProvider);
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should chat with deep mode (16000 tokens)', async () => {
|
||||
const userMessage = 'Analyze the philosophical implications of artificial consciousness.';
|
||||
const response = await anthropicProviderDeep.chat({
|
||||
systemMessage: 'You are a philosopher and cognitive scientist.',
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
});
|
||||
console.log(`Deep Mode - User: ${userMessage}`);
|
||||
console.log(`Deep Mode - Response length: ${response.message.length} chars`);
|
||||
expect(response.role).toEqual('assistant');
|
||||
expect(response.message).toBeTruthy();
|
||||
expect(response.message.length).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should stop deep mode provider', async () => {
|
||||
await anthropicProviderDeep.stop();
|
||||
});
|
||||
|
||||
// Test 'off' mode
|
||||
tap.test('Extended Thinking: should create Anthropic provider with thinking disabled', async () => {
|
||||
anthropicProviderOff = new smartai.AnthropicProvider({
|
||||
anthropicToken: await testQenv.getEnvVarOnDemand('ANTHROPIC_TOKEN'),
|
||||
extendedThinking: 'off',
|
||||
});
|
||||
await anthropicProviderOff.start();
|
||||
expect(anthropicProviderOff).toBeInstanceOf(smartai.AnthropicProvider);
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should chat with thinking disabled', async () => {
|
||||
const userMessage = 'What is 2 + 2?';
|
||||
const response = await anthropicProviderOff.chat({
|
||||
systemMessage: 'You are a helpful assistant.',
|
||||
userMessage: userMessage,
|
||||
messageHistory: [],
|
||||
});
|
||||
console.log(`Thinking Off - User: ${userMessage}`);
|
||||
console.log(`Thinking Off - Response: ${response.message}`);
|
||||
expect(response.role).toEqual('assistant');
|
||||
expect(response.message).toBeTruthy();
|
||||
expect(response.message).toInclude('4');
|
||||
});
|
||||
|
||||
tap.test('Extended Thinking: should stop off mode provider', async () => {
|
||||
await anthropicProviderOff.stop();
|
||||
});
|
||||
|
||||
// Test with vision method
|
||||
tap.test('Extended Thinking: should work with vision method', async () => {
|
||||
const provider = new smartai.AnthropicProvider({
|
||||
anthropicToken: await testQenv.getEnvVarOnDemand('ANTHROPIC_TOKEN'),
|
||||
extendedThinking: 'normal',
|
||||
});
|
||||
await provider.start();
|
||||
|
||||
// Create a simple test image (1x1 red pixel PNG)
|
||||
const redPixelPng = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==',
|
||||
'base64'
|
||||
);
|
||||
|
||||
const response = await provider.vision({
|
||||
image: redPixelPng,
|
||||
prompt: 'What color is this image?',
|
||||
});
|
||||
|
||||
console.log(`Vision with Thinking - Response: ${response}`);
|
||||
expect(response).toBeTruthy();
|
||||
expect(response.toLowerCase()).toInclude('red');
|
||||
|
||||
await provider.stop();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartai',
|
||||
version: '0.7.5',
|
||||
version: '0.8.0',
|
||||
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.'
|
||||
}
|
||||
|
||||
@@ -111,19 +111,30 @@ export interface ImageResponse {
|
||||
export abstract class MultiModalModel {
|
||||
/**
|
||||
* SmartPdf instance for document processing
|
||||
* Shared across all methods that need PDF functionality
|
||||
* Lazy-loaded only when PDF processing is needed to avoid starting browser unnecessarily
|
||||
*/
|
||||
protected smartpdfInstance: plugins.smartpdf.SmartPdf;
|
||||
protected smartpdfInstance: plugins.smartpdf.SmartPdf | null = null;
|
||||
|
||||
/**
|
||||
* Ensures SmartPdf instance is initialized and ready
|
||||
* Call this before using smartpdfInstance in document processing methods
|
||||
*/
|
||||
protected async ensureSmartpdfReady(): Promise<void> {
|
||||
if (!this.smartpdfInstance) {
|
||||
this.smartpdfInstance = new plugins.smartpdf.SmartPdf();
|
||||
await this.smartpdfInstance.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the model and any necessary resources
|
||||
* Should be called before using any other methods
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
this.smartpdfInstance = new plugins.smartpdf.SmartPdf();
|
||||
await this.smartpdfInstance.start();
|
||||
// SmartPdf is now lazy-loaded only when needed for PDF processing
|
||||
// This avoids starting a browser unless document() method is actually used
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up any resources used by the model
|
||||
* Should be called when the model is no longer needed
|
||||
@@ -131,6 +142,7 @@ export abstract class MultiModalModel {
|
||||
public async stop(): Promise<void> {
|
||||
if (this.smartpdfInstance) {
|
||||
await this.smartpdfInstance.stop();
|
||||
this.smartpdfInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface IAnthropicProviderOptions {
|
||||
enableWebSearch?: boolean;
|
||||
searchDomainAllowList?: string[];
|
||||
searchDomainBlockList?: string[];
|
||||
extendedThinking?: 'quick' | 'normal' | 'deep' | 'off';
|
||||
}
|
||||
|
||||
export class AnthropicProvider extends MultiModalModel {
|
||||
@@ -42,6 +43,25 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
await super.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the thinking configuration based on provider options.
|
||||
* Defaults to 'normal' mode (8000 tokens) if not specified.
|
||||
*/
|
||||
private getThinkingConfig(): { type: 'enabled'; budget_tokens: number } | undefined {
|
||||
const mode = this.options.extendedThinking ?? 'normal';
|
||||
|
||||
const budgetMap = {
|
||||
quick: 2048,
|
||||
normal: 8000,
|
||||
deep: 16000,
|
||||
off: 0,
|
||||
};
|
||||
|
||||
const budget = budgetMap[mode];
|
||||
|
||||
return budget > 0 ? { type: 'enabled', budget_tokens: budget } : undefined;
|
||||
}
|
||||
|
||||
public async chatStream(input: ReadableStream<Uint8Array>): Promise<ReadableStream<string>> {
|
||||
// Create a TextDecoder to handle incoming chunks
|
||||
const decoder = new TextDecoder();
|
||||
@@ -76,12 +96,14 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
|
||||
// If we have a complete message, send it to Anthropic
|
||||
if (currentMessage) {
|
||||
const thinkingConfig = this.getThinkingConfig();
|
||||
const stream = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
messages: [{ role: currentMessage.role, content: currentMessage.content }],
|
||||
system: '',
|
||||
stream: true,
|
||||
max_tokens: 4000,
|
||||
max_tokens: 20000,
|
||||
...(thinkingConfig && { thinking: thinkingConfig }),
|
||||
});
|
||||
|
||||
// Process each chunk from Anthropic
|
||||
@@ -120,6 +142,7 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
content: msg.content
|
||||
}));
|
||||
|
||||
const thinkingConfig = this.getThinkingConfig();
|
||||
const result = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
system: optionsArg.systemMessage,
|
||||
@@ -127,7 +150,8 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
...messages,
|
||||
{ role: 'user' as const, content: optionsArg.userMessage }
|
||||
],
|
||||
max_tokens: 4000,
|
||||
max_tokens: 20000,
|
||||
...(thinkingConfig && { thinking: thinkingConfig }),
|
||||
});
|
||||
|
||||
// Extract text content from the response
|
||||
@@ -167,13 +191,15 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
}
|
||||
];
|
||||
|
||||
const thinkingConfig = this.getThinkingConfig();
|
||||
const result = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content
|
||||
}],
|
||||
max_tokens: 1024
|
||||
max_tokens: 10000,
|
||||
...(thinkingConfig && { thinking: thinkingConfig }),
|
||||
});
|
||||
|
||||
// Extract text content from the response
|
||||
@@ -192,11 +218,14 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
pdfDocuments: Uint8Array[];
|
||||
messageHistory: ChatMessage[];
|
||||
}): Promise<{ message: any }> {
|
||||
// Ensure SmartPdf is initialized before processing documents
|
||||
await this.ensureSmartpdfReady();
|
||||
|
||||
// Convert PDF documents to images using SmartPDF
|
||||
let documentImageBytesArray: Uint8Array[] = [];
|
||||
|
||||
for (const pdfDocument of optionsArg.pdfDocuments) {
|
||||
const documentImageArray = await this.smartpdfInstance.convertPDFToPngBytes(pdfDocument);
|
||||
const documentImageArray = await this.smartpdfInstance!.convertPDFToPngBytes(pdfDocument);
|
||||
documentImageBytesArray = documentImageBytesArray.concat(documentImageArray);
|
||||
}
|
||||
|
||||
@@ -226,6 +255,7 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
});
|
||||
}
|
||||
|
||||
const thinkingConfig = this.getThinkingConfig();
|
||||
const result = await this.anthropicApiClient.messages.create({
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
system: optionsArg.systemMessage,
|
||||
@@ -233,7 +263,8 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
...messages,
|
||||
{ role: 'user', content }
|
||||
],
|
||||
max_tokens: 4096
|
||||
max_tokens: 20000,
|
||||
...(thinkingConfig && { thinking: thinkingConfig }),
|
||||
});
|
||||
|
||||
// Extract text content from the response
|
||||
@@ -283,8 +314,8 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
}
|
||||
|
||||
// Configure the request based on search depth
|
||||
const maxTokens = optionsArg.searchDepth === 'deep' ? 8192 :
|
||||
optionsArg.searchDepth === 'advanced' ? 6144 : 4096;
|
||||
const maxTokens = optionsArg.searchDepth === 'deep' ? 20000 :
|
||||
optionsArg.searchDepth === 'advanced' ? 20000 : 20000;
|
||||
|
||||
// Create the research request
|
||||
const requestParams: any = {
|
||||
@@ -305,6 +336,12 @@ export class AnthropicProvider extends MultiModalModel {
|
||||
requestParams.tools = tools;
|
||||
}
|
||||
|
||||
// Add thinking configuration if enabled
|
||||
const thinkingConfig = this.getThinkingConfig();
|
||||
if (thinkingConfig) {
|
||||
requestParams.thinking = thinkingConfig;
|
||||
}
|
||||
|
||||
// Execute the research request
|
||||
const result = await this.anthropicApiClient.messages.create(requestParams);
|
||||
|
||||
|
||||
@@ -55,11 +55,8 @@ export class ElevenLabsProvider extends MultiModalModel {
|
||||
modelId?: string;
|
||||
voiceSettings?: IElevenLabsVoiceSettings;
|
||||
}): Promise<NodeJS.ReadableStream> {
|
||||
const voiceId = optionsArg.voiceId || this.options.defaultVoiceId;
|
||||
|
||||
if (!voiceId) {
|
||||
throw new Error('Voice ID is required for ElevenLabs TTS. Please provide voiceId in the method call or set defaultVoiceId in provider options.');
|
||||
}
|
||||
// Use Samara voice as default fallback
|
||||
const voiceId = optionsArg.voiceId || this.options.defaultVoiceId || '19STyYD15bswVz51nqLf';
|
||||
|
||||
const modelId = optionsArg.modelId || this.options.defaultModelId || 'eleven_v3';
|
||||
|
||||
|
||||
@@ -216,11 +216,14 @@ export class OllamaProvider extends MultiModalModel {
|
||||
pdfDocuments: Uint8Array[];
|
||||
messageHistory: ChatMessage[];
|
||||
}): Promise<{ message: any }> {
|
||||
// Ensure SmartPdf is initialized before processing documents
|
||||
await this.ensureSmartpdfReady();
|
||||
|
||||
// Convert PDF documents to images using SmartPDF
|
||||
let documentImageBytesArray: Uint8Array[] = [];
|
||||
|
||||
for (const pdfDocument of optionsArg.pdfDocuments) {
|
||||
const documentImageArray = await this.smartpdfInstance.convertPDFToPngBytes(pdfDocument);
|
||||
const documentImageArray = await this.smartpdfInstance!.convertPDFToPngBytes(pdfDocument);
|
||||
documentImageBytesArray = documentImageBytesArray.concat(documentImageArray);
|
||||
}
|
||||
|
||||
|
||||
@@ -173,11 +173,14 @@ export class OpenAiProvider extends MultiModalModel {
|
||||
content: any;
|
||||
}[];
|
||||
}) {
|
||||
// Ensure SmartPdf is initialized before processing documents
|
||||
await this.ensureSmartpdfReady();
|
||||
|
||||
let pdfDocumentImageBytesArray: Uint8Array[] = [];
|
||||
|
||||
// Convert each PDF into one or more image byte arrays.
|
||||
for (const pdfDocument of optionsArg.pdfDocuments) {
|
||||
const documentImageArray = await this.smartpdfInstance.convertPDFToPngBytes(pdfDocument);
|
||||
const documentImageArray = await this.smartpdfInstance!.convertPDFToPngBytes(pdfDocument);
|
||||
pdfDocumentImageBytesArray = pdfDocumentImageBytesArray.concat(documentImageArray);
|
||||
}
|
||||
|
||||
|
||||
@@ -149,11 +149,14 @@ export class XAIProvider extends MultiModalModel {
|
||||
pdfDocuments: Uint8Array[];
|
||||
messageHistory: { role: string; content: string; }[];
|
||||
}): Promise<{ message: any }> {
|
||||
// Ensure SmartPdf is initialized before processing documents
|
||||
await this.ensureSmartpdfReady();
|
||||
|
||||
// First convert PDF documents to images
|
||||
let pdfDocumentImageBytesArray: Uint8Array[] = [];
|
||||
|
||||
|
||||
for (const pdfDocument of optionsArg.pdfDocuments) {
|
||||
const documentImageArray = await this.smartpdfInstance.convertPDFToPngBytes(pdfDocument);
|
||||
const documentImageArray = await this.smartpdfInstance!.convertPDFToPngBytes(pdfDocument);
|
||||
pdfDocumentImageBytesArray = pdfDocumentImageBytesArray.concat(documentImageArray);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user