refactor: Migrate services to core package and update project dependencies
- Move services from web package to core package - Update import statements across web package to use @prompt-optimizer/core - Add LangChain and related dependencies to package.json - Refactor and standardize service interfaces and implementations - Update test suites to work with new package structure - Improve module exports and type consistency
This commit is contained in:
1 parent
676a8903cb
commit
cb720ea073
43 files changed
+1847
-452
No files matched your search
@@ -135,8 +135,13 @@ prompt-optimizer/
|
||||
- npm >= 7
|
||||
|
||||
### 安装依赖
|
||||
npm install
|
||||
```bash
|
||||
# 安装 pnpm(如果未安装)
|
||||
npm install -g pnpm
|
||||
|
||||
# 安装项目依赖
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
@@ -145,15 +150,38 @@ npm install
|
||||
VITE_GEMINI_API_KEY=你的Gemini API密钥
|
||||
VITE_DEEPSEEK_API_KEY=你的DeepSeek API密钥
|
||||
|
||||
### 开发命令
|
||||
|
||||
```bash
|
||||
# 启动 web 开发服务器
|
||||
pnpm dev:web
|
||||
|
||||
# 启动 core 包开发模式(监听修改)
|
||||
pnpm dev:core
|
||||
|
||||
# 构建项目
|
||||
pnpm build # 构建所有包
|
||||
pnpm build:core # 只构建 core 包
|
||||
pnpm build:web # 只构建 web 包
|
||||
|
||||
# 运行测试
|
||||
pnpm test # 运行所有测试
|
||||
pnpm test:core # 运行 core 包测试
|
||||
pnpm test:web # 运行 web 包测试
|
||||
```
|
||||
|
||||
### 启动开发服务器
|
||||
npm run dev
|
||||
```bash
|
||||
pnpm dev:web
|
||||
```
|
||||
访问 `http://localhost:5173` 查看应用
|
||||
|
||||
### 构建生产版本
|
||||
npm run build
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
构建后的文件将生成在 `dist` 目录下
|
||||
构建后的文件将生成在 `packages/web/dist` 目录下
|
||||
|
||||
### 发布到vercel
|
||||
```bash
|
||||
@@ -164,7 +192,6 @@ vercel login
|
||||
|
||||
# 发布项目
|
||||
vercel --prod
|
||||
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
Generated
+1254
File diff suppressed because it is too large.
Load diff
@@ -7,5 +7,14 @@
|
||||
"build:web": "pnpm --filter web build",
|
||||
"build:extension": "pnpm --filter extension build",
|
||||
"test": "pnpm -r test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/core": "^0.3.33",
|
||||
"@langchain/google-genai": "^0.1.6",
|
||||
"@langchain/openai": "^0.3.17",
|
||||
"uuid": "^11.0.5"
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,17 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts",
|
||||
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
||||
@@ -17,5 +25,11 @@
|
||||
"tsup": "^8.0.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/core": "^0.3.33",
|
||||
"@langchain/google-genai": "^0.1.6",
|
||||
"@langchain/openai": "^0.3.17",
|
||||
"uuid": "^11.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,35 @@
|
||||
// Core package entry point
|
||||
export * from './types'
|
||||
export * from './services'
|
||||
export * from './services'
|
||||
export * from './services/llm/service'
|
||||
export * from './services/llm/types'
|
||||
export * from './services/model/manager'
|
||||
export * from './services/model/types'
|
||||
export * from './services/prompt/service'
|
||||
export * from './services/prompt/types'
|
||||
export * from './services/template/manager'
|
||||
export * from './services/template/types'
|
||||
export * from './services/template/errors'
|
||||
export * from './services/template/defaults'
|
||||
export * from './services/history/manager'
|
||||
export * from './services/history/types'
|
||||
export * from './services/history/errors'
|
||||
export * from './services/prompt/errors'
|
||||
|
||||
// 导出所有类型和错误
|
||||
export * from './services/llm/types'
|
||||
export * from './services/llm/errors'
|
||||
export * from './services/model/types'
|
||||
export * from './services/prompt/types'
|
||||
export * from './services/prompt/errors'
|
||||
export * from './services/template/types'
|
||||
export * from './services/template/errors'
|
||||
export * from './services/history/types'
|
||||
export * from './services/history/errors'
|
||||
|
||||
// 导出所有实现
|
||||
export { LLMService, createLLMService } from './services/llm/service'
|
||||
export { ModelManager } from './services/model/manager'
|
||||
export { PromptService, createPromptService } from './services/prompt/service'
|
||||
export { TemplateManager } from './services/template/manager'
|
||||
export { HistoryManager } from './services/history/manager'
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,17 @@
|
||||
// Core services
|
||||
export * from './template'
|
||||
export * from './history'
|
||||
export * from './llm'
|
||||
export * from './template/manager'
|
||||
export * from './template/types'
|
||||
|
||||
export * from './history/manager'
|
||||
export * from './history/types'
|
||||
|
||||
export * from './llm/service'
|
||||
export * from './llm/types'
|
||||
export * from './llm/errors'
|
||||
|
||||
export * from './model/manager'
|
||||
export * from './model/types'
|
||||
|
||||
export * from './prompt/service'
|
||||
export * from './prompt/types'
|
||||
export * from './prompt/errors'
|
||||
File renamed without changes.
+32
-3
@@ -77,9 +77,10 @@ export class LLMService implements ILLMService {
|
||||
case 'deepseek':
|
||||
case 'custom':
|
||||
model = new ChatOpenAI({
|
||||
openAIApiKey: modelConfig.apiKey,
|
||||
apiKey: modelConfig.apiKey,
|
||||
modelName: modelConfig.defaultModel,
|
||||
configuration: {
|
||||
apiKey: modelConfig.apiKey,
|
||||
baseURL: modelConfig.baseURL
|
||||
},
|
||||
temperature: 0.7,
|
||||
@@ -291,15 +292,43 @@ export class LLMService implements ILLMService {
|
||||
callbacks.onComplete();
|
||||
} catch (error) {
|
||||
console.error('流式处理过程中出错:', error);
|
||||
callbacks.onError(error);
|
||||
callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('流式请求失败:', error);
|
||||
callbacks.onError(error);
|
||||
callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*/
|
||||
async testConnection(provider: string): Promise<void> {
|
||||
try {
|
||||
if (!provider) {
|
||||
throw new RequestConfigError('模型提供商不能为空');
|
||||
}
|
||||
|
||||
// 发送一个简单的测试消息
|
||||
const testMessages: Message[] = [
|
||||
{
|
||||
role: 'user',
|
||||
content: '请回答ok'
|
||||
}
|
||||
];
|
||||
|
||||
// 使用 sendMessage 进行测试
|
||||
await this.sendMessage(testMessages, provider);
|
||||
|
||||
} catch (error: any) {
|
||||
if (error instanceof RequestConfigError || error instanceof APIError) {
|
||||
throw error;
|
||||
}
|
||||
throw new APIError(`连接测试失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出工厂函数
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ModelConfig } from '../model/types';
|
||||
import { BaseMessage } from '@langchain/core/messages';
|
||||
|
||||
/**
|
||||
* 消息角色类型
|
||||
@@ -67,6 +66,11 @@ export interface ILLMService {
|
||||
onError: (error: Error) => void;
|
||||
}
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*/
|
||||
testConnection(provider: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface StreamHandlers {
|
||||
+23
-8
@@ -1,11 +1,26 @@
|
||||
/// <reference types="vite/client" />
|
||||
import { ModelConfig } from './types';
|
||||
|
||||
// 获取环境变量的辅助函数
|
||||
const getEnvVar = (key: string): string => {
|
||||
if (typeof process !== 'undefined' && process.env && process.env[key]) {
|
||||
return process.env[key] || '';
|
||||
}
|
||||
// @ts-ignore
|
||||
if (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env[key]) {
|
||||
// @ts-ignore
|
||||
return import.meta.env[key] || '';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 从环境变量获取 API keys
|
||||
const OPENAI_API_KEY = (import.meta.env.VITE_OPENAI_API_KEY || '').trim();
|
||||
const GEMINI_API_KEY = (import.meta.env.VITE_GEMINI_API_KEY || '').trim();
|
||||
const DEEPSEEK_API_KEY = (import.meta.env.VITE_DEEPSEEK_API_KEY || '').trim();
|
||||
const CUSTOM_API_KEY = (import.meta.env.VITE_CUSTOM_API_KEY || '').trim();
|
||||
const OPENAI_API_KEY = getEnvVar('VITE_OPENAI_API_KEY').trim();
|
||||
const GEMINI_API_KEY = getEnvVar('VITE_GEMINI_API_KEY').trim();
|
||||
const DEEPSEEK_API_KEY = getEnvVar('VITE_DEEPSEEK_API_KEY').trim();
|
||||
const CUSTOM_API_KEY = getEnvVar('VITE_CUSTOM_API_KEY').trim();
|
||||
const CUSTOM_API_BASE_URL = getEnvVar('VITE_CUSTOM_API_BASE_URL');
|
||||
const CUSTOM_API_MODEL = getEnvVar('VITE_CUSTOM_API_MODEL');
|
||||
|
||||
export const defaultModels: Record<string, ModelConfig> = {
|
||||
openai: {
|
||||
@@ -19,7 +34,7 @@ export const defaultModels: Record<string, ModelConfig> = {
|
||||
},
|
||||
gemini: {
|
||||
name: 'Google Gemini',
|
||||
baseURL: 'https://geminiapi.always200.com/v1beta/openai',
|
||||
baseURL: 'https://geminiapi.always200.com/v1beta',
|
||||
models: ['gemini-2.0-flash'],
|
||||
defaultModel: 'gemini-2.0-flash',
|
||||
apiKey: GEMINI_API_KEY,
|
||||
@@ -37,9 +52,9 @@ export const defaultModels: Record<string, ModelConfig> = {
|
||||
},
|
||||
custom: {
|
||||
name: '自定义API',
|
||||
baseURL: import.meta.env.VITE_CUSTOM_API_BASE_URL,
|
||||
models: [import.meta.env.VITE_CUSTOM_API_MODEL],
|
||||
defaultModel: import.meta.env.VITE_CUSTOM_API_MODEL,
|
||||
baseURL: CUSTOM_API_BASE_URL,
|
||||
models: [CUSTOM_API_MODEL],
|
||||
defaultModel: CUSTOM_API_MODEL,
|
||||
apiKey: CUSTOM_API_KEY,
|
||||
enabled: !!CUSTOM_API_KEY,
|
||||
provider: 'custom'
|
||||
+3
-2
@@ -184,7 +184,8 @@ export class ModelManager implements IModelManager {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模型配置失败:', error);
|
||||
throw new Error('加载模型配置失败: ' + error);
|
||||
// 不抛出错误,而是使用默认配置
|
||||
this.models = { ...defaultModels };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +197,7 @@ export class ModelManager implements IModelManager {
|
||||
localStorage.setItem('models', JSON.stringify(this.models));
|
||||
} catch (error) {
|
||||
console.error('保存模型配置失败:', error);
|
||||
throw new Error('保存模型配置失败: ' + error);
|
||||
// 不抛出错误,继续使用内存中的配置
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+32
-78
@@ -79,7 +79,6 @@ export class PromptService implements IPromptService {
|
||||
*/
|
||||
async optimizePrompt(prompt: string, modelKey: string): Promise<string> {
|
||||
try {
|
||||
// 前置验证
|
||||
this.validateInput(prompt, modelKey);
|
||||
|
||||
// 获取模型配置(使用统一错误)
|
||||
@@ -96,8 +95,8 @@ export class PromptService implements IPromptService {
|
||||
try {
|
||||
template = await this.templateManager.getTemplate('optimize');
|
||||
} catch (error) {
|
||||
// 包装错误消息
|
||||
throw new OptimizationError(`优化失败: ${error.message}`, prompt);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new OptimizationError(`优化失败: ${errorMessage}`, prompt);
|
||||
}
|
||||
|
||||
if (!template?.content) {
|
||||
@@ -134,14 +133,8 @@ export class PromptService implements IPromptService {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 统一错误处理
|
||||
if (error instanceof OptimizationError) {
|
||||
throw error;
|
||||
}
|
||||
throw new OptimizationError(
|
||||
`${ERROR_MESSAGES.OPTIMIZATION_FAILED}: ${error.message}`,
|
||||
prompt
|
||||
);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new OptimizationError(`优化失败: ${errorMessage}`, prompt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +147,9 @@ export class PromptService implements IPromptService {
|
||||
modelKey: string
|
||||
): Promise<string> {
|
||||
try {
|
||||
this.validateInput(originalPrompt, modelKey);
|
||||
this.validateInput(iterateInput, modelKey);
|
||||
|
||||
// 获取模型配置
|
||||
const modelConfig = this.modelManager.getModel(modelKey);
|
||||
if (!modelConfig) {
|
||||
@@ -165,8 +161,8 @@ export class PromptService implements IPromptService {
|
||||
try {
|
||||
template = await this.templateManager.getTemplate('iterate');
|
||||
} catch (error) {
|
||||
// 包装错误消息
|
||||
throw new IterationError(`迭代失败: ${error.message}`, originalPrompt, iterateInput);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new IterationError(`迭代失败: ${errorMessage}`, originalPrompt, iterateInput);
|
||||
}
|
||||
|
||||
if (!template?.content) {
|
||||
@@ -201,26 +197,19 @@ export class PromptService implements IPromptService {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof IterationError) {
|
||||
throw error;
|
||||
}
|
||||
throw new IterationError(
|
||||
`迭代失败: ${error.message}`,
|
||||
originalPrompt,
|
||||
iterateInput
|
||||
);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new IterationError(`迭代失败: ${errorMessage}`, originalPrompt, iterateInput);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试提示词
|
||||
*/
|
||||
async testPrompt(
|
||||
prompt: string,
|
||||
testInput: string,
|
||||
modelKey: string
|
||||
): Promise<string> {
|
||||
async testPrompt(prompt: string, testInput: string, modelKey: string): Promise<string> {
|
||||
try {
|
||||
this.validateInput(prompt, modelKey);
|
||||
this.validateInput(testInput, modelKey);
|
||||
|
||||
// 获取模型配置
|
||||
const modelConfig = this.modelManager.getModel(modelKey);
|
||||
if (!modelConfig) {
|
||||
@@ -254,14 +243,8 @@ export class PromptService implements IPromptService {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error instanceof TestError) {
|
||||
throw error;
|
||||
}
|
||||
throw new TestError(
|
||||
`测试失败: ${error.message}`,
|
||||
prompt,
|
||||
testInput
|
||||
);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new TestError(`测试失败: ${errorMessage}`, prompt, testInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +273,9 @@ export class PromptService implements IPromptService {
|
||||
}
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.validateInput(prompt, modelKey);
|
||||
this.validateInput(testInput, modelKey);
|
||||
|
||||
const modelConfig = this.modelManager.getModel(modelKey);
|
||||
if (!modelConfig) {
|
||||
throw new ServiceDependencyError('模型不存在', 'ModelManager');
|
||||
@@ -304,14 +290,8 @@ export class PromptService implements IPromptService {
|
||||
|
||||
// 移除历史记录相关操作
|
||||
} catch (error) {
|
||||
if (error instanceof TestError) {
|
||||
throw error;
|
||||
}
|
||||
throw new TestError(
|
||||
`测试失败: ${error.message}`,
|
||||
prompt,
|
||||
testInput
|
||||
);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new TestError(`测试失败: ${errorMessage}`, prompt, testInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +309,6 @@ export class PromptService implements IPromptService {
|
||||
}
|
||||
): Promise<void> {
|
||||
try {
|
||||
// 前置验证
|
||||
this.validateInput(prompt, modelKey);
|
||||
|
||||
// 获取模型配置
|
||||
@@ -366,16 +345,8 @@ export class PromptService implements IPromptService {
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof OptimizationError) {
|
||||
callbacks.onError(error);
|
||||
throw error;
|
||||
}
|
||||
const wrappedError = new OptimizationError(
|
||||
`${ERROR_MESSAGES.OPTIMIZATION_FAILED}: ${error.message}`,
|
||||
prompt
|
||||
);
|
||||
callbacks.onError(wrappedError);
|
||||
throw wrappedError;
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new OptimizationError(`优化失败: ${errorMessage}`, prompt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +361,9 @@ export class PromptService implements IPromptService {
|
||||
template: { content: string } | string
|
||||
): Promise<void> {
|
||||
try {
|
||||
this.validateInput(originalPrompt, modelKey);
|
||||
this.validateInput(iterateInput, modelKey);
|
||||
|
||||
// 获取模型配置
|
||||
const modelConfig = this.modelManager.getModel(modelKey);
|
||||
if (!modelConfig) {
|
||||
@@ -432,17 +406,8 @@ export class PromptService implements IPromptService {
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof IterationError) {
|
||||
handlers.onError(error);
|
||||
throw error;
|
||||
}
|
||||
const wrappedError = new IterationError(
|
||||
`迭代失败: ${error.message}`,
|
||||
originalPrompt,
|
||||
iterateInput
|
||||
);
|
||||
handlers.onError(wrappedError);
|
||||
throw wrappedError;
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new IterationError(`迭代失败: ${errorMessage}`, originalPrompt, iterateInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,22 +420,11 @@ export async function createPromptService(
|
||||
historyManager: HistoryManager = defaultHistoryManager
|
||||
): Promise<PromptService> {
|
||||
try {
|
||||
// 初始化依赖服务
|
||||
await templateManager.init();
|
||||
await historyManager.init();
|
||||
|
||||
// 创建并返回服务实例
|
||||
return new PromptService(
|
||||
modelManager,
|
||||
llmService,
|
||||
templateManager,
|
||||
historyManager
|
||||
);
|
||||
return new PromptService(modelManager, llmService, templateManager, historyManager);
|
||||
} catch (error) {
|
||||
console.error('创建 PromptService 失败:', error);
|
||||
throw new ServiceDependencyError(
|
||||
`提示词管理器初始化失败: ${error.message}`,
|
||||
'TemplateManager'
|
||||
);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`初始化失败: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Message } from '../llm/types';
|
||||
import { PromptRecord } from '../history/types';
|
||||
|
||||
/**
|
||||
File renamed without changes.
File renamed without changes.
+13
-24
@@ -1,6 +1,6 @@
|
||||
import { ITemplateManager, Template, TemplateManagerConfig, templateSchema } from './types';
|
||||
import { DEFAULT_TEMPLATES } from './defaults';
|
||||
import { TemplateError, TemplateValidationError, TemplateStorageError } from './errors';
|
||||
import { TemplateError, TemplateValidationError } from './errors';
|
||||
|
||||
/**
|
||||
* 提示词管理器实现
|
||||
@@ -44,7 +44,8 @@ export class TemplateManager implements ITemplateManager {
|
||||
|
||||
await this.loadUserTemplates();
|
||||
} catch (error) {
|
||||
throw new TemplateError(`初始化失败: ${error.message}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new TemplateError(`初始化失败: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,17 +183,13 @@ export class TemplateManager implements ITemplateManager {
|
||||
const template = JSON.parse(templateJson) as Template;
|
||||
const result = templateSchema.safeParse(template);
|
||||
if (!result.success) {
|
||||
throw new TemplateValidationError(`提示词验证失败: ${result.error.message}`);
|
||||
throw new TemplateValidationError(`提示词验证失败: ${result.error.errors.map(e => e.message).join(', ')}`);
|
||||
}
|
||||
await this.saveTemplate(template);
|
||||
this.userTemplates.set(template.id, template);
|
||||
await this.persistUserTemplates();
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
throw new TemplateValidationError('提示词格式无效');
|
||||
}
|
||||
if (error instanceof TemplateValidationError) {
|
||||
throw error;
|
||||
}
|
||||
throw new TemplateError(`导入提示词失败: ${error.message}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new TemplateError(`导入提示词失败: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,16 +204,6 @@ export class TemplateManager implements ITemplateManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证提示词
|
||||
*/
|
||||
private async validateTemplate(template: Template): Promise<void> {
|
||||
const result = templateSchema.safeParse(template);
|
||||
if (!result.success) {
|
||||
throw new TemplateValidationError(`提示词验证失败: ${result.error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化用户提示词
|
||||
*/
|
||||
@@ -225,7 +212,8 @@ export class TemplateManager implements ITemplateManager {
|
||||
const templates = Array.from(this.userTemplates.values());
|
||||
localStorage.setItem(this.config.storageKey, JSON.stringify(templates));
|
||||
} catch (error) {
|
||||
throw new TemplateError(`保存提示词失败: ${error.message}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new TemplateError(`保存用户提示词失败: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,11 +226,12 @@ export class TemplateManager implements ITemplateManager {
|
||||
if (data) {
|
||||
const templates = JSON.parse(data) as Template[];
|
||||
templates.forEach(template => {
|
||||
this.userTemplates.set(template.id, { ...template, isBuiltin: false });
|
||||
this.userTemplates.set(template.id, template);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw new TemplateError(`加载用户提示词失败: ${error.message}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new TemplateError(`加载用户提示词失败: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-15
@@ -64,18 +64,4 @@ export const templateSchema = z.object({
|
||||
templateType: z.enum(['optimize', 'iterate'])
|
||||
}),
|
||||
isBuiltin: z.boolean().optional()
|
||||
});
|
||||
|
||||
export class TemplateValidationError extends Error {
|
||||
constructor(message: string, public templateId: string, public errors: string[] = []) {
|
||||
super(message);
|
||||
this.name = 'TemplateValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplateLoadError extends Error {
|
||||
constructor(message: string, public templateId: string) {
|
||||
super(message);
|
||||
this.name = 'TemplateLoadError';
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -156,10 +156,13 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, computed, watch } from 'vue'
|
||||
import { createLLMService } from './services/llm/service'
|
||||
import { createPromptService } from './services/prompt/service'
|
||||
import { modelManager } from './services/model/manager'
|
||||
import { templateManager } from './services/template/manager'
|
||||
import {
|
||||
createLLMService,
|
||||
createPromptService,
|
||||
modelManager,
|
||||
templateManager,
|
||||
historyManager
|
||||
} from '@prompt-optimizer/core'
|
||||
import ModelManager from './components/ModelManager.vue'
|
||||
import TemplateManager from './components/TemplateManager.vue'
|
||||
import Toast from './components/Toast.vue'
|
||||
@@ -169,7 +172,6 @@ import InputPanel from './components/InputPanel.vue'
|
||||
import OutputPanel from './components/OutputPanel.vue'
|
||||
import { useToast } from './composables/useToast'
|
||||
import TemplateSelect from './components/TemplateSelect.vue'
|
||||
import { historyManager } from './services/history/manager'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// 初始化服务
|
||||
|
||||
@@ -44,6 +44,10 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="testConnection(model.key)"
|
||||
class="px-4 py-1.5 text-sm rounded-lg bg-blue-600/20 text-blue-300 hover:bg-blue-600/30 transition-colors">
|
||||
测试连接
|
||||
</button>
|
||||
<button @click="editModel(model.key)"
|
||||
class="px-4 py-1.5 text-sm rounded-lg bg-purple-600/20 text-purple-300 hover:bg-purple-600/30 transition-colors">
|
||||
编辑
|
||||
@@ -221,7 +225,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, defineEmits } from 'vue';
|
||||
import { modelManager } from '../services/model/manager';
|
||||
import { modelManager, createLLMService } from '@prompt-optimizer/core';
|
||||
import { useToast } from '../composables/useToast';
|
||||
|
||||
const toast = useToast();
|
||||
@@ -371,6 +375,33 @@ const addCustomModel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 测试连接
|
||||
const testConnection = async (key) => {
|
||||
const model = modelManager.getModel(key);
|
||||
if (!model) {
|
||||
toast.error('模型不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!model.enabled) {
|
||||
toast.error('请先启用模型');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
toast.info('正在测试连接...');
|
||||
const llmService = createLLMService(modelManager);
|
||||
|
||||
// 发送一个简单的测试请求
|
||||
await llmService.testConnection(key);
|
||||
|
||||
toast.success('连接测试成功');
|
||||
} catch (error) {
|
||||
console.error('连接测试失败:', error);
|
||||
toast.error(error.message || '连接测试失败');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadModels();
|
||||
});
|
||||
|
||||
@@ -278,7 +278,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { templateManager } from '../services/template/manager'
|
||||
import { templateManager } from '@prompt-optimizer/core'
|
||||
import { useToast } from '../composables/useToast'
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import { templateManager } from '../services/template/manager'
|
||||
import { templateManager } from '@prompt-optimizer/core'
|
||||
import { clickOutside } from '../directives/clickOutside'
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -1,84 +1,31 @@
|
||||
import { createLLMService } from '../../../../src/services/llm/service';
|
||||
import { ModelManager } from '../../../../src/services/model/manager';
|
||||
import { expect, describe, it, beforeEach, beforeAll, vi } from 'vitest';
|
||||
import { APIError, RequestConfigError } from '../../../../src/services/llm/errors';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { ERROR_MESSAGES } from '../../../../src/services/llm/errors';
|
||||
import { createPromptService } from '../../../../src/services/prompt/factory';
|
||||
|
||||
// 加载环境变量
|
||||
beforeAll(() => {
|
||||
dotenv.config({ path: path.resolve(process.cwd(), '.env.local') });
|
||||
});
|
||||
import {
|
||||
createLLMService,
|
||||
ModelManager,
|
||||
APIError,
|
||||
RequestConfigError,
|
||||
ERROR_MESSAGES
|
||||
} from '@prompt-optimizer/core';
|
||||
import { expect, describe, it, beforeEach } from 'vitest';
|
||||
|
||||
describe('LLM 服务通用测试', () => {
|
||||
let llmService;
|
||||
let modelManager;
|
||||
let promptService;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
modelManager = new ModelManager();
|
||||
// 清除所有已有模型
|
||||
const models = modelManager.getAllModels();
|
||||
models.forEach(model => {
|
||||
if (model.key) {
|
||||
modelManager.deleteModel(model.key);
|
||||
}
|
||||
});
|
||||
llmService = createLLMService(modelManager);
|
||||
|
||||
// Mock ChatOpenAI
|
||||
vi.mock('@langchain/openai', () => ({
|
||||
ChatOpenAI: vi.fn().mockImplementation(() => ({
|
||||
invoke: vi.fn().mockRejectedValue(new Error('API Error'))
|
||||
}))
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
promptService = await createPromptService();
|
||||
});
|
||||
|
||||
describe('API 调用错误处理', () => {
|
||||
it('应该能正确处理无效的 API 密钥', async () => {
|
||||
const testModel = 'test';
|
||||
modelManager.addModel(testModel, {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://test.api/chat/completions',
|
||||
models: ['test-model'],
|
||||
defaultModel: 'test-model',
|
||||
apiKey: 'invalid-key',
|
||||
enabled: true,
|
||||
provider: 'openai'
|
||||
});
|
||||
|
||||
const messages = [
|
||||
{ role: 'user', content: '你好,我们来玩个游戏' }
|
||||
];
|
||||
|
||||
await expect(llmService.sendMessage(messages, testModel))
|
||||
.rejects
|
||||
.toThrow(APIError);
|
||||
}, 10000); // 增加超时时间到 10 秒
|
||||
|
||||
it('应该能正确处理无效的 baseURL', async () => {
|
||||
const testModel = 'test';
|
||||
modelManager.addModel(testModel, {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://invalid.api/chat/completions',
|
||||
models: ['test-model'],
|
||||
defaultModel: 'test-model',
|
||||
apiKey: 'test-key',
|
||||
enabled: true,
|
||||
provider: 'openai'
|
||||
});
|
||||
|
||||
const messages = [
|
||||
{ role: 'user', content: '你好,我们来玩个游戏' }
|
||||
];
|
||||
|
||||
await expect(llmService.sendMessage(messages, testModel))
|
||||
.rejects
|
||||
.toThrow(APIError);
|
||||
}, 10000); // 增加超时时间到 10 秒
|
||||
|
||||
it('应该能正确处理无效的消息格式', async () => {
|
||||
const testModel = 'test';
|
||||
const testModel = 'test-invalid-message';
|
||||
modelManager.addModel(testModel, {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://test.api/chat/completions',
|
||||
@@ -97,7 +44,7 @@ describe('LLM 服务通用测试', () => {
|
||||
});
|
||||
|
||||
it('应该能正确处理未启用的模型', async () => {
|
||||
const testModel = 'test';
|
||||
const testModel = 'test-disabled';
|
||||
modelManager.addModel(testModel, {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://test.api/chat/completions',
|
||||
@@ -118,7 +65,7 @@ describe('LLM 服务通用测试', () => {
|
||||
});
|
||||
|
||||
it('应该能正确处理空消息列表', async () => {
|
||||
const testModel = 'test';
|
||||
const testModel = 'test-empty-messages';
|
||||
modelManager.addModel(testModel, {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://test.api/chat/completions',
|
||||
@@ -133,18 +80,11 @@ describe('LLM 服务通用测试', () => {
|
||||
await llmService.sendMessage([], testModel);
|
||||
}).rejects.toThrow(RequestConfigError);
|
||||
});
|
||||
|
||||
it('应正确处理无效的模型配置', async () => {
|
||||
vi.spyOn(modelManager, 'getModel').mockReturnValue(undefined);
|
||||
await expect(promptService.optimizePrompt('test', 'test-model')).rejects.toThrow(
|
||||
ERROR_MESSAGES.MODEL_NOT_FOUND
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('配置管理', () => {
|
||||
it('应该能正确处理模型配置更新', () => {
|
||||
const testModel = 'test';
|
||||
const testModel = 'test-update';
|
||||
const config = {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://test.api/chat/completions',
|
||||
@@ -173,7 +113,7 @@ describe('LLM 服务通用测试', () => {
|
||||
});
|
||||
|
||||
it('应该能正确处理模型的启用和禁用', () => {
|
||||
const testModel = 'test';
|
||||
const testModel = 'test-enable-disable';
|
||||
const config = {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://test.api/chat/completions',
|
||||
@@ -185,7 +125,8 @@ describe('LLM 服务通用测试', () => {
|
||||
};
|
||||
|
||||
modelManager.addModel(testModel, config);
|
||||
expect(modelManager.getModel(testModel).enabled).toBe(true);
|
||||
const model = modelManager.getModel(testModel);
|
||||
expect(model.enabled).toBe(true);
|
||||
|
||||
modelManager.updateModel(testModel, { enabled: false });
|
||||
expect(modelManager.getModel(testModel).enabled).toBe(false);
|
||||
@@ -194,11 +135,4 @@ describe('LLM 服务通用测试', () => {
|
||||
expect(modelManager.getModel(testModel).enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界条件测试', () => {
|
||||
it('应处理空提示词输入', async () => {
|
||||
await expect(promptService.optimizePrompt('', 'test-model'))
|
||||
.rejects.toThrow(ERROR_MESSAGES.EMPTY_INPUT);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createLLMService } from '../../../../src/services/llm/service';
|
||||
import { ModelManager } from '../../../../src/services/model/manager';
|
||||
import { createLLMService, ModelManager } from '@prompt-optimizer/core';
|
||||
import { expect, describe, it, beforeEach, beforeAll } from 'vitest';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createLLMService } from '../../../../src/services/llm/service';
|
||||
import { ModelManager } from '../../../../src/services/model/manager';
|
||||
import { createLLMService, ModelManager } from '@prompt-optimizer/core';
|
||||
import { expect, describe, it, beforeEach, beforeAll } from 'vitest';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createLLMService } from '../../../../src/services/llm/service';
|
||||
import { ModelManager } from '../../../../src/services/model/manager';
|
||||
import { createLLMService, ModelManager } from '@prompt-optimizer/core';
|
||||
import { expect, describe, it, beforeEach, beforeAll } from 'vitest';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
@@ -2,15 +2,11 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
import App from '../../src/App.vue'
|
||||
import { createLLMService } from '../../src/services/llm/service'
|
||||
import { createPromptService } from '../../src/services/prompt/service'
|
||||
import { modelManager } from '../../src/services/model/manager'
|
||||
import { templateManager } from '../../src/services/template/manager'
|
||||
import { createLLMService, createPromptService, modelManager, templateManager, historyManager } from '@prompt-optimizer/core'
|
||||
import ElementPlus from 'element-plus'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { ElInput, ElButton, ElSelect, ElOption } from 'element-plus'
|
||||
import { useToast } from '../../src/composables/useToast'
|
||||
import { historyManager } from '../../src/services/history/manager'
|
||||
|
||||
// Mock toast functions
|
||||
const mockSuccess = vi.fn()
|
||||
@@ -90,30 +86,48 @@ const mockPromptService = {
|
||||
getHistory: vi.fn().mockReturnValue([])
|
||||
}
|
||||
|
||||
vi.mock('../../src/services/llm/service', () => ({
|
||||
createLLMService: vi.fn(() => mockLLMService)
|
||||
}))
|
||||
|
||||
vi.mock('../../src/services/prompt/service', () => ({
|
||||
createPromptService: vi.fn(() => mockPromptService)
|
||||
}))
|
||||
|
||||
vi.mock('../../src/services/model/manager', () => ({
|
||||
vi.mock('@prompt-optimizer/core', () => ({
|
||||
createLLMService: vi.fn(() => mockLLMService),
|
||||
createPromptService: vi.fn(() => mockPromptService),
|
||||
modelManager: {
|
||||
getAllModels: vi.fn(() => [
|
||||
{ key: 'test', name: 'Test Model', model: 'test-model', enabled: true }
|
||||
])
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock historyManager
|
||||
vi.mock('../../src/services/history/manager', () => ({
|
||||
},
|
||||
historyManager: {
|
||||
init: vi.fn(),
|
||||
clearHistory: vi.fn(),
|
||||
createNewChain: vi.fn(),
|
||||
addIteration: vi.fn(),
|
||||
getAllChains: vi.fn()
|
||||
},
|
||||
templateManager: {
|
||||
init: vi.fn(),
|
||||
clearCache: vi.fn(),
|
||||
getTemplate: vi.fn(),
|
||||
listTemplates: vi.fn(),
|
||||
listTemplatesByType: vi.fn().mockReturnValue([
|
||||
{
|
||||
id: 'template-1',
|
||||
name: '优化提示词',
|
||||
content: '优化模板内容',
|
||||
metadata: {
|
||||
templateType: 'optimize',
|
||||
version: '1.0',
|
||||
lastModified: Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'template-2',
|
||||
name: '迭代提示词',
|
||||
content: '迭代模板内容',
|
||||
metadata: {
|
||||
templateType: 'iterate',
|
||||
version: '1.0',
|
||||
lastModified: Date.now()
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { HistoryManager } from '../../../../src/services/history/manager';
|
||||
import { HistoryError, RecordNotFoundError, StorageError } from '../../../../src/services/history/errors';
|
||||
import { PromptRecord } from '../../../../src/services/history/types';
|
||||
import {
|
||||
HistoryManager,
|
||||
HistoryError,
|
||||
RecordNotFoundError,
|
||||
StorageError,
|
||||
PromptRecord
|
||||
} from '@prompt-optimizer/core';
|
||||
|
||||
describe('HistoryManager', () => {
|
||||
let manager: HistoryManager;
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { LLMService } from '../../../src/services/llm/service';
|
||||
import { ModelManager } from '../../../src/services/model/manager';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
|
||||
import { HumanMessage, SystemMessage, AIMessage } from '@langchain/core/messages';
|
||||
import { RequestConfigError, APIError } from '../../../src/services/llm/errors';
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LLMService, ModelManager, RequestConfigError, APIError } from '@prompt-optimizer/core';
|
||||
|
||||
describe('LLMService', () => {
|
||||
let llmService;
|
||||
@@ -15,12 +10,19 @@ describe('LLMService', () => {
|
||||
apiKey: 'test-key',
|
||||
baseURL: 'https://api.test.com',
|
||||
defaultModel: 'test-model',
|
||||
enabled: true
|
||||
enabled: true,
|
||||
models: ['test-model']
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
modelManager = new ModelManager();
|
||||
vi.spyOn(modelManager, 'getModel').mockReturnValue(mockModelConfig);
|
||||
// 清除所有已有模型
|
||||
const models = modelManager.getAllModels();
|
||||
models.forEach(model => {
|
||||
if (model.key) {
|
||||
modelManager.deleteModel(model.key);
|
||||
}
|
||||
});
|
||||
llmService = new LLMService(modelManager);
|
||||
});
|
||||
|
||||
@@ -52,6 +54,12 @@ describe('LLMService', () => {
|
||||
expect(() => llmService['validateModelConfig'](invalidConfig))
|
||||
.toThrow('API密钥不能为空');
|
||||
});
|
||||
|
||||
it('当模型未启用时应抛出错误', () => {
|
||||
const disabledConfig = { ...mockModelConfig, enabled: false };
|
||||
expect(() => llmService['validateModelConfig'](disabledConfig))
|
||||
.toThrow('模型未启用');
|
||||
});
|
||||
});
|
||||
|
||||
describe('请求配置构建', () => {
|
||||
@@ -72,30 +80,16 @@ describe('LLMService', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('消息发送', () => {
|
||||
it('应该正确发送消息', async () => {
|
||||
const mockResponse = new AIMessage('test response');
|
||||
vi.spyOn(ChatOpenAI.prototype, 'invoke').mockResolvedValue(mockResponse);
|
||||
|
||||
const messages = [
|
||||
{ role: 'system', content: 'test' },
|
||||
{ role: 'user', content: 'test' }
|
||||
];
|
||||
const result = await llmService.sendMessage(messages, testProvider);
|
||||
expect(result).toBe('test response');
|
||||
it('当消息为空时应抛出错误', () => {
|
||||
expect(() => llmService.buildRequestConfig(mockModelConfig, []))
|
||||
.toThrow(RequestConfigError);
|
||||
});
|
||||
|
||||
it('当请求失败时应抛出错误', async () => {
|
||||
vi.spyOn(ChatOpenAI.prototype, 'invoke').mockRejectedValue(new Error('API Error'));
|
||||
|
||||
const messages = [
|
||||
{ role: 'system', content: 'test' }
|
||||
];
|
||||
await expect(llmService.sendMessage(messages, testProvider))
|
||||
.rejects
|
||||
.toThrow('发送消息失败: API Error');
|
||||
it('当消息格式无效时应抛出错误', () => {
|
||||
const invalidMessages = [{ role: 'invalid', content: 'test' }];
|
||||
expect(() => llmService.buildRequestConfig(mockModelConfig, invalidMessages))
|
||||
.toThrow(RequestConfigError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { LLMService } from '../../../../src/services/llm/service';
|
||||
import { APIError, RequestConfigError } from '../../../../src/services/llm/errors';
|
||||
import { ModelConfig } from '../../../../src/services/model/types';
|
||||
import { Message } from '../../../../src/services/llm/types';
|
||||
import { ModelManager } from '../../../../src/services/model/manager';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
|
||||
import { AIMessageChunk } from '@langchain/core/messages';
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import {
|
||||
LLMService,
|
||||
ModelManager,
|
||||
ModelConfig,
|
||||
APIError,
|
||||
RequestConfigError,
|
||||
Message
|
||||
} from '@prompt-optimizer/core';
|
||||
|
||||
describe('LLMService', () => {
|
||||
let service: LLMService;
|
||||
@@ -15,7 +15,6 @@ describe('LLMService', () => {
|
||||
beforeEach(() => {
|
||||
modelManager = new ModelManager();
|
||||
service = new LLMService(modelManager);
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const mockModelConfig: ModelConfig = {
|
||||
@@ -33,50 +32,81 @@ describe('LLMService', () => {
|
||||
{ role: 'user', content: 'Hello!' }
|
||||
];
|
||||
|
||||
describe('sendMessage', () => {
|
||||
it('should throw error when model is disabled', async () => {
|
||||
describe('validateModelConfig', () => {
|
||||
it('should throw error when model is disabled', () => {
|
||||
const disabledConfig = { ...mockModelConfig, enabled: false };
|
||||
vi.spyOn(modelManager, 'getModel').mockReturnValue(disabledConfig);
|
||||
expect(() => service['validateModelConfig'](disabledConfig))
|
||||
.toThrow('模型未启用');
|
||||
});
|
||||
|
||||
await expect(service.sendMessage(mockMessages, 'test'))
|
||||
.rejects
|
||||
it('should throw error when apiKey is missing', () => {
|
||||
const invalidConfig = { ...mockModelConfig, apiKey: '' };
|
||||
expect(() => service['validateModelConfig'](invalidConfig))
|
||||
.toThrow('API密钥不能为空');
|
||||
});
|
||||
|
||||
it('should throw error when provider is missing', () => {
|
||||
const invalidConfig = { ...mockModelConfig, provider: '' };
|
||||
expect(() => service['validateModelConfig'](invalidConfig))
|
||||
.toThrow('模型提供商不能为空');
|
||||
});
|
||||
|
||||
it('should throw error when defaultModel is missing', () => {
|
||||
const invalidConfig = { ...mockModelConfig, defaultModel: '' };
|
||||
expect(() => service['validateModelConfig'](invalidConfig))
|
||||
.toThrow('默认模型不能为空');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateMessages', () => {
|
||||
it('should validate valid messages', () => {
|
||||
expect(() => service['validateMessages'](mockMessages)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for empty messages', () => {
|
||||
expect(() => service['validateMessages']([]))
|
||||
.toThrow('消息列表不能为空');
|
||||
});
|
||||
|
||||
it('should throw error for invalid role', () => {
|
||||
const invalidMessages: Message[] = [{ role: 'invalid' as any, content: 'test' }];
|
||||
expect(() => service['validateMessages'](invalidMessages))
|
||||
.toThrow('不支持的消息类型: invalid');
|
||||
});
|
||||
|
||||
it('should throw error for missing content', () => {
|
||||
const invalidMessages: Message[] = [{ role: 'user', content: '' }];
|
||||
expect(() => service['validateMessages'](invalidMessages))
|
||||
.toThrow('消息格式无效: 缺少必要字段');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildRequestConfig', () => {
|
||||
it('should build valid request config', () => {
|
||||
const config = service.buildRequestConfig(mockModelConfig, mockMessages);
|
||||
expect(config).toEqual({
|
||||
url: 'https://api.test.com/chat/completions',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer test-key'
|
||||
},
|
||||
body: {
|
||||
model: 'model-1',
|
||||
messages: mockMessages
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error for invalid model config', () => {
|
||||
const invalidConfig = { ...mockModelConfig, apiKey: '' };
|
||||
expect(() => service.buildRequestConfig(invalidConfig, mockMessages))
|
||||
.toThrow(RequestConfigError);
|
||||
});
|
||||
|
||||
it('should throw error when model does not exist', async () => {
|
||||
vi.spyOn(modelManager, 'getModel').mockReturnValue(undefined);
|
||||
|
||||
await expect(service.sendMessage(mockMessages, 'non-existent'))
|
||||
.rejects
|
||||
it('should throw error for invalid messages', () => {
|
||||
const invalidMessages: Message[] = [{ role: 'invalid' as any, content: 'test' }];
|
||||
expect(() => service.buildRequestConfig(mockModelConfig, invalidMessages))
|
||||
.toThrow(RequestConfigError);
|
||||
});
|
||||
|
||||
it('should successfully send message using OpenAI model', async () => {
|
||||
vi.spyOn(modelManager, 'getModel').mockReturnValue(mockModelConfig);
|
||||
const mockResponse = new AIMessageChunk('Test response');
|
||||
vi.spyOn(ChatOpenAI.prototype, 'invoke').mockResolvedValue(mockResponse);
|
||||
|
||||
const response = await service.sendMessage(mockMessages, 'test');
|
||||
expect(response).toBe('Test response');
|
||||
});
|
||||
|
||||
it('should successfully send message using Gemini model', async () => {
|
||||
const geminiConfig = { ...mockModelConfig, provider: 'gemini' };
|
||||
vi.spyOn(modelManager, 'getModel').mockReturnValue(geminiConfig);
|
||||
const mockResponse = new AIMessageChunk('Test response');
|
||||
vi.spyOn(ChatGoogleGenerativeAI.prototype, 'invoke').mockResolvedValue(mockResponse);
|
||||
|
||||
const response = await service.sendMessage(mockMessages, 'test');
|
||||
expect(response).toBe('Test response');
|
||||
});
|
||||
|
||||
it('should handle API errors gracefully', async () => {
|
||||
vi.spyOn(modelManager, 'getModel').mockReturnValue(mockModelConfig);
|
||||
vi.spyOn(ChatOpenAI.prototype, 'invoke').mockRejectedValue(new Error('API Error'));
|
||||
|
||||
await expect(service.sendMessage(mockMessages, 'test'))
|
||||
.rejects
|
||||
.toThrow(APIError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,15 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { ModelManager } from '../../../../src/services/model/manager';
|
||||
import { ModelConfig } from '../../../../src/services/model/types';
|
||||
import { ModelConfigError } from '../../../../src/services/llm/errors';
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import {
|
||||
ModelManager,
|
||||
ModelConfig,
|
||||
ModelConfigError
|
||||
} from '@prompt-optimizer/core';
|
||||
|
||||
describe('ModelManager', () => {
|
||||
let manager: ModelManager;
|
||||
let mockModelConfig: ModelConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
// 清除 localStorage
|
||||
localStorage.clear();
|
||||
|
||||
mockModelConfig = {
|
||||
name: 'Test Model',
|
||||
baseURL: 'https://test.api',
|
||||
@@ -21,11 +20,13 @@ describe('ModelManager', () => {
|
||||
provider: 'openai'
|
||||
};
|
||||
|
||||
// 创建一个新的 ModelManager 实例,但先清除默认模型
|
||||
manager = new ModelManager();
|
||||
// 清除默认模型
|
||||
const defaultModels = manager.getAllModels();
|
||||
defaultModels.forEach(model => {
|
||||
manager.deleteModel(model.key);
|
||||
if (model.key) {
|
||||
manager.deleteModel(model.key);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,9 +59,12 @@ describe('ModelManager', () => {
|
||||
baseURL: 'https://updated.api'
|
||||
};
|
||||
manager.updateModel('test', updateConfig);
|
||||
const models = manager.getAllModels();
|
||||
expect(models[0].name).toBe('Updated Model');
|
||||
expect(models[0].baseURL).toBe('https://updated.api');
|
||||
const model = manager.getModel('test');
|
||||
if (!model) {
|
||||
throw new Error('Model should exist');
|
||||
}
|
||||
expect(model.name).toBe('Updated Model');
|
||||
expect(model.baseURL).toBe('https://updated.api');
|
||||
});
|
||||
|
||||
it('should throw error when updating non-existent model', () => {
|
||||
@@ -79,7 +83,7 @@ describe('ModelManager', () => {
|
||||
it('should delete existing model', () => {
|
||||
manager.addModel('test', mockModelConfig);
|
||||
manager.deleteModel('test');
|
||||
expect(manager.getAllModels()).toHaveLength(0);
|
||||
expect(manager.getModel('test')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw error when deleting non-existent model', () => {
|
||||
@@ -88,51 +92,18 @@ describe('ModelManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllModels', () => {
|
||||
it('should return all models with their keys', () => {
|
||||
manager.addModel('test1', mockModelConfig);
|
||||
manager.addModel('test2', { ...mockModelConfig, name: 'Test Model 2' });
|
||||
const models = manager.getAllModels();
|
||||
expect(models).toHaveLength(2);
|
||||
expect(models[0]).toHaveProperty('key');
|
||||
expect(models[1]).toHaveProperty('key');
|
||||
});
|
||||
|
||||
it('should return empty array when no models exist', () => {
|
||||
const models = manager.getAllModels();
|
||||
expect(models).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('storage operations', () => {
|
||||
it('should persist models to localStorage', () => {
|
||||
describe('getModel', () => {
|
||||
it('should return model by key', () => {
|
||||
manager.addModel('test', mockModelConfig);
|
||||
const storedData = localStorage.getItem('models');
|
||||
expect(storedData).toBeDefined();
|
||||
const storedModels = JSON.parse(storedData!);
|
||||
expect(storedModels.test).toMatchObject(mockModelConfig);
|
||||
const model = manager.getModel('test');
|
||||
if (!model) {
|
||||
throw new Error('Model should exist');
|
||||
}
|
||||
expect(model).toMatchObject(mockModelConfig);
|
||||
});
|
||||
|
||||
it('should load models from localStorage', () => {
|
||||
localStorage.setItem('models', JSON.stringify({ test: mockModelConfig }));
|
||||
manager = new ModelManager();
|
||||
// 删除默认模型后再检查
|
||||
const defaultModels = manager.getAllModels();
|
||||
defaultModels.forEach(model => {
|
||||
if (model.key !== 'test') {
|
||||
manager.deleteModel(model.key);
|
||||
}
|
||||
});
|
||||
const models = manager.getAllModels();
|
||||
expect(models).toHaveLength(1);
|
||||
expect(models[0]).toMatchObject({ ...mockModelConfig, key: 'test' });
|
||||
});
|
||||
|
||||
it('should handle localStorage errors gracefully', () => {
|
||||
vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
||||
throw new Error('Storage error');
|
||||
});
|
||||
expect(() => new ModelManager()).not.toThrow();
|
||||
it('should return undefined for non-existent model', () => {
|
||||
expect(manager.getModel('non-existent')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,19 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { PromptService } from '../../../../src/services/prompt/service';
|
||||
import { ModelManager } from '../../../../src/services/model/manager';
|
||||
import { TemplateManager } from '../../../../src/services/template/manager';
|
||||
import { HistoryManager } from '../../../../src/services/history/manager';
|
||||
import { createLLMService } from '../../../../src/services/llm/service';
|
||||
import { ModelConfig } from '../../../../src/services/model/types';
|
||||
import { Template } from '../../../../src/services/template/types';
|
||||
import { PromptRecord } from '../../../../src/services/history/types';
|
||||
import { LLMService } from '../../../../src/services/llm/service';
|
||||
import { OptimizationError, IterationError, TestError } from '../../../../src/services/prompt/errors';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { vi, describe, beforeEach, it, expect } from 'vitest'
|
||||
import {
|
||||
createLLMService,
|
||||
LLMService,
|
||||
ModelManager,
|
||||
PromptService,
|
||||
TemplateManager,
|
||||
HistoryManager,
|
||||
ModelConfig,
|
||||
Template,
|
||||
PromptRecord,
|
||||
OptimizationError,
|
||||
IterationError,
|
||||
TestError
|
||||
} from '@prompt-optimizer/core'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// 模拟 fetch API
|
||||
const mockFetch = vi.fn();
|
||||
@@ -44,7 +48,11 @@ describe('PromptService', () => {
|
||||
id: 'test',
|
||||
name: 'Test Template',
|
||||
content: 'test template content',
|
||||
version: '1.0'
|
||||
metadata: {
|
||||
version: '1.0',
|
||||
lastModified: Date.now(),
|
||||
templateType: 'optimize' as const
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -309,7 +317,11 @@ describe('PromptService 提示词管理器初始化测试', () => {
|
||||
id: 'test',
|
||||
name: 'Test Template',
|
||||
content: 'test template content',
|
||||
version: '1.0'
|
||||
metadata: {
|
||||
version: '1.0',
|
||||
lastModified: Date.now(),
|
||||
templateType: 'optimize' as const
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { TemplateManager } from '../../../../src/services/template/manager';
|
||||
import { DEFAULT_TEMPLATES } from '../../../../src/services/template/defaults';
|
||||
import { Template } from '../../../../src/services/template/types';
|
||||
import { TemplateValidationError } from '../../../../src/services/template/errors';
|
||||
import {
|
||||
TemplateManager,
|
||||
Template,
|
||||
TemplateValidationError,
|
||||
DEFAULT_TEMPLATES
|
||||
} from '@prompt-optimizer/core';
|
||||
|
||||
describe('TemplateManager', () => {
|
||||
let templateManager: TemplateManager;
|
||||
@@ -18,7 +20,8 @@ describe('TemplateManager', () => {
|
||||
author: 'Test User',
|
||||
description: '测试用的提示词',
|
||||
templateType: 'optimize'
|
||||
}
|
||||
},
|
||||
isBuiltin: false
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -181,7 +184,8 @@ describe('TemplateManager', () => {
|
||||
const invalidTemplate = { ...mockUserTemplate, id: '' };
|
||||
|
||||
await expect(() => templateManager.importTemplate(JSON.stringify(invalidTemplate)))
|
||||
.rejects.toThrowError(TemplateValidationError);
|
||||
.rejects
|
||||
.toThrow(/提示词验证失败/);
|
||||
console.log('验证: 正确拒绝无效的导入提示词');
|
||||
});
|
||||
});
|
||||
|
||||
+34
-17
@@ -1,25 +1,42 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 15174,
|
||||
host: true
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: resolve(__dirname, 'index.html')
|
||||
export default defineConfig(({ mode }) => {
|
||||
// 加载环境变量
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
|
||||
return {
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 15174,
|
||||
host: true
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: resolve(__dirname, 'index.html')
|
||||
}
|
||||
}
|
||||
},
|
||||
publicDir: 'public',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src')
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['@prompt-optimizer/core']
|
||||
},
|
||||
define: {
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
...Object.keys(env).reduce((acc, key) => {
|
||||
acc[key] = env[key];
|
||||
return acc;
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
},
|
||||
publicDir: 'public',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,19 +1,35 @@
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import path from 'path';
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
test: {
|
||||
// 全局超时设置为5秒
|
||||
testTimeout: 5000,
|
||||
// 环境设置
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./tests/setup.js'],
|
||||
include: ['tests/**/*.{test,spec}.{js,ts,jsx,tsx}'],
|
||||
globals: true
|
||||
// 包含的文件模式
|
||||
include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
// 排除的文件模式
|
||||
exclude: ['**/node_modules/**', '**/dist/**', '**/.{idea,git,cache,output,temp}/**'],
|
||||
// 全局测试设置
|
||||
globals: true,
|
||||
// 测试覆盖率配置
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html'],
|
||||
exclude: [
|
||||
'coverage/**',
|
||||
'dist/**',
|
||||
'**/[.]**',
|
||||
'packages/*/test?(s)/**',
|
||||
'**/*.d.ts',
|
||||
'**/virtual:*',
|
||||
'**/__x00__*',
|
||||
'**/\x00*',
|
||||
'cypress/**',
|
||||
],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
Generated
+31
-1
@@ -6,9 +6,39 @@ settings:
|
||||
|
||||
importers:
|
||||
|
||||
.: {}
|
||||
.:
|
||||
dependencies:
|
||||
'@langchain/core':
|
||||
specifier: ^0.3.33
|
||||
version: 0.3.39(openai@4.83.0(ws@8.18.0)(zod@3.24.2))
|
||||
'@langchain/google-genai':
|
||||
specifier: ^0.1.6
|
||||
version: 0.1.8(@langchain/core@0.3.39(openai@4.83.0(ws@8.18.0)(zod@3.24.2)))(zod@3.24.2)
|
||||
'@langchain/openai':
|
||||
specifier: ^0.3.17
|
||||
version: 0.3.17(@langchain/core@0.3.39(openai@4.83.0(ws@8.18.0)(zod@3.24.2)))(ws@8.18.0)
|
||||
uuid:
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5
|
||||
devDependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1(vite@6.1.0(@types/node@18.19.75)(jiti@1.21.7)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))
|
||||
|
||||
packages/core:
|
||||
dependencies:
|
||||
'@langchain/core':
|
||||
specifier: ^0.3.33
|
||||
version: 0.3.39(openai@4.83.0(ws@8.18.0)(zod@3.24.2))
|
||||
'@langchain/google-genai':
|
||||
specifier: ^0.1.6
|
||||
version: 0.1.8(@langchain/core@0.3.39(openai@4.83.0(ws@8.18.0)(zod@3.24.2)))(zod@3.24.2)
|
||||
'@langchain/openai':
|
||||
specifier: ^0.3.17
|
||||
version: 0.3.17(@langchain/core@0.3.39(openai@4.83.0(ws@8.18.0)(zod@3.24.2)))(ws@8.18.0)
|
||||
uuid:
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5
|
||||
devDependencies:
|
||||
tsup:
|
||||
specifier: ^8.0.2
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createApp, h } from 'vue';
|
||||
import App from '../../src/App.vue';
|
||||
import { LLMService } from '@prompt-optimizer/core';
|
||||
import { PromptService } from '@prompt-optimizer/core';
|
||||
import { ModelManager } from '@prompt-optimizer/core';
|
||||
import { TemplateManager } from '@prompt-optimizer/core';
|
||||
import { HistoryManager } from '@prompt-optimizer/core';
|
||||
// ... existing code ...
|
||||
@@ -0,0 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { LLMService } from '@prompt-optimizer/core';
|
||||
import { ModelManager } from '@prompt-optimizer/core';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
// ... existing code ...
|
||||
Reference in new issue
Block a user