mockOpenAI.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. // services/modelAdapters/mockOpenAI.ts
  2. import { BaseModelAdapter } from "./base";
  3. import type {
  4. OpenAIMessage,
  5. SendOptions,
  6. ParsedChunk,
  7. ToolCall,
  8. } from "../../types/chat";
  9. import type { IAiModel } from "../../api/ai";
  10. export class MockOpenAIAdapter extends BaseModelAdapter {
  11. model: IAiModel | undefined;
  12. name = "mock-gpt-4";
  13. supportsFunctionCall = true;
  14. protected mockDelay = (ms: number) =>
  15. new Promise((resolve) => setTimeout(resolve, ms));
  16. async sendMessage(
  17. messages: OpenAIMessage[],
  18. options: SendOptions
  19. ): Promise<AsyncIterable<string>> {
  20. const payload = this.buildRequestPayload(messages, options);
  21. console.log("[Mock OpenAI] Sending message with payload:", payload);
  22. return this.createMockStreamIterable(messages, options);
  23. }
  24. private async *createMockStreamIterable(
  25. messages: OpenAIMessage[],
  26. _options: SendOptions
  27. ): AsyncIterable<string> {
  28. // 模拟初始延迟
  29. await this.mockDelay(100);
  30. const lastMessage = messages[messages.length - 1];
  31. const userContent = lastMessage?.content || "";
  32. // 检查是否需要函数调用
  33. const needsFunctionCall = this.shouldCallFunction(userContent);
  34. if (needsFunctionCall) {
  35. // 模拟函数调用流程
  36. yield* this.generateFunctionCallResponse(userContent);
  37. } else {
  38. // 模拟普通文本响应
  39. yield* this.generateTextResponse(userContent);
  40. }
  41. }
  42. private shouldCallFunction(content: string): boolean {
  43. const functionTriggers = [
  44. "天气",
  45. "weather",
  46. "温度",
  47. "气温",
  48. "搜索",
  49. "search",
  50. "查找",
  51. "find",
  52. "翻译",
  53. "translate",
  54. "巴利",
  55. "pali",
  56. ];
  57. return functionTriggers.some((trigger) =>
  58. content.toLowerCase().includes(trigger.toLowerCase())
  59. );
  60. }
  61. private async *generateFunctionCallResponse(
  62. userContent: string
  63. ): AsyncIterable<string> {
  64. // 第一步:生成函数调用意图的响应
  65. const functionCallChunk = {
  66. choices: [
  67. {
  68. delta: {
  69. content: null,
  70. function_call: this.determineFunctionCall(userContent),
  71. },
  72. finish_reason: "function_call",
  73. },
  74. ],
  75. };
  76. yield JSON.stringify(functionCallChunk);
  77. await this.mockDelay(200);
  78. // 模拟函数执行后的最终响应
  79. const finalResponseText = this.generateFunctionBasedResponse(userContent);
  80. yield* this.streamText(finalResponseText);
  81. }
  82. private determineFunctionCall(content: string): any {
  83. if (content.includes("天气") || content.toLowerCase().includes("weather")) {
  84. const city = this.extractCityFromContent(content);
  85. return {
  86. name: "getWeather",
  87. arguments: JSON.stringify({ city }),
  88. };
  89. } else if (
  90. content.includes("搜索") ||
  91. content.toLowerCase().includes("search")
  92. ) {
  93. const term = this.extractSearchTermFromContent(content);
  94. return {
  95. name: "searchTerm",
  96. arguments: JSON.stringify({ term }),
  97. };
  98. }
  99. return {
  100. name: "getWeather",
  101. arguments: JSON.stringify({ city: "北京" }),
  102. };
  103. }
  104. private extractCityFromContent(content: string): string {
  105. const cities = [
  106. "北京",
  107. "上海",
  108. "广州",
  109. "深圳",
  110. "杭州",
  111. "成都",
  112. "西安",
  113. "武汉",
  114. "New York",
  115. "London",
  116. "Tokyo",
  117. "Paris",
  118. "Sydney",
  119. ];
  120. for (const city of cities) {
  121. if (content.includes(city)) {
  122. return city;
  123. }
  124. }
  125. return "北京"; // 默认城市
  126. }
  127. private extractSearchTermFromContent(content: string): string {
  128. // 简单提取逻辑,实际应用中可能需要更复杂的NLP处理
  129. const matches = content.match(/搜索["']?([^"']+)["']?/);
  130. if (matches) {
  131. return matches[1];
  132. }
  133. const searchMatches = content.match(/search\s+["']?([^"']+)["']?/i);
  134. if (searchMatches) {
  135. return searchMatches[1];
  136. }
  137. return "佛法"; // 默认搜索词
  138. }
  139. private generateFunctionBasedResponse(userContent: string): string {
  140. if (
  141. userContent.includes("天气") ||
  142. userContent.toLowerCase().includes("weather")
  143. ) {
  144. const city = this.extractCityFromContent(userContent);
  145. return `根据天气查询结果,${city}今天的天气情况如下:\n\n🌤️ **天气状况**:晴朗\n🌡️ **温度**:25°C\n💧 **湿度**:60%\n\n今天是个出行的好日子!记得适当补充水分。`;
  146. } else if (
  147. userContent.includes("搜索") ||
  148. userContent.toLowerCase().includes("search")
  149. ) {
  150. const term = this.extractSearchTermFromContent(userContent);
  151. return `我已经为您搜索了"${term}"相关的内容。以下是搜索结果:\n\n📚 **搜索结果**:\n• 找到了 15 个相关条目\n• 包含词汇解释、语法分析等\n• 提供了详细的语言学资料\n\n如需查看具体内容,请告诉我您感兴趣的具体方面。`;
  152. }
  153. return "我理解您的请求,已经为您处理完成。如有其他需要帮助的地方,请随时告诉我。";
  154. }
  155. private async *generateTextResponse(
  156. userContent: string
  157. ): AsyncIterable<string> {
  158. const responses = [
  159. "我很高兴为您提供帮助!",
  160. "这是一个很有趣的问题。",
  161. "让我为您详细解答一下。",
  162. "根据我的理解,我认为...",
  163. "这个问题涉及到多个方面,让我逐一为您分析。",
  164. "希望这个回答对您有所帮助。",
  165. ];
  166. // 根据用户输入选择合适的响应
  167. let responseText = "";
  168. if (userContent.length > 50) {
  169. responseText = `您提出了一个详细的问题。${
  170. responses[Math.floor(Math.random() * responses.length)]
  171. }
  172. 针对您的问题,我可以从以下几个角度来回答:
  173. 1. **主要观点**:${userContent.substring(0, 20)}...这个话题确实值得深入讨论。
  174. 2. **相关背景**:这类问题通常需要综合考虑多个因素。
  175. 3. **建议方案**:我建议您可以从实际情况出发,结合具体需求来选择最合适的方法。
  176. 如果您需要更详细的解释或有其他相关问题,请随时告诉我!`;
  177. } else {
  178. responseText = `${responses[Math.floor(Math.random() * responses.length)]}
  179. 关于"${userContent}"这个问题,我的理解是这样的:
  180. 这确实是一个值得探讨的话题。根据我的知识,我认为最重要的是要考虑实际的应用场景和具体需求。
  181. 希望我的回答对您有帮助。如果您还有任何疑问,欢迎继续提问!`;
  182. }
  183. yield* this.streamText(responseText);
  184. }
  185. private async *streamText(text: string): AsyncIterable<string> {
  186. const words = text.split("");
  187. for (let i = 0; i < words.length; i++) {
  188. const chunk = {
  189. choices: [
  190. {
  191. delta: {
  192. content: words[i],
  193. },
  194. finish_reason: i === words.length - 1 ? "stop" : null,
  195. },
  196. ],
  197. };
  198. yield JSON.stringify(chunk);
  199. // 模拟打字机效果,随机延迟
  200. const delay = Math.random() * 50 + 10; // 10-60ms 随机延迟
  201. await this.mockDelay(delay);
  202. }
  203. // 发送结束标记
  204. const finishChunk = {
  205. choices: [
  206. {
  207. delta: {},
  208. finish_reason: "stop",
  209. },
  210. ],
  211. };
  212. yield JSON.stringify(finishChunk);
  213. }
  214. parseStreamChunk(chunk: string): ParsedChunk | null {
  215. try {
  216. const parsed = JSON.parse(chunk);
  217. const delta = parsed.choices?.[0]?.delta;
  218. const finishReason = parsed.choices?.[0]?.finish_reason;
  219. return {
  220. content: delta?.content,
  221. tool_calls: delta?.function_call,
  222. finish_reason: finishReason,
  223. };
  224. } catch (error) {
  225. console.warn("[Mock OpenAI] Failed to parse chunk:", chunk, error);
  226. return null;
  227. }
  228. }
  229. async handleFunctionCall(functionCall: ToolCall): Promise<any> {
  230. console.log("[Mock OpenAI] Handling function call:", functionCall);
  231. // 模拟函数执行延迟
  232. await this.mockDelay(300);
  233. switch (functionCall.function.name) {
  234. case "searchTerm":
  235. return await this.mockSearchTerm(functionCall.function.arguments);
  236. case "getWeather":
  237. return await this.mockGetWeather(functionCall.function.arguments);
  238. default:
  239. console.warn(
  240. `[Mock OpenAI] Unknown function: ${functionCall.function.name}`
  241. );
  242. throw new Error(`未知函数: ${functionCall.function.name}`);
  243. }
  244. }
  245. private async mockSearchTerm(term: string): Promise<any> {
  246. console.log(`[Mock OpenAI] Searching for term: ${term}`);
  247. // 模拟搜索延迟
  248. await this.mockDelay(500);
  249. // 生成模拟的搜索结果
  250. const mockResults = [
  251. {
  252. id: 1,
  253. word: term,
  254. definition: `${term}的定义:这是一个重要的概念`,
  255. grammar: "名词",
  256. example: `使用${term}的例句示例`,
  257. },
  258. {
  259. id: 2,
  260. word: `${term}相关词`,
  261. definition: `与${term}相关的另一个概念`,
  262. grammar: "动词",
  263. example: `相关用法示例`,
  264. },
  265. {
  266. id: 3,
  267. word: `${term}变体`,
  268. definition: `${term}的变体形式`,
  269. grammar: "形容词",
  270. example: `变体使用示例`,
  271. },
  272. ];
  273. return {
  274. ok: true,
  275. data: {
  276. rows: mockResults,
  277. total: mockResults.length,
  278. },
  279. };
  280. }
  281. private async mockGetWeather(city: string): Promise<any> {
  282. console.log(`[Mock OpenAI] Getting weather for city: ${city}`);
  283. // 模拟天气API延迟
  284. await this.mockDelay(300);
  285. // 生成随机天气数据
  286. const conditions = ["晴朗", "多云", "小雨", "阴天", "晴转多云"];
  287. const temperatures = ["22°C", "25°C", "18°C", "28°C", "20°C"];
  288. const humidities = ["45%", "60%", "75%", "55%", "65%"];
  289. return {
  290. city,
  291. temperature:
  292. temperatures[Math.floor(Math.random() * temperatures.length)],
  293. condition: conditions[Math.floor(Math.random() * conditions.length)],
  294. humidity: humidities[Math.floor(Math.random() * humidities.length)],
  295. timestamp: new Date().toISOString(),
  296. source: "Mock Weather API",
  297. };
  298. }
  299. // 工具方法:构建请求负载(继承自BaseModelAdapter)
  300. protected buildRequestPayload(
  301. messages: OpenAIMessage[],
  302. options: SendOptions
  303. ) {
  304. return {
  305. model: this.name, // 添加required的model字段
  306. messages: messages, // 直接使用原始messages,不需要重新映射
  307. stream: true,
  308. temperature: options.temperature || 0.7,
  309. max_tokens: options.max_tokens || 2000,
  310. top_p: options.top_p || 1,
  311. tools: options.tools ?? [],
  312. tool_choice: "auto", // 确保不为undefined
  313. };
  314. }
  315. }
  316. // 导出工厂函数,便于在不同环境中使用
  317. export function createMockOpenAIAdapter(): MockOpenAIAdapter {
  318. return new MockOpenAIAdapter();
  319. }
  320. // 导出配置选项
  321. export interface MockOpenAIOptions {
  322. responseDelay?: number; // 响应延迟
  323. streamDelay?: number; // 流式输出延迟
  324. enableFunctionCall?: boolean; // 是否启用函数调用
  325. mockErrorRate?: number; // 模拟错误率 (0-1)
  326. }
  327. // 可配置的Mock适配器
  328. export class ConfigurableMockOpenAIAdapter extends MockOpenAIAdapter {
  329. constructor(private options: MockOpenAIOptions = {}) {
  330. super();
  331. this.name = "configurable-mock-gpt-4";
  332. }
  333. private get responseDelay() {
  334. return this.options.responseDelay || 100;
  335. }
  336. private get ___streamDelay() {
  337. return this.options.streamDelay || 30;
  338. }
  339. protected mockDelay = (ms?: number) =>
  340. new Promise((resolve) => setTimeout(resolve, ms || this.responseDelay));
  341. async handleFunctionCall(functionCall: ToolCall): Promise<any> {
  342. if (!this.options.enableFunctionCall) {
  343. throw new Error("Function call disabled in mock adapter");
  344. }
  345. // 模拟随机错误
  346. if (
  347. this.options.mockErrorRate &&
  348. Math.random() < this.options.mockErrorRate
  349. ) {
  350. throw new Error("Mock function call error");
  351. }
  352. return super.handleFunctionCall(functionCall);
  353. }
  354. }