Merge pull request #458 from kaovilai/thiknagain
Extends #445 to other providers which many provides deepseek
This commit is contained in:
@@ -8,6 +8,7 @@ import axios from 'axios';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { tokenCount } from '../utils/tokenCount';
|
import { tokenCount } from '../utils/tokenCount';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
|
|
||||||
@@ -54,8 +55,8 @@ export class AnthropicEngine implements AiEngine {
|
|||||||
const data = await this.client.messages.create(params);
|
const data = await this.client.messages.create(params);
|
||||||
|
|
||||||
const message = data?.content[0].text;
|
const message = data?.content[0].text;
|
||||||
|
let content = message;
|
||||||
return message;
|
return removeContentTags(content, 'think');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
outro(`${chalk.red('✖')} ${err?.message || err}`);
|
outro(`${chalk.red('✖')} ${err?.message || err}`);
|
||||||
|
|||||||
+4
-1
@@ -7,6 +7,7 @@ import axios from 'axios';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { tokenCount } from '../utils/tokenCount';
|
import { tokenCount } from '../utils/tokenCount';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
|
|
||||||
@@ -52,7 +53,9 @@ export class AzureEngine implements AiEngine {
|
|||||||
if (message?.content === null) {
|
if (message?.content === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return message?.content;
|
|
||||||
|
let content = message?.content;
|
||||||
|
return removeContentTags(content, 'think');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
outro(`${chalk.red('✖')} ${this.config.model}`);
|
outro(`${chalk.red('✖')} ${this.config.model}`);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { tokenCount } from '../utils/tokenCount';
|
import { tokenCount } from '../utils/tokenCount';
|
||||||
import { OpenAiEngine, OpenAiConfig } from './openAi';
|
import { OpenAiEngine, OpenAiConfig } from './openAi';
|
||||||
|
|
||||||
@@ -41,8 +42,8 @@ export class DeepseekEngine extends OpenAiEngine {
|
|||||||
const completion = await this.client.chat.completions.create(params);
|
const completion = await this.client.chat.completions.create(params);
|
||||||
|
|
||||||
const message = completion.choices[0].message;
|
const message = completion.choices[0].message;
|
||||||
|
let content = message?.content;
|
||||||
return message?.content;
|
return removeContentTags(content, 'think');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
|
|
||||||
interface FlowiseAiConfig extends AiEngineConfig {}
|
interface FlowiseAiConfig extends AiEngineConfig {}
|
||||||
@@ -36,7 +37,8 @@ export class FlowiseEngine implements AiEngine {
|
|||||||
try {
|
try {
|
||||||
const response = await this.client.post('', payload);
|
const response = await this.client.post('', payload);
|
||||||
const message = response.data;
|
const message = response.data;
|
||||||
return message?.text;
|
let content = message?.text;
|
||||||
|
return removeContentTags(content, 'think');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const message = err.response?.data?.error ?? err.message;
|
const message = err.response?.data?.error ?? err.message;
|
||||||
throw new Error('local model issues. details: ' + message);
|
throw new Error('local model issues. details: ' + message);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '@google/generative-ai';
|
} from '@google/generative-ai';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
|
|
||||||
interface GeminiConfig extends AiEngineConfig {}
|
interface GeminiConfig extends AiEngineConfig {}
|
||||||
@@ -71,7 +72,8 @@ export class GeminiEngine implements AiEngine {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.response.text();
|
const content = result.response.text();
|
||||||
|
return removeContentTags(content, 'think');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
if (
|
if (
|
||||||
|
|||||||
+10
-15
@@ -1,27 +1,21 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Mistral } from '@mistralai/mistralai';
|
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { tokenCount } from '../utils/tokenCount';
|
import { tokenCount } from '../utils/tokenCount';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
import {
|
|
||||||
AssistantMessage as MistralAssistantMessage,
|
|
||||||
SystemMessage as MistralSystemMessage,
|
|
||||||
ToolMessage as MistralToolMessage,
|
|
||||||
UserMessage as MistralUserMessage
|
|
||||||
} from '@mistralai/mistralai/models/components';
|
|
||||||
|
|
||||||
|
// Using any for Mistral types to avoid type declaration issues
|
||||||
export interface MistralAiConfig extends AiEngineConfig {}
|
export interface MistralAiConfig extends AiEngineConfig {}
|
||||||
export type MistralCompletionMessageParam = Array<
|
export type MistralCompletionMessageParam = Array<any>;
|
||||||
| (MistralSystemMessage & { role: "system" })
|
|
||||||
| (MistralUserMessage & { role: "user" })
|
// Import Mistral dynamically to avoid TS errors
|
||||||
| (MistralAssistantMessage & { role: "assistant" })
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
| (MistralToolMessage & { role: "tool" })
|
const Mistral = require('@mistralai/mistralai').Mistral;
|
||||||
>
|
|
||||||
|
|
||||||
export class MistralAiEngine implements AiEngine {
|
export class MistralAiEngine implements AiEngine {
|
||||||
config: MistralAiConfig;
|
config: MistralAiConfig;
|
||||||
client: Mistral;
|
client: any; // Using any type for Mistral client to avoid TS errors
|
||||||
|
|
||||||
constructor(config: MistralAiConfig) {
|
constructor(config: MistralAiConfig) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@@ -64,7 +58,8 @@ export class MistralAiEngine implements AiEngine {
|
|||||||
if (!message || !message.content)
|
if (!message || !message.content)
|
||||||
throw Error('No completion choice available.')
|
throw Error('No completion choice available.')
|
||||||
|
|
||||||
return message.content as string;
|
let content = message.content as string;
|
||||||
|
return removeContentTags(content, 'think');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
if (
|
if (
|
||||||
|
|||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
import { chown } from 'fs';
|
|
||||||
|
|
||||||
interface MLXConfig extends AiEngineConfig {}
|
interface MLXConfig extends AiEngineConfig {}
|
||||||
|
|
||||||
@@ -37,11 +37,11 @@ export class MLXEngine implements AiEngine {
|
|||||||
|
|
||||||
const choices = response.data.choices;
|
const choices = response.data.choices;
|
||||||
const message = choices[0].message;
|
const message = choices[0].message;
|
||||||
|
let content = message?.content;
|
||||||
return message?.content;
|
return removeContentTags(content, 'think');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const message = err.response?.data?.error ?? err.message;
|
const message = err.response?.data?.error ?? err.message;
|
||||||
throw new Error(`MLX provider error: ${message}`);
|
throw new Error(`MLX provider error: ${message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
|
|
||||||
interface OllamaConfig extends AiEngineConfig {}
|
interface OllamaConfig extends AiEngineConfig {}
|
||||||
@@ -35,12 +36,7 @@ export class OllamaEngine implements AiEngine {
|
|||||||
|
|
||||||
const { message } = response.data;
|
const { message } = response.data;
|
||||||
let content = message?.content;
|
let content = message?.content;
|
||||||
|
return removeContentTags(content, 'think');
|
||||||
if (content && content.includes('<think>')) {
|
|
||||||
return content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const message = err.response?.data?.error ?? err.message;
|
const message = err.response?.data?.error ?? err.message;
|
||||||
throw new Error(`Ollama provider error: ${message}`);
|
throw new Error(`Ollama provider error: ${message}`);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { OpenAI } from 'openai';
|
import { OpenAI } from 'openai';
|
||||||
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
|
||||||
|
import { removeContentTags } from '../utils/removeContentTags';
|
||||||
import { tokenCount } from '../utils/tokenCount';
|
import { tokenCount } from '../utils/tokenCount';
|
||||||
import { AiEngine, AiEngineConfig } from './Engine';
|
import { AiEngine, AiEngineConfig } from './Engine';
|
||||||
|
|
||||||
@@ -45,8 +46,8 @@ export class OpenAiEngine implements AiEngine {
|
|||||||
const completion = await this.client.chat.completions.create(params);
|
const completion = await this.client.chat.completions.create(params);
|
||||||
|
|
||||||
const message = completion.choices[0].message;
|
const message = completion.choices[0].message;
|
||||||
|
let content = message?.content;
|
||||||
return message?.content;
|
return removeContentTags(content, 'think');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as Error;
|
const err = error as Error;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Removes content wrapped in specified tags from a string
|
||||||
|
* @param content The content string to process
|
||||||
|
* @param tag The tag name without angle brackets (e.g., 'think' for '<think></think>')
|
||||||
|
* @returns The content with the specified tags and their contents removed, and trimmed
|
||||||
|
*/
|
||||||
|
export function removeContentTags<T extends string | null | undefined>(content: T, tag: string): T {
|
||||||
|
if (!content || typeof content !== 'string') {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic implementation for other cases
|
||||||
|
const openTag = `<${tag}>`;
|
||||||
|
const closeTag = `</${tag}>`;
|
||||||
|
|
||||||
|
// Parse the content and remove tags
|
||||||
|
let result = '';
|
||||||
|
let skipUntil: number | null = null;
|
||||||
|
let depth = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < content.length; i++) {
|
||||||
|
// Check for opening tag
|
||||||
|
if (content.substring(i, i + openTag.length) === openTag) {
|
||||||
|
depth++;
|
||||||
|
if (depth === 1) {
|
||||||
|
skipUntil = content.indexOf(closeTag, i + openTag.length);
|
||||||
|
i = i + openTag.length - 1; // Skip the opening tag
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for closing tag
|
||||||
|
else if (content.substring(i, i + closeTag.length) === closeTag && depth > 0) {
|
||||||
|
depth--;
|
||||||
|
if (depth === 0) {
|
||||||
|
i = i + closeTag.length - 1; // Skip the closing tag
|
||||||
|
skipUntil = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add character if not inside a tag
|
||||||
|
if (skipUntil === null) {
|
||||||
|
result += content[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize spaces (replace multiple spaces with a single space)
|
||||||
|
result = result.replace(/\s+/g, ' ').trim();
|
||||||
|
|
||||||
|
return result as unknown as T;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { removeContentTags } from '../../src/utils/removeContentTags';
|
||||||
|
|
||||||
|
describe('removeContentTags', () => {
|
||||||
|
it('should remove content wrapped in specified tags', () => {
|
||||||
|
const content = 'This is <think>something to hide</think> visible content';
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe('This is visible content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple tag occurrences', () => {
|
||||||
|
const content = '<think>hidden</think> visible <think>also hidden</think> text';
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe('visible text');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiline content within tags', () => {
|
||||||
|
const content = 'Start <think>hidden\nover multiple\nlines</think> End';
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe('Start End');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return content as is when tag is not found', () => {
|
||||||
|
const content = 'Content without any tags';
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe('Content without any tags');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with different tag names', () => {
|
||||||
|
const content = 'This is <custom>something to hide</custom> visible content';
|
||||||
|
const result = removeContentTags(content, 'custom');
|
||||||
|
expect(result).toBe('This is visible content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null content', () => {
|
||||||
|
const content = null;
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined content', () => {
|
||||||
|
const content = undefined;
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trim the result', () => {
|
||||||
|
const content = ' <think>hidden</think> visible ';
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe('visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle nested tags correctly', () => {
|
||||||
|
const content = 'Outside <think>Inside <think>Nested</think></think> End';
|
||||||
|
const result = removeContentTags(content, 'think');
|
||||||
|
expect(result).toBe('Outside End');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user