yxmiler commited on
Commit
6bdac06
·
verified ·
1 Parent(s): c37b9fb

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +13 -0
  2. index.js +358 -0
  3. package.json +16 -0
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:lts-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package*.json ./
6
+
7
+ RUN npm install
8
+
9
+ COPY . .
10
+
11
+ EXPOSE 7860
12
+
13
+ CMD ["npm", "start"]
index.js ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import WebSocket from 'ws';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { randomBytes } from 'crypto';
5
+ import cors from 'cors';
6
+ import dotenv from 'dotenv';
7
+
8
+ // 配置加载
9
+ dotenv.config();
10
+
11
+ // 配置常量
12
+ const 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 客户端类
41
+ class 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" ? "USER" : current.role.toUpperCase();
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
+ // 响应处理类
117
+ class 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工具类
174
+ class 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
+ break;
222
+ case 'complete':
223
+ isComplete = true;
224
+ ws.close();
225
+ resolve(responseContent);
226
+ break;
227
+ }
228
+ });
229
+
230
+ ws.on('error', (err) => {
231
+ console.error('WebSocket错误:', err);
232
+ reject(err);
233
+ });
234
+
235
+ ws.on('close', (code, reason) => {
236
+ console.log('请求完毕,关闭连接');
237
+ if (!isComplete) {
238
+ reject(new Error('WebSocket closed unexpectedly'));
239
+ }
240
+ });
241
+ });
242
+ }
243
+
244
+ // 发送聊天订阅
245
+ static sendChatSubscription(ws, requestPayload) {
246
+ const subscribeMessage = {
247
+ id: uuidv4(),
248
+ type: 'subscribe',
249
+ payload: {
250
+ variables: {
251
+ messageInput: requestPayload.contextMessages,
252
+ messageContext: null,
253
+ organizationId: 'org_JfjtEvzbwOikUEUn',
254
+ integrationId: 'clwtqz9sq001izszu8ms5g4om',
255
+ chatMode: 'AUTO',
256
+ context: requestPayload.systemMessage,
257
+ messageAttributes: {},
258
+ includeAIAnnotations: false,
259
+ environment: 'production'
260
+ },
261
+ extensions: {},
262
+ operationName: 'OnNewSessionChatResult',
263
+ 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) {
264
+ newSessionChatResult(
265
+ 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}
266
+ ) {
267
+ isEnd
268
+ sessionId
269
+ message {
270
+ id
271
+ content
272
+ }
273
+ __typename
274
+ }
275
+ }`
276
+ }
277
+ };
278
+
279
+ ws.send(JSON.stringify(subscribeMessage));
280
+ }
281
+
282
+ // 处理聊天响应
283
+ static async handleChatResponse(message) {
284
+ if (message.payload && message.payload.data) {
285
+ const chatResult = message.payload.data.newSessionChatResult;
286
+ if (chatResult && chatResult.isEnd == true && chatResult.message) {
287
+ return chatResult.message.content;
288
+ }
289
+ }
290
+ return null;
291
+ }
292
+ }
293
+
294
+ // 创建Express应用
295
+ const app = express();
296
+
297
+ // 中间件配置
298
+ app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
299
+ app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
300
+ app.use(cors({
301
+ origin: '*',
302
+ methods: ['GET', 'POST', 'OPTIONS'],
303
+ allowedHeaders: ['Content-Type', 'Authorization']
304
+ }));
305
+
306
+ // 获取模型列表路由
307
+ app.get('/hf/v1/models', (req, res) => {
308
+ res.json({
309
+ object: "list",
310
+ data: [{
311
+ id: "claude-3-5-sonnet-20241022",
312
+ object: "model",
313
+ created: Math.floor(Date.now() / 1000),
314
+ owned_by: "claude",
315
+ }]
316
+ });
317
+ });
318
+
319
+ // 聊天完成路由
320
+ app.post('/hf/v1/chat/completions', async (req, res) => {
321
+ try {
322
+ const { messages, model, stream } = req.body;
323
+ const authToken = req.headers.authorization?.replace('Bearer ', '');
324
+
325
+ if (authToken !== CONFIG.API.API_KEY) {
326
+ return res.status(401).json({ error: "Unauthorized" });
327
+ }
328
+
329
+ const apiClient = new AiApiClient(req.body.model);
330
+ const requestPayload = await apiClient.transformMessages(req.body);
331
+
332
+ const userMessage = messages.reverse().find(message => message.role === 'user')?.content;
333
+ if (!userMessage) {
334
+ return res.status(400).json({ error: "缺失用户消息" });
335
+ }
336
+
337
+ const responseContent = await WebSocketUtils.createWebSocketClient(requestPayload);
338
+
339
+ if (stream) {
340
+ await ResponseHandler.handleStreamResponse(responseContent, model, res);
341
+ } else {
342
+ await ResponseHandler.handleNormalResponse(userMessage, responseContent, model, res);
343
+ }
344
+ } catch (error) {
345
+ console.error('处理请求时发生错误:', error);
346
+ res.status(500).json({ error: "内部服务器错误", details: error.message });
347
+ }
348
+ });
349
+
350
+ // 404处理
351
+ app.use((req, res) => {
352
+ res.status(404).json({ message: "请使用正确请求路径" });
353
+ });
354
+
355
+ // 启动服务器
356
+ app.listen(CONFIG.SERVER.PORT, () => {
357
+ console.log(`服务器运行在 http://localhost:${CONFIG.SERVER.PORT}`);
358
+ });
package.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "claudeServicee",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "author": "yxmiler",
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "dotenv": "^16.3.1",
13
+ "cors": "^2.8.5",
14
+ "uuid": "^9.0.0"
15
+ }
16
+ }