mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-10 10:22:29 +08:00
yes, this finally does set streaming to true
This commit is contained in:
parent
59a358a3ee
commit
b05b88dd76
@ -410,8 +410,14 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
*/
|
||||
private async handleDirectResponse(messageParams: any): Promise<boolean> {
|
||||
try {
|
||||
// Send the message via POST request
|
||||
const postResponse = await server.post<any>(`llm/sessions/${this.sessionId}/messages`, messageParams);
|
||||
// Add format parameter to maintain consistency with the streaming GET request
|
||||
const postParams = {
|
||||
...messageParams,
|
||||
format: 'stream' // Match the format parameter used in the GET streaming request
|
||||
};
|
||||
|
||||
// Send the message via POST request with the updated params
|
||||
const postResponse = await server.post<any>(`llm/sessions/${this.sessionId}/messages`, postParams);
|
||||
|
||||
// If the POST request returned content directly, display it
|
||||
if (postResponse && postResponse.content) {
|
||||
@ -460,8 +466,8 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
const useAdvancedContext = messageParams.useAdvancedContext;
|
||||
const showThinking = messageParams.showThinking;
|
||||
|
||||
// Set up streaming via EventSource
|
||||
const streamUrl = `./api/llm/sessions/${this.sessionId}/messages?format=stream&useAdvancedContext=${useAdvancedContext}&showThinking=${showThinking}`;
|
||||
// Set up streaming via EventSource - explicitly add stream=true parameter to ensure consistency
|
||||
const streamUrl = `./api/llm/sessions/${this.sessionId}/messages?format=stream&stream=true&useAdvancedContext=${useAdvancedContext}&showThinking=${showThinking}`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const source = new EventSource(streamUrl);
|
||||
|
@ -20,6 +20,18 @@ export interface StreamChunk {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for chat completion requests
|
||||
*
|
||||
* Key properties:
|
||||
* - stream: If true, the response will be streamed
|
||||
* - model: Model name to use
|
||||
* - provider: Provider to use (openai, anthropic, ollama, etc.)
|
||||
* - enableTools: If true, enables tool support
|
||||
*
|
||||
* The stream option is particularly important and should be consistently handled
|
||||
* throughout the pipeline. It should be explicitly set to true or false.
|
||||
*/
|
||||
export interface ChatCompletionOptions {
|
||||
model?: string;
|
||||
temperature?: number;
|
||||
|
@ -197,6 +197,13 @@ export class AIServiceManager implements IAIServiceManager {
|
||||
async generateChatCompletion(messages: Message[], options: ChatCompletionOptions = {}): Promise<ChatResponse> {
|
||||
this.ensureInitialized();
|
||||
|
||||
log.info(`[AIServiceManager] generateChatCompletion called with options: ${JSON.stringify({
|
||||
model: options.model,
|
||||
stream: options.stream,
|
||||
enableTools: options.enableTools
|
||||
})}`);
|
||||
log.info(`[AIServiceManager] Stream option type: ${typeof options.stream}`);
|
||||
|
||||
if (!messages || messages.length === 0) {
|
||||
throw new Error('No messages provided for chat completion');
|
||||
}
|
||||
@ -219,6 +226,7 @@ export class AIServiceManager implements IAIServiceManager {
|
||||
if (availableProviders.includes(providerName as ServiceProviders)) {
|
||||
try {
|
||||
const modifiedOptions = { ...options, model: modelName };
|
||||
log.info(`[AIServiceManager] Using provider ${providerName} from model prefix with modifiedOptions.stream: ${modifiedOptions.stream}`);
|
||||
return await this.services[providerName as ServiceProviders].generateChatCompletion(messages, modifiedOptions);
|
||||
} catch (error) {
|
||||
log.error(`Error with specified provider ${providerName}: ${error}`);
|
||||
@ -232,6 +240,7 @@ export class AIServiceManager implements IAIServiceManager {
|
||||
|
||||
for (const provider of sortedProviders) {
|
||||
try {
|
||||
log.info(`[AIServiceManager] Trying provider ${provider} with options.stream: ${options.stream}`);
|
||||
return await this.services[provider].generateChatCompletion(messages, options);
|
||||
} catch (error) {
|
||||
log.error(`Error with provider ${provider}: ${error}`);
|
||||
|
@ -227,14 +227,36 @@ export class ChatPipeline {
|
||||
log.info(`Prepared ${preparedMessages.messages.length} messages for LLM, tools enabled: ${useTools}`);
|
||||
|
||||
// Setup streaming handler if streaming is enabled and callback provided
|
||||
const enableStreaming = this.config.enableStreaming &&
|
||||
modelSelection.options.stream !== false &&
|
||||
typeof streamCallback === 'function';
|
||||
|
||||
if (enableStreaming) {
|
||||
// Make sure stream is enabled in options
|
||||
// Check if streaming should be enabled based on several conditions
|
||||
const streamEnabledInConfig = this.config.enableStreaming;
|
||||
const streamFormatRequested = input.format === 'stream';
|
||||
const streamRequestedInOptions = modelSelection.options.stream === true;
|
||||
const streamCallbackAvailable = typeof streamCallback === 'function';
|
||||
|
||||
log.info(`[ChatPipeline] Request type info - Format: ${input.format || 'not specified'}, Options from pipelineInput: ${JSON.stringify({stream: input.options?.stream})}`);
|
||||
log.info(`[ChatPipeline] Stream settings - config.enableStreaming: ${streamEnabledInConfig}, format parameter: ${input.format}, modelSelection.options.stream: ${modelSelection.options.stream}, streamCallback available: ${streamCallbackAvailable}`);
|
||||
|
||||
// IMPORTANT: Different behavior for GET vs POST requests:
|
||||
// - For GET requests with streamCallback available: Always enable streaming
|
||||
// - For POST requests: Use streaming options but don't actually stream (since we can't stream back to client)
|
||||
if (streamCallbackAvailable) {
|
||||
// If a stream callback is available (GET requests), we can stream the response
|
||||
modelSelection.options.stream = true;
|
||||
log.info(`[ChatPipeline] Stream callback available, setting stream=true for real-time streaming`);
|
||||
} else {
|
||||
// For POST requests, preserve the stream flag as-is from input options
|
||||
// This ensures LLM request format is consistent across both GET and POST
|
||||
if (streamRequestedInOptions) {
|
||||
log.info(`[ChatPipeline] No stream callback but stream requested in options, preserving stream=true`);
|
||||
} else {
|
||||
log.info(`[ChatPipeline] No stream callback and no stream in options, setting stream=false`);
|
||||
modelSelection.options.stream = false;
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`[ChatPipeline] Final modelSelection.options.stream = ${modelSelection.options.stream}`);
|
||||
log.info(`[ChatPipeline] Will actual streaming occur? ${streamCallbackAvailable && modelSelection.options.stream}`);
|
||||
|
||||
|
||||
// STAGE 5 & 6: Handle LLM completion and tool execution loop
|
||||
log.info(`========== STAGE 5: LLM COMPLETION ==========`);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BasePipelineStage } from '../pipeline_stage.js';
|
||||
import type { LLMCompletionInput } from '../interfaces.js';
|
||||
import type { ChatResponse } from '../../ai_interface.js';
|
||||
import type { ChatCompletionOptions, ChatResponse } from '../../ai_interface.js';
|
||||
import aiServiceManager from '../../ai_service_manager.js';
|
||||
import toolRegistry from '../../tools/tool_registry.js';
|
||||
import log from '../../../log.js';
|
||||
@ -19,8 +19,35 @@ export class LLMCompletionStage extends BasePipelineStage<LLMCompletionInput, {
|
||||
protected async process(input: LLMCompletionInput): Promise<{ response: ChatResponse }> {
|
||||
const { messages, options, provider } = input;
|
||||
|
||||
// Create a copy of options to avoid modifying the original
|
||||
const updatedOptions = { ...options };
|
||||
// Log input options, particularly focusing on the stream option
|
||||
log.info(`[LLMCompletionStage] Input options: ${JSON.stringify({
|
||||
model: options.model,
|
||||
provider,
|
||||
stream: options.stream,
|
||||
enableTools: options.enableTools
|
||||
})}`);
|
||||
log.info(`[LLMCompletionStage] Stream option in input: ${options.stream}, type: ${typeof options.stream}`);
|
||||
|
||||
// Create a deep copy of options to avoid modifying the original
|
||||
const updatedOptions: ChatCompletionOptions = JSON.parse(JSON.stringify(options));
|
||||
|
||||
// IMPORTANT: Ensure stream property is explicitly set to a boolean value
|
||||
// This is critical to ensure consistent behavior across all providers
|
||||
updatedOptions.stream = options.stream === true;
|
||||
|
||||
log.info(`[LLMCompletionStage] Explicitly set stream option to boolean: ${updatedOptions.stream}`);
|
||||
|
||||
// If this is a direct (non-stream) call to Ollama but has the stream flag,
|
||||
// ensure we set additional metadata to maintain proper state
|
||||
if (updatedOptions.stream && !provider && updatedOptions.providerMetadata?.provider === 'ollama') {
|
||||
log.info(`[LLMCompletionStage] This is an Ollama request with stream=true, ensuring provider config is consistent`);
|
||||
}
|
||||
|
||||
log.info(`[LLMCompletionStage] Copied options: ${JSON.stringify({
|
||||
model: updatedOptions.model,
|
||||
stream: updatedOptions.stream,
|
||||
enableTools: updatedOptions.enableTools
|
||||
})}`);
|
||||
|
||||
// Check if tools should be enabled
|
||||
if (updatedOptions.enableTools !== false) {
|
||||
@ -48,15 +75,22 @@ export class LLMCompletionStage extends BasePipelineStage<LLMCompletionInput, {
|
||||
}
|
||||
|
||||
log.info(`Generating LLM completion, provider: ${selectedProvider || 'auto'}, model: ${updatedOptions?.model || 'default'}`);
|
||||
log.info(`[LLMCompletionStage] Options before service call: ${JSON.stringify({
|
||||
model: updatedOptions.model,
|
||||
stream: updatedOptions.stream,
|
||||
enableTools: updatedOptions.enableTools
|
||||
})}`);
|
||||
|
||||
// If provider is specified (either explicit or from metadata), use that specific provider
|
||||
if (selectedProvider && aiServiceManager.isProviderAvailable(selectedProvider)) {
|
||||
const service = aiServiceManager.getService(selectedProvider);
|
||||
log.info(`[LLMCompletionStage] Using specific service for ${selectedProvider}, stream option: ${updatedOptions.stream}`);
|
||||
const response = await service.generateChatCompletion(messages, updatedOptions);
|
||||
return { response };
|
||||
}
|
||||
|
||||
// Otherwise use the service manager to select an available provider
|
||||
log.info(`[LLMCompletionStage] Using auto-selected service, stream option: ${updatedOptions.stream}`);
|
||||
const response = await aiServiceManager.generateChatCompletion(messages, updatedOptions);
|
||||
return { response };
|
||||
}
|
||||
|
@ -18,8 +18,20 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
||||
protected async process(input: ModelSelectionInput): Promise<{ options: ChatCompletionOptions }> {
|
||||
const { options: inputOptions, query, contentLength } = input;
|
||||
|
||||
// Log input options
|
||||
log.info(`[ModelSelectionStage] Input options: ${JSON.stringify({
|
||||
model: inputOptions?.model,
|
||||
stream: inputOptions?.stream,
|
||||
enableTools: inputOptions?.enableTools
|
||||
})}`);
|
||||
log.info(`[ModelSelectionStage] Stream option in input: ${inputOptions?.stream}, type: ${typeof inputOptions?.stream}`);
|
||||
|
||||
// Start with provided options or create a new object
|
||||
const updatedOptions: ChatCompletionOptions = { ...(inputOptions || {}) };
|
||||
|
||||
// Preserve the stream option exactly as it was provided, including undefined state
|
||||
// This is critical for ensuring the stream option propagates correctly down the pipeline
|
||||
log.info(`[ModelSelectionStage] After copy, stream: ${updatedOptions.stream}, type: ${typeof updatedOptions.stream}`);
|
||||
|
||||
// If model already specified, don't override it
|
||||
if (updatedOptions.model) {
|
||||
@ -36,6 +48,7 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
||||
log.info(`Using explicitly specified model: ${updatedOptions.model}`);
|
||||
}
|
||||
|
||||
log.info(`[ModelSelectionStage] Returning early with stream: ${updatedOptions.stream}`);
|
||||
return { options: updatedOptions };
|
||||
}
|
||||
|
||||
@ -151,6 +164,13 @@ export class ModelSelectionStage extends BasePipelineStage<ModelSelectionInput,
|
||||
this.addProviderMetadata(updatedOptions, defaultProvider, defaultModelName);
|
||||
|
||||
log.info(`Selected model: ${defaultModelName} from provider: ${defaultProvider} for query complexity: ${queryComplexity}`);
|
||||
log.info(`[ModelSelectionStage] Final options: ${JSON.stringify({
|
||||
model: updatedOptions.model,
|
||||
stream: updatedOptions.stream,
|
||||
provider: defaultProvider,
|
||||
enableTools: updatedOptions.enableTools
|
||||
})}`);
|
||||
|
||||
return { options: updatedOptions };
|
||||
}
|
||||
|
||||
|
@ -114,11 +114,21 @@ export class OllamaService extends BaseAIService {
|
||||
messages: messagesToSend
|
||||
};
|
||||
|
||||
// Debug logging for stream option
|
||||
log.info(`Stream option in providerOptions: ${providerOptions.stream}`);
|
||||
log.info(`Stream option type: ${typeof providerOptions.stream}`);
|
||||
|
||||
log.info(`Stream: ${providerOptions.stream}`);
|
||||
// Stream is a top-level option
|
||||
if (providerOptions.stream !== undefined) {
|
||||
requestBody.stream = providerOptions.stream;
|
||||
// Stream is a top-level option - ALWAYS set it explicitly to ensure consistency
|
||||
// This is critical for ensuring streaming works properly
|
||||
requestBody.stream = providerOptions.stream === true;
|
||||
log.info(`Set requestBody.stream to boolean: ${requestBody.stream}`);
|
||||
|
||||
// Log additional information about the streaming context
|
||||
log.info(`Streaming context: Will stream to client: ${typeof opts.streamCallback === 'function'}`);
|
||||
|
||||
// If we have a streaming callback but the stream flag isn't set for some reason, warn about it
|
||||
if (typeof opts.streamCallback === 'function' && !requestBody.stream) {
|
||||
log.warn(`WARNING: Stream callback provided but stream=false in request. This may cause streaming issues.`);
|
||||
}
|
||||
|
||||
// Add options object if provided
|
||||
|
@ -542,7 +542,7 @@ export async function getOllamaOptions(
|
||||
|
||||
// API parameters
|
||||
model: modelName, // Clean model name without provider prefix
|
||||
stream: opts.stream,
|
||||
stream: opts.stream !== undefined ? opts.stream : true, // Default to true if not specified
|
||||
options: {
|
||||
temperature: opts.temperature,
|
||||
num_ctx: modelContextWindow,
|
||||
|
@ -458,7 +458,9 @@ class RestChatService {
|
||||
temperature: session.metadata.temperature,
|
||||
maxTokens: session.metadata.maxTokens,
|
||||
model: session.metadata.model,
|
||||
stream: req.method === 'GET' ? true : undefined // Explicitly set stream: true for GET requests
|
||||
// Always set stream to true for all request types to ensure consistency
|
||||
// This ensures the pipeline always knows streaming is supported, even for POST requests
|
||||
stream: true
|
||||
},
|
||||
streamCallback: req.method === 'GET' ? (data, done) => {
|
||||
res.write(`data: ${JSON.stringify({ content: data, done })}\n\n`);
|
||||
|
Loading…
x
Reference in New Issue
Block a user