reduce the use of any, part 1

This commit is contained in:
perf3ct 2025-04-16 17:07:54 +00:00
parent bbb382ef65
commit 64f2a93ac0
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
4 changed files with 244 additions and 99 deletions

View File

@ -9,10 +9,36 @@ export interface Message {
content: string; content: string;
name?: string; name?: string;
tool_call_id?: string; tool_call_id?: string;
tool_calls?: ToolCall[] | any[]; tool_calls?: ToolCall[];
sessionId?: string; // Optional session ID for WebSocket communication sessionId?: string; // Optional session ID for WebSocket communication
} }
// Define additional interfaces for tool-related types
export interface ToolChoice {
type: 'none' | 'auto' | 'function';
function?: {
name: string;
};
}
export interface ToolData {
type: 'function';
function: {
name: string;
description: string;
parameters: Record<string, unknown>;
};
}
export interface ToolExecutionInfo {
type: 'start' | 'update' | 'complete' | 'error';
tool: {
name: string;
arguments: Record<string, unknown>;
};
result?: string | Record<string, unknown>;
}
/** /**
* Interface for streaming response chunks * Interface for streaming response chunks
* *
@ -41,23 +67,30 @@ export interface StreamChunk {
* Raw provider-specific data from the original response chunk * Raw provider-specific data from the original response chunk
* This can include thinking state, tool execution info, etc. * This can include thinking state, tool execution info, etc.
*/ */
raw?: any; raw?: Record<string, unknown>;
/** /**
* Tool calls from the LLM (if any) * Tool calls from the LLM (if any)
* These may be accumulated over multiple chunks during streaming * These may be accumulated over multiple chunks during streaming
*/ */
tool_calls?: ToolCall[] | any[]; tool_calls?: ToolCall[];
/** /**
* Tool execution information during streaming * Tool execution information during streaming
* Includes tool name, args, and execution status * Includes tool name, args, and execution status
*/ */
toolExecution?: { toolExecution?: ToolExecutionInfo;
type: 'start' | 'update' | 'complete' | 'error'; }
tool: any;
result?: any; /**
}; * Tool execution status for feedback to models
*/
export interface ToolExecutionStatus {
toolCallId: string;
name: string;
success: boolean;
result: string;
error?: string;
} }
/** /**
@ -108,13 +141,13 @@ export interface ChatCompletionOptions {
* @param isDone Whether this is the final chunk * @param isDone Whether this is the final chunk
* @param originalChunk Optional original provider-specific chunk for advanced usage * @param originalChunk Optional original provider-specific chunk for advanced usage
*/ */
streamCallback?: (text: string, isDone: boolean, originalChunk?: any) => Promise<void> | void; streamCallback?: (text: string, isDone: boolean, originalChunk?: Record<string, unknown>) => Promise<void> | void;
enableTools?: boolean; // Whether to enable tool calling enableTools?: boolean; // Whether to enable tool calling
tools?: any[]; // Tools to provide to the LLM tools?: ToolData[]; // Tools to provide to the LLM
tool_choice?: any; // Tool choice parameter for the LLM tool_choice?: ToolChoice; // Tool choice parameter for the LLM
useAdvancedContext?: boolean; // Whether to use advanced context enrichment useAdvancedContext?: boolean; // Whether to use advanced context enrichment
toolExecutionStatus?: any[]; // Status information about executed tools for feedback toolExecutionStatus?: ToolExecutionStatus[]; // Status information about executed tools for feedback
providerMetadata?: ModelMetadata; // Metadata about the provider and model capabilities providerMetadata?: ModelMetadata; // Metadata about the provider and model capabilities
sessionId?: string; // Session ID for storing tool execution results sessionId?: string; // Session ID for storing tool execution results
@ -177,7 +210,7 @@ export interface ChatResponse {
stream?: (callback: (chunk: StreamChunk) => Promise<void> | void) => Promise<string>; stream?: (callback: (chunk: StreamChunk) => Promise<void> | void) => Promise<string>;
/** Tool calls from the LLM (if tools were used and the model supports them) */ /** Tool calls from the LLM (if tools were used and the model supports them) */
tool_calls?: ToolCall[] | any[]; tool_calls?: ToolCall[];
} }
export interface AIService { export interface AIService {

View File

@ -1,6 +1,66 @@
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html';
import becca from '../../../becca/becca.js'; import becca from '../../../becca/becca.js';
// Define interfaces for JSON structures
interface CanvasElement {
type: string;
text?: string;
}
interface CanvasContent {
elements?: CanvasElement[];
}
interface MindMapNode {
text?: string;
children?: MindMapNode[];
}
interface MindMapContent {
root?: MindMapNode;
}
interface RelationMapNote {
noteId: string;
title?: string;
name?: string;
}
interface RelationMapRelation {
sourceNoteId: string;
targetNoteId: string;
name?: string;
}
interface RelationMapContent {
notes?: RelationMapNote[];
relations?: RelationMapRelation[];
}
interface GeoMapMarker {
title?: string;
lat: number;
lng: number;
description?: string;
}
interface GeoMapContent {
markers?: GeoMapMarker[];
}
interface ErrorWithMessage {
message: string;
}
/** /**
* Get the content of a note * Get the content of a note
*/ */
@ -51,21 +111,22 @@ export function formatNoteContent(content: string, type: string, mime: string, t
if (mime === 'application/json') { if (mime === 'application/json') {
try { try {
// Parse JSON content // Parse JSON content
const jsonContent = JSON.parse(content); const jsonContent = JSON.parse(content) as CanvasContent;
// Extract text elements from canvas // Extract text elements from canvas
if (jsonContent.elements && Array.isArray(jsonContent.elements)) { if (jsonContent.elements && Array.isArray(jsonContent.elements)) {
const texts = jsonContent.elements const texts = jsonContent.elements
.filter((element: any) => element.type === 'text' && element.text) .filter((element) => element.type === 'text' && element.text)
.map((element: any) => element.text); .map((element) => element.text as string);
formattedContent += 'Canvas content:\n' + texts.join('\n'); formattedContent += 'Canvas content:\n' + texts.join('\n');
} else { } else {
formattedContent += '[Empty canvas]'; formattedContent += '[Empty canvas]';
} }
} }
catch (e: any) { catch (e) {
formattedContent += `[Error parsing canvas content: ${e.message}]`; const error = e as ErrorWithMessage;
formattedContent += `[Error parsing canvas content: ${error.message}]`;
} }
} else { } else {
formattedContent += '[Canvas content]'; formattedContent += '[Canvas content]';
@ -76,10 +137,10 @@ export function formatNoteContent(content: string, type: string, mime: string, t
if (mime === 'application/json') { if (mime === 'application/json') {
try { try {
// Parse JSON content // Parse JSON content
const jsonContent = JSON.parse(content); const jsonContent = JSON.parse(content) as MindMapContent;
// Extract node text from mind map // Extract node text from mind map
const extractMindMapNodes = (node: any): string[] => { const extractMindMapNodes = (node: MindMapNode): string[] => {
let texts: string[] = []; let texts: string[] = [];
if (node.text) { if (node.text) {
texts.push(node.text); texts.push(node.text);
@ -98,8 +159,9 @@ export function formatNoteContent(content: string, type: string, mime: string, t
formattedContent += '[Empty mind map]'; formattedContent += '[Empty mind map]';
} }
} }
catch (e: any) { catch (e) {
formattedContent += `[Error parsing mind map content: ${e.message}]`; const error = e as ErrorWithMessage;
formattedContent += `[Error parsing mind map content: ${error.message}]`;
} }
} else { } else {
formattedContent += '[Mind map content]'; formattedContent += '[Mind map content]';
@ -110,23 +172,23 @@ export function formatNoteContent(content: string, type: string, mime: string, t
if (mime === 'application/json') { if (mime === 'application/json') {
try { try {
// Parse JSON content // Parse JSON content
const jsonContent = JSON.parse(content); const jsonContent = JSON.parse(content) as RelationMapContent;
// Extract relation map entities and connections // Extract relation map entities and connections
let result = 'Relation map content:\n'; let result = 'Relation map content:\n';
if (jsonContent.notes && Array.isArray(jsonContent.notes)) { if (jsonContent.notes && Array.isArray(jsonContent.notes)) {
result += 'Notes: ' + jsonContent.notes result += 'Notes: ' + jsonContent.notes
.map((note: any) => note.title || note.name) .map((note) => note.title || note.name)
.filter(Boolean) .filter(Boolean)
.join(', ') + '\n'; .join(', ') + '\n';
} }
if (jsonContent.relations && Array.isArray(jsonContent.relations)) { if (jsonContent.relations && Array.isArray(jsonContent.relations)) {
result += 'Relations: ' + jsonContent.relations result += 'Relations: ' + jsonContent.relations
.map((rel: any) => { .map((rel) => {
const sourceNote = jsonContent.notes.find((n: any) => n.noteId === rel.sourceNoteId); const sourceNote = jsonContent.notes?.find((n) => n.noteId === rel.sourceNoteId);
const targetNote = jsonContent.notes.find((n: any) => n.noteId === rel.targetNoteId); const targetNote = jsonContent.notes?.find((n) => n.noteId === rel.targetNoteId);
const source = sourceNote ? (sourceNote.title || sourceNote.name) : 'unknown'; const source = sourceNote ? (sourceNote.title || sourceNote.name) : 'unknown';
const target = targetNote ? (targetNote.title || targetNote.name) : 'unknown'; const target = targetNote ? (targetNote.title || targetNote.name) : 'unknown';
return `${source}${rel.name || ''}${target}`; return `${source}${rel.name || ''}${target}`;
@ -136,8 +198,9 @@ export function formatNoteContent(content: string, type: string, mime: string, t
formattedContent += result; formattedContent += result;
} }
catch (e: any) { catch (e) {
formattedContent += `[Error parsing relation map content: ${e.message}]`; const error = e as ErrorWithMessage;
formattedContent += `[Error parsing relation map content: ${error.message}]`;
} }
} else { } else {
formattedContent += '[Relation map content]'; formattedContent += '[Relation map content]';
@ -148,14 +211,14 @@ export function formatNoteContent(content: string, type: string, mime: string, t
if (mime === 'application/json') { if (mime === 'application/json') {
try { try {
// Parse JSON content // Parse JSON content
const jsonContent = JSON.parse(content); const jsonContent = JSON.parse(content) as GeoMapContent;
let result = 'Geographic map content:\n'; let result = 'Geographic map content:\n';
if (jsonContent.markers && Array.isArray(jsonContent.markers)) { if (jsonContent.markers && Array.isArray(jsonContent.markers)) {
if (jsonContent.markers.length > 0) { if (jsonContent.markers.length > 0) {
result += jsonContent.markers result += jsonContent.markers
.map((marker: any) => { .map((marker) => {
return `Location: ${marker.title || ''} (${marker.lat}, ${marker.lng})${marker.description ? ' - ' + marker.description : ''}`; return `Location: ${marker.title || ''} (${marker.lat}, ${marker.lng})${marker.description ? ' - ' + marker.description : ''}`;
}) })
.join('\n'); .join('\n');
@ -168,8 +231,9 @@ export function formatNoteContent(content: string, type: string, mime: string, t
formattedContent += result; formattedContent += result;
} }
catch (e: any) { catch (e) {
formattedContent += `[Error parsing geographic map content: ${e.message}]`; const error = e as ErrorWithMessage;
formattedContent += `[Error parsing geographic map content: ${error.message}]`;
} }
} else { } else {
formattedContent += '[Geographic map content]'; formattedContent += '[Geographic map content]';

View File

@ -55,11 +55,20 @@ export interface IContextFormatter {
): Promise<string>; ): Promise<string>;
} }
/**
* Interface for LLM Service
*/
export interface ILLMService {
sendMessage(message: string, options?: Record<string, unknown>): Promise<string>;
generateEmbedding?(text: string): Promise<number[]>;
streamMessage?(message: string, callback: (text: string) => void, options?: Record<string, unknown>): Promise<string>;
}
/** /**
* Interface for query enhancer * Interface for query enhancer
*/ */
export interface IQueryEnhancer { export interface IQueryEnhancer {
generateSearchQueries(question: string, llmService: any): Promise<string[]>; generateSearchQueries(question: string, llmService: ILLMService): Promise<string[]>;
estimateQueryComplexity(query: string): number; estimateQueryComplexity(query: string): number;
} }
@ -90,6 +99,15 @@ export interface IContentChunker {
chunkNoteContent(noteId: string, content: string, title: string): Promise<NoteChunk[]>; chunkNoteContent(noteId: string, content: string, title: string): Promise<NoteChunk[]>;
} }
/**
* Options for context service
*/
export interface ContextServiceOptions {
maxResults?: number;
summarize?: boolean;
llmService?: ILLMService;
}
/** /**
* Interface for context service * Interface for context service
*/ */
@ -97,17 +115,13 @@ export interface IContextService {
initialize(): Promise<void>; initialize(): Promise<void>;
processQuery( processQuery(
userQuestion: string, userQuestion: string,
llmService: any, llmService: ILLMService,
contextNoteId?: string | null, contextNoteId?: string | null,
showThinking?: boolean showThinking?: boolean
): Promise<{ context: string; sources: NoteSearchResult[]; thinking?: string }>; ): Promise<{ context: string; sources: NoteSearchResult[]; thinking?: string }>;
findRelevantNotes( findRelevantNotes(
query: string, query: string,
contextNoteId?: string | null, contextNoteId?: string | null,
options?: { options?: ContextServiceOptions
maxResults?: number;
summarize?: boolean;
llmService?: any;
}
): Promise<NoteSearchResult[]>; ): Promise<NoteSearchResult[]>;
} }

View File

@ -2,7 +2,7 @@ import { BaseAIService } from '../base_ai_service.js';
import type { Message, ChatCompletionOptions, ChatResponse, StreamChunk } from '../ai_interface.js'; import type { Message, ChatCompletionOptions, ChatResponse, StreamChunk } from '../ai_interface.js';
import { OllamaMessageFormatter } from '../formatters/ollama_formatter.js'; import { OllamaMessageFormatter } from '../formatters/ollama_formatter.js';
import log from '../../log.js'; import log from '../../log.js';
import type { ToolCall } from '../tools/tool_interfaces.js'; import type { ToolCall, Tool } from '../tools/tool_interfaces.js';
import toolRegistry from '../tools/tool_registry.js'; import toolRegistry from '../tools/tool_registry.js';
import type { OllamaOptions } from './provider_options.js'; import type { OllamaOptions } from './provider_options.js';
import { getOllamaOptions } from './providers.js'; import { getOllamaOptions } from './providers.js';
@ -25,6 +25,35 @@ interface ToolExecutionStatus {
error?: string; error?: string;
} }
// Interface for Ollama-specific messages
interface OllamaMessage {
role: string;
content: string;
tool_call_id?: string;
tool_calls?: OllamaToolCall[];
name?: string;
}
// Interface for Ollama tool calls
interface OllamaToolCall {
id: string;
function: {
name: string;
arguments: Record<string, unknown>;
};
}
// Interface for Ollama request options
interface OllamaRequestOptions {
model: string;
messages: OllamaMessage[];
stream?: boolean;
options?: Record<string, unknown>;
format?: string;
tools?: Tool[];
[key: string]: unknown;
}
export class OllamaService extends BaseAIService { export class OllamaService extends BaseAIService {
private formatter: OllamaMessageFormatter; private formatter: OllamaMessageFormatter;
private client: Ollama | null = null; private client: Ollama | null = null;
@ -104,7 +133,7 @@ export class OllamaService extends BaseAIService {
// Check if we should add tool execution feedback // Check if we should add tool execution feedback
if (providerOptions.toolExecutionStatus && Array.isArray(providerOptions.toolExecutionStatus) && providerOptions.toolExecutionStatus.length > 0) { if (providerOptions.toolExecutionStatus && Array.isArray(providerOptions.toolExecutionStatus) && providerOptions.toolExecutionStatus.length > 0) {
log.info(`Adding tool execution feedback to messages`); log.info(`Adding tool execution feedback to messages`);
messages = this.addToolExecutionFeedback(messages, providerOptions.toolExecutionStatus); messages = this.addToolExecutionFeedback(messages, providerOptions.toolExecutionStatus as ToolExecutionStatus[]);
} }
// Determine whether to use the formatter or send messages directly // Determine whether to use the formatter or send messages directly
@ -126,11 +155,11 @@ export class OllamaService extends BaseAIService {
} }
// Get tools if enabled // Get tools if enabled
let tools = []; let tools: Tool[] = [];
if (providerOptions.enableTools !== false) { if (providerOptions.enableTools !== false) {
try { try {
tools = providerOptions.tools && providerOptions.tools.length > 0 tools = providerOptions.tools && providerOptions.tools.length > 0
? providerOptions.tools ? providerOptions.tools as Tool[]
: toolRegistry.getAllToolDefinitions(); : toolRegistry.getAllToolDefinitions();
// Handle empty tools array // Handle empty tools array
@ -145,15 +174,16 @@ export class OllamaService extends BaseAIService {
if (tools.length > 0) { if (tools.length > 0) {
log.info(`Sending ${tools.length} tool definitions to Ollama`); log.info(`Sending ${tools.length} tool definitions to Ollama`);
} }
} catch (error: any) { } catch (error) {
log.error(`Error preparing tools: ${error.message || String(error)}`); const errorMsg = error instanceof Error ? error.message : String(error);
log.error(`Error preparing tools: ${errorMsg}`);
tools = []; // Empty fallback tools = []; // Empty fallback
} }
} }
// Convert our message format to Ollama's format // Convert our message format to Ollama's format
const convertedMessages = messagesToSend.map(msg => { const convertedMessages = messagesToSend.map(msg => {
const converted: any = { const converted: OllamaMessage = {
role: msg.role, role: msg.role,
content: msg.content content: msg.content
}; };
@ -161,21 +191,23 @@ export class OllamaService extends BaseAIService {
if (msg.tool_calls) { if (msg.tool_calls) {
converted.tool_calls = msg.tool_calls.map(tc => { converted.tool_calls = msg.tool_calls.map(tc => {
// For Ollama, arguments must be an object, not a string // For Ollama, arguments must be an object, not a string
let processedArgs = tc.function.arguments; let processedArgs: Record<string, unknown> = {};
// If arguments is a string, try to parse it as JSON // If arguments is a string, try to parse it as JSON
if (typeof processedArgs === 'string') { if (typeof tc.function.arguments === 'string') {
try { try {
processedArgs = JSON.parse(processedArgs); processedArgs = JSON.parse(tc.function.arguments);
} catch (e) { } catch (e) {
// If parsing fails, create an object with a single property // If parsing fails, create an object with a single property
log.info(`Could not parse tool arguments as JSON: ${e}`); log.info(`Could not parse tool arguments as JSON: ${e}`);
processedArgs = { raw: processedArgs }; processedArgs = { raw: tc.function.arguments };
} }
} else if (typeof tc.function.arguments === 'object') {
processedArgs = tc.function.arguments as Record<string, unknown>;
} }
return { return {
id: tc.id, id: tc.id ?? '',
function: { function: {
name: tc.function.name, name: tc.function.name,
arguments: processedArgs arguments: processedArgs
@ -196,65 +228,67 @@ export class OllamaService extends BaseAIService {
}); });
// Prepare base request options // Prepare base request options
const baseRequestOptions = { const baseRequestOptions: OllamaRequestOptions = {
model: providerOptions.model, model: providerOptions.model,
messages: convertedMessages, messages: convertedMessages,
options: providerOptions.options, stream: opts.stream === true
// Add tools if available
tools: tools.length > 0 ? tools : undefined
}; };
// Get client instance // Add tool definitions if available
if (tools && tools.length > 0 && providerOptions.enableTools !== false) {
baseRequestOptions.tools = tools;
}
// Add any model-specific parameters
if (providerOptions.options) {
baseRequestOptions.options = providerOptions.options;
}
// If JSON response is expected, set format
if (providerOptions.expectsJsonResponse) {
baseRequestOptions.format = 'json';
}
log.info(`Sending request to Ollama with model: ${providerOptions.model}`);
// Handle streaming vs non-streaming responses
const client = this.getClient(); const client = this.getClient();
// Handle streaming if (opts.stream === true) {
if (opts.stream || opts.streamCallback) { // Use streaming API
return this.handleStreamingResponse(client, baseRequestOptions, opts, providerOptions); return this.handleStreamingResponse(client, baseRequestOptions, opts, providerOptions);
} else { } else {
// Non-streaming request // Use non-streaming API
log.info(`Using non-streaming mode with Ollama client`); try {
log.info(`Sending non-streaming request to Ollama`);
// Create non-streaming request // Create a properly typed request with stream: false
const nonStreamingRequest = { const chatRequest: ChatRequest & { stream?: false } = {
...baseRequestOptions, ...baseRequestOptions,
stream: false as const // Use const assertion for type safety stream: false
}; };
const response = await client.chat(nonStreamingRequest); const response = await client.chat(chatRequest);
// Log response details log.info(`Received response from Ollama`);
log.info(`========== OLLAMA API RESPONSE ==========`);
log.info(`Model: ${response.model}, Content length: ${response.message?.content?.length || 0} chars`);
log.info(`Tokens: ${response.prompt_eval_count || 0} prompt, ${response.eval_count || 0} completion, ${(response.prompt_eval_count || 0) + (response.eval_count || 0)} total`);
// Handle the response and extract tool calls if present // Transform tool calls if present
const chatResponse: ChatResponse = { const toolCalls = this.transformToolCalls(response.message.tool_calls);
text: response.message?.content || '',
model: response.model || providerOptions.model, return {
provider: this.getName(), text: response.message.content,
usage: { model: providerOptions.model,
promptTokens: response.prompt_eval_count || 0, provider: 'ollama',
completionTokens: response.eval_count || 0, tool_calls: toolCalls.length > 0 ? toolCalls : undefined
totalTokens: (response.prompt_eval_count || 0) + (response.eval_count || 0)
}
}; };
} catch (error) {
// Add tool calls if present const errorMsg = error instanceof Error ? error.message : String(error);
if (response.message?.tool_calls && response.message.tool_calls.length > 0) { log.error(`Error in Ollama request: ${errorMsg}`);
log.info(`Ollama response includes ${response.message.tool_calls.length} tool calls`); throw error;
chatResponse.tool_calls = this.transformToolCalls(response.message.tool_calls);
} }
return chatResponse;
} }
} catch (error: any) { } catch (error) {
// Enhanced error handling with detailed diagnostics const errorMsg = error instanceof Error ? error.message : String(error);
log.error(`Ollama service error: ${error.message || String(error)}`); log.error(`Error in Ollama service: ${errorMsg}`);
if (error.stack) {
log.error(`Error stack trace: ${error.stack}`);
}
// Propagate the original error
throw error; throw error;
} }
} }
@ -266,7 +300,7 @@ export class OllamaService extends BaseAIService {
*/ */
private async handleStreamingResponse( private async handleStreamingResponse(
client: Ollama, client: Ollama,
requestOptions: any, requestOptions: OllamaRequestOptions,
opts: ChatCompletionOptions, opts: ChatCompletionOptions,
providerOptions: OllamaOptions providerOptions: OllamaOptions
): Promise<ChatResponse> { ): Promise<ChatResponse> {
@ -369,7 +403,7 @@ export class OllamaService extends BaseAIService {
await callback({ await callback({
text: chunk.message?.content || '', text: chunk.message?.content || '',
done: false, // Add done property to satisfy StreamChunk done: false, // Add done property to satisfy StreamChunk
raw: chunk raw: chunk as unknown as Record<string, unknown>
}); });
// Log completion // Log completion