Team Ai
Apppublic

LinASTC/DocsAnthropic2API

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
index.js363 linesDownload Raw Back to root
1import express from 'express';
2import WebSocket from 'ws';
3import { v4 as uuidv4 } from 'uuid';
4import { randomBytes } from 'crypto';
5import cors from 'cors';
6import dotenv from 'dotenv';
7
8// 配置加载
9dotenv.config();
10
11// 配置常量
12const CONFIG = {
13  API: {
14      BASE_URL: "wss://api.inkeep.com/graphql",
15      API_KEY: process.env.API_KEY || "sk-123456",
16  },
17  MODELS: {
18      'claude-3-5-sonnet-20241022': 'claude-3-5-sonnet-20241022',
19  },
20  SERVER: {
21      PORT: process.env.PORT || 3000,
22      BODY_LIMIT: '5mb'
23  },
24  DEFAULT_HEADERS: {
25      'Host': 'api.inkeep.com',
26      'Connection': 'Upgrade',
27      'Pragma': 'no-cache',
28      'Cache-Control': 'no-cache',
29      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
30      'Upgrade': 'websocket',
31      'Origin': 'https://docs.anthropic.com',
32      'Sec-WebSocket-Version': '13',
33      'Accept-Encoding': 'gzip, deflate, br, zstd',
34      'Accept-Language': 'zh-CN,zh;q=0.9',
35      'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
36      'Sec-WebSocket-Protocol': 'graphql-transport-ws'
37  }
38};
39
40// AI API 客户端类
41class AiApiClient {
42  constructor(modelId) {
43      this.modelId = CONFIG.MODELS[modelId];
44      if (!this.modelId) {
45          throw new Error(`不支持的模型: ${modelId}`);
46      }
47  }
48
49  // 处理消息内容
50  processMessageContent(content) {
51      if (typeof content === 'string') return content;
52      if (Array.isArray(content)) {
53          return content
54              .filter(item => item.type === 'text')
55              .map(item => item.text)
56              .join('\n');
57      }
58      return typeof content === 'object' ? content.text || null : null;
59  }
60
61  // 转换消息格式
62  async transformMessages(request) {
63      let systemMessageList = [];
64      let systemMergeMode = false;
65      let closedSystemMergeMode = false;
66
67      const contextMessages = await request.messages.reduce(async (accPromise, current) => {
68          const acc = await accPromise;
69          const currentContent = this.processMessageContent(current.content);
70          
71          if (currentContent === null) return acc;
72
73          const currentMessageRole = current.role === "system" || current.role === "user" ? "HUMAN" : "ASSISTANT";
74
75          // 系统消息处理逻辑
76          if (current.role === "system") {
77              if (!closedSystemMergeMode) {
78                  systemMergeMode = true;
79                  const lastSystemMessage = systemMessageList[systemMessageList.length - 1];
80
81                  if (!lastSystemMessage) {
82                      systemMessageList.push(currentContent);
83                  } else {
84                      systemMessageList[systemMessageList.length - 1] = `${lastSystemMessage}\n${currentContent}`;
85                  }
86                  return acc;
87              }
88          }
89
90          // 关闭系统消息合并模式
91          if (current.role !== "system" && systemMergeMode) {
92              systemMergeMode = false;
93              closedSystemMergeMode = true;
94          }
95
96          // 消息合并逻辑
97          const previousMessage = acc[acc.length - 1];
98          const newMessage = `${currentMessageRole}: ${currentContent}`;
99
100          if (!previousMessage || previousMessage.startsWith(currentMessageRole)) {
101              return previousMessage
102                  ? [...acc.slice(0, -1), `${previousMessage}\n${currentContent}`]
103                  : [...acc, newMessage];
104          }
105
106          return [...acc, newMessage];
107      }, Promise.resolve([]));
108
109      return {
110          contextMessages: contextMessages.join('\n'),
111          systemMessage: systemMessageList.join('\n')
112      };
113  }
114}
115
116// 响应处理类
117class ResponseHandler {
118  // 流式响应处理
119  static async handleStreamResponse(responseContent, model, res) {
120      res.setHeader('Content-Type', 'text/event-stream');
121      res.setHeader('Cache-Control', 'no-cache');
122      res.setHeader('Connection', 'keep-alive');
123      
124      let index = 0;
125      while (index < responseContent.length) {
126          const chunkSize = Math.floor(Math.random() * (30 - 16)) + 15;
127          const chunk = responseContent.slice(index, index + chunkSize);
128          
129          res.write(`data: ${JSON.stringify({
130              id: uuidv4(),
131              object: 'chat.completion.chunk',
132              created: Math.floor(Date.now() / 1000),
133              model: model,
134              choices: [{
135                  index: 0,
136                  delta: { content: chunk },
137                  finish_reason: null
138              }]
139          })}\n\n`);
140          
141          index += chunkSize;
142          await new Promise(resolve => setTimeout(resolve, 50));
143      }
144      
145      res.write('data: [DONE]\n\n');
146      res.end();
147  }
148
149  // 普通响应处理
150  static async handleNormalResponse(userMessage, responseContent, model, res) {
151      res.json({
152          id: uuidv4(),
153          object: "chat.completion",
154          created: Math.floor(Date.now() / 1000),
155          model: model,
156          choices: [{
157              index: 0,
158              message: {
159                  role: "assistant",
160                  content: responseContent
161              },
162              finish_reason: "stop"
163          }],
164          usage: {
165              prompt_tokens: userMessage.length,
166              completion_tokens: responseContent.length,
167              total_tokens: userMessage.length + responseContent.length
168          }
169      });
170  }
171}
172
173// WebSocket工具类
174class WebSocketUtils {
175  // 生成WebSocket密钥
176  static generateWebSocketKey() {
177      return randomBytes(16).toString('base64');
178  }
179
180  // 创建WebSocket客户端
181  static createWebSocketClient(requestPayload) {
182      return new Promise((resolve, reject) => {
183          const websocketKey = this.generateWebSocketKey();
184          const ws = new WebSocket(CONFIG.API.BASE_URL, 'graphql-transport-ws', {
185              headers: {
186                  ...CONFIG.DEFAULT_HEADERS,
187                  'Sec-WebSocket-Key': websocketKey,
188              }
189          });
190
191          let responseContent = '';
192          let isComplete = false;
193
194          ws.on('open', () => {
195              console.log('WebSocket连接已建立');
196              const connectionInitMessage = {
197                  type: 'connection_init',
198                  payload: {
199                      headers: {
200                          Authorization: 'Bearer ee5b7c15ed3553cd6abc407340aad09ac7cb3b9f76d8613a'
201                      }
202                  }
203              };
204              ws.send(JSON.stringify(connectionInitMessage));
205          });
206
207          ws.on('message', async (data) => {
208              const message = data.toString();
209              const parsedMessage = JSON.parse(message);
210              
211              switch (parsedMessage.type) {
212                  case 'connection_ack':
213                      console.log('WebSocket连接请求中');
214                      this.sendChatSubscription(ws, requestPayload);
215                      break;
216                  case 'next':
217                      const chatResponse = await this.handleChatResponse(parsedMessage);
218                      if (chatResponse) {
219                        responseContent = chatResponse;
220                        // 获取到响应后立即关闭连接
221                        isComplete = true;
222                        ws.close();
223                        resolve(responseContent);
224                    }
225                      break;
226                  case 'complete':
227                      isComplete = true;
228                      ws.close();
229                      resolve(responseContent);
230                      break;
231              }
232          });
233
234          ws.on('error', (err) => {
235              console.error('WebSocket错误:', err);
236              reject(err);
237          });
238
239          ws.on('close', (code, reason) => {
240              console.log('请求完毕,关闭连接');
241              if (!isComplete) {
242                  reject(new Error('WebSocket closed unexpectedly'));
243              }
244          });
245      });
246  }
247
248  // 发送聊天订阅
249  static sendChatSubscription(ws, requestPayload) {
250      const subscribeMessage = {
251          id: uuidv4(),
252          type: 'subscribe',
253          payload: {
254              variables: {
255                  messageInput: requestPayload.contextMessages,
256                  messageContext: null,
257                  organizationId: 'org_JfjtEvzbwOikUEUn',
258                  integrationId: 'clwtqz9sq001izszu8ms5g4om',
259                  chatMode: 'AUTO',
260                  context: requestPayload.systemMessage,
261                  messageAttributes: {},
262                  includeAIAnnotations: false,
263                  environment: 'production'
264              },
265              extensions: {},
266              operationName: 'OnNewSessionChatResult',
267              query: `subscription OnNewSessionChatResult($messageInput: String!, $messageContext: String, $organizationId: ID!, $integrationId: ID, $chatMode: ChatMode, $filters: ChatFiltersInput, $messageAttributes: JSON, $tags: [String!], $workflowId: String, $context: String, $guidance: String, $includeAIAnnotations: Boolean!, $environment: String) {
268                  newSessionChatResult(
269                      input: {messageInput: $messageInput, messageContext: $messageContext, organizationId: $organizationId, integrationId: $integrationId, chatMode: $chatMode, filters: $filters, messageAttributes: $messageAttributes, tags: $tags, workflowId: $workflowId, context: $context, guidance: $guidance, environment: $environment}
270                  ) {
271                      isEnd
272                      sessionId
273                      message {
274                          id
275                          content
276                      }
277                      __typename
278                  }
279              }`
280          }
281      };
282
283      ws.send(JSON.stringify(subscribeMessage));
284  }
285
286  // 处理聊天响应
287  static async handleChatResponse(message) {
288      if (message.payload && message.payload.data) {
289          const chatResult = message.payload.data.newSessionChatResult;
290          if (chatResult && chatResult.isEnd == true && chatResult.message) {
291              return chatResult.message.content;
292          }
293      }
294      return null;
295  }
296}
297
298// 创建Express应用
299const app = express();
300
301// 中间件配置
302app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
303app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
304app.use(cors({
305  origin: '*',
306  methods: ['GET', 'POST', 'OPTIONS'],
307  allowedHeaders: ['Content-Type', 'Authorization']
308}));
309
310// 获取模型列表路由
311app.get('/hf/v1/models', (req, res) => {
312  res.json({
313      object: "list",
314      data: [{
315          id: "claude-3-5-sonnet-20241022",
316          object: "model",
317          created: Math.floor(Date.now() / 1000),
318          owned_by: "claude",
319      }]
320  });
321});
322
323// 聊天完成路由
324app.post('/hf/v1/chat/completions', async (req, res) => {
325  try {
326      const { messages, model, stream } = req.body;
327      const authToken = req.headers.authorization?.replace('Bearer ', '');
328      
329      if (authToken !== CONFIG.API.API_KEY) {
330          return res.status(401).json({ error: "Unauthorized" });
331      }
332
333      const apiClient = new AiApiClient(req.body.model);
334      const requestPayload = await apiClient.transformMessages(req.body);
335
336      const userMessage = messages.reverse().find(message => message.role === 'user')?.content;
337      if (!userMessage) {
338          return res.status(400).json({ error: "缺失用户消息" });
339      }
340
341      const responseContent = await WebSocketUtils.createWebSocketClient(requestPayload);
342
343      if (stream) {
344          await ResponseHandler.handleStreamResponse(responseContent, model, res);
345      } else {
346          await ResponseHandler.handleNormalResponse(userMessage, responseContent, model, res);
347      }
348  } catch (error) {
349      console.error('处理请求时发生错误:', error);
350      res.status(500).json({ error: "内部服务器错误", details: error.message });
351  }
352});
353
354// 404处理
355app.use((req, res) => {
356  res.status(404).json({ message: "服务创建成功运行中,请根据规则使用正确请求路径" });
357});
358
359// 启动服务器
360app.listen(CONFIG.SERVER.PORT, () => {
361  console.log(`服务器运行在 http://localhost:${CONFIG.SERVER.PORT}`);
362});
363