Manus AI commited on
Commit
9733766
·
1 Parent(s): d22687e

Fix: Simplify invokeLLM to match the successful example exactly

Browse files
Files changed (1) hide show
  1. server/_core/llm.ts +18 -307
server/_core/llm.ts CHANGED
@@ -2,343 +2,54 @@ import { ENV } from "./env";
2
 
3
  export type Role = "system" | "user" | "assistant" | "tool" | "function";
4
 
5
- export type TextContent = {
6
- type: "text";
7
- text: string;
8
- };
9
-
10
- export type ImageContent = {
11
- type: "image_url";
12
- image_url: {
13
- url: string;
14
- detail?: "auto" | "low" | "high";
15
- };
16
- };
17
-
18
- export type FileContent = {
19
- type: "file_url";
20
- file_url: {
21
- url: string;
22
- mime_type?: "audio/mpeg" | "audio/wav" | "application/pdf" | "audio/mp4" | "video/mp4" ;
23
- };
24
- };
25
-
26
- export type MessageContent = string | TextContent | ImageContent | FileContent;
27
-
28
  export type Message = {
29
  role: Role;
30
- content: MessageContent | MessageContent[];
31
- name?: string;
32
- tool_call_id?: string;
33
- };
34
-
35
- export type Tool = {
36
- type: "function";
37
- function: {
38
- name: string;
39
- description?: string;
40
- parameters?: Record<string, unknown>;
41
- };
42
- };
43
-
44
- export type ToolChoicePrimitive = "none" | "auto" | "required";
45
- export type ToolChoiceByName = { name: string };
46
- export type ToolChoiceExplicit = {
47
- type: "function";
48
- function: {
49
- name: string;
50
- };
51
  };
52
 
53
- export type ToolChoice =
54
- | ToolChoicePrimitive
55
- | ToolChoiceByName
56
- | ToolChoiceExplicit;
57
-
58
  export type InvokeParams = {
59
  messages: Message[];
60
  model?: string;
61
- tools?: Tool[];
62
- toolChoice?: ToolChoice;
63
- tool_choice?: ToolChoice;
64
- maxTokens?: number;
65
- max_tokens?: number;
66
- outputSchema?: OutputSchema;
67
- output_schema?: OutputSchema;
68
- responseFormat?: ResponseFormat;
69
- response_format?: ResponseFormat;
70
- };
71
-
72
- export type ToolCall = {
73
- id: string;
74
- type: "function";
75
- function: {
76
- name: string;
77
- arguments: string;
78
- };
79
  };
80
 
81
  export type InvokeResult = {
82
- id: string;
83
- created: number;
84
- model: string;
85
  choices: Array<{
86
- index: number;
87
  message: {
88
  role: Role;
89
- content: string | Array<TextContent | ImageContent | FileContent>;
90
- tool_calls?: ToolCall[];
91
  };
92
- finish_reason: string | null;
93
  }>;
94
- usage?: {
95
- prompt_tokens: number;
96
- completion_tokens: number;
97
- total_tokens: number;
98
- };
99
- };
100
-
101
- export type JsonSchema = {
102
- name: string;
103
- schema: Record<string, unknown>;
104
- strict?: boolean;
105
- };
106
-
107
- export type OutputSchema = JsonSchema;
108
-
109
- export type ResponseFormat =
110
- | { type: "text" }
111
- | { type: "json_object" }
112
- | { type: "json_schema"; json_schema: JsonSchema };
113
-
114
- const ensureArray = (
115
- value: MessageContent | MessageContent[]
116
- ): MessageContent[] => (Array.isArray(value) ? value : [value]);
117
-
118
- const normalizeContentPart = (
119
- part: MessageContent
120
- ): TextContent | ImageContent | FileContent => {
121
- if (typeof part === "string") {
122
- return { type: "text", text: part };
123
- }
124
-
125
- if (part.type === "text") {
126
- return part;
127
- }
128
-
129
- if (part.type === "image_url") {
130
- return part;
131
- }
132
-
133
- if (part.type === "file_url") {
134
- return part;
135
- }
136
-
137
- throw new Error("Unsupported message content part");
138
- };
139
-
140
- const normalizeMessage = (message: Message) => {
141
- const { role, name, tool_call_id } = message;
142
-
143
- if (role === "tool" || role === "function") {
144
- const content = ensureArray(message.content)
145
- .map(part => (typeof part === "string" ? part : JSON.stringify(part)))
146
- .join("\n");
147
-
148
- return {
149
- role,
150
- name,
151
- tool_call_id,
152
- content,
153
- };
154
- }
155
-
156
- const contentParts = ensureArray(message.content).map(normalizeContentPart);
157
-
158
- // If there's only text content, collapse to a single string for compatibility
159
- if (contentParts.length === 1 && contentParts[0].type === "text") {
160
- return {
161
- role,
162
- name,
163
- content: contentParts[0].text,
164
- };
165
- }
166
-
167
- return {
168
- role,
169
- name,
170
- content: contentParts,
171
- };
172
- };
173
-
174
- const normalizeToolChoice = (
175
- toolChoice: ToolChoice | undefined,
176
- tools: Tool[] | undefined
177
- ): "none" | "auto" | ToolChoiceExplicit | undefined => {
178
- if (!toolChoice) return undefined;
179
-
180
- if (toolChoice === "none" || toolChoice === "auto") {
181
- return toolChoice;
182
- }
183
-
184
- if (toolChoice === "required") {
185
- if (!tools || tools.length === 0) {
186
- throw new Error(
187
- "tool_choice 'required' was provided but no tools were configured"
188
- );
189
- }
190
-
191
- if (tools.length > 1) {
192
- throw new Error(
193
- "tool_choice 'required' needs a single tool or specify the tool name explicitly"
194
- );
195
- }
196
-
197
- return {
198
- type: "function",
199
- function: { name: tools[0].function.name },
200
- };
201
- }
202
-
203
- if ("name" in toolChoice) {
204
- return {
205
- type: "function",
206
- function: { name: toolChoice.name },
207
- };
208
- }
209
-
210
- return toolChoice;
211
- };
212
-
213
- const resolveApiUrl = (model: string) => {
214
- // Prioritize BUILT_IN_FORGE_API_URL environment variable
215
- if (ENV.forgeApiUrl && ENV.forgeApiUrl.trim().length > 0) {
216
- return `${ENV.forgeApiUrl.replace(/\/$/, "")}/v1/chat/completions`;
217
- }
218
-
219
- // Use direct Inference API for the specific model to ensure stability
220
- return `https://api-inference.huggingface.co/models/${model}/v1/chat/completions`;
221
- };
222
-
223
- const assertApiKey = () => {
224
- const apiKey = ENV.forgeApiKey || process.env.HF_TOKEN || process.env.HF_ACCESS_TOKEN;
225
- if (!apiKey) {
226
- throw new Error("No API Key found. Please configure HF_TOKEN in Space Secrets.");
227
- }
228
- return apiKey;
229
- };
230
-
231
- const normalizeResponseFormat = ({
232
- responseFormat,
233
- response_format,
234
- outputSchema,
235
- output_schema,
236
- }: {
237
- responseFormat?: ResponseFormat;
238
- response_format?: ResponseFormat;
239
- outputSchema?: OutputSchema;
240
- output_schema?: OutputSchema;
241
- }):
242
- | { type: "json_schema"; json_schema: JsonSchema }
243
- | { type: "text" }
244
- | { type: "json_object" }
245
- | undefined => {
246
- const explicitFormat = responseFormat || response_format;
247
- if (explicitFormat) {
248
- if (
249
- explicitFormat.type === "json_schema" &&
250
- !explicitFormat.json_schema?.schema
251
- ) {
252
- throw new Error(
253
- "responseFormat json_schema requires a defined schema object"
254
- );
255
- }
256
- return explicitFormat;
257
- }
258
-
259
- const schema = outputSchema || output_schema;
260
- if (!schema) return undefined;
261
-
262
- if (!schema.name || !schema.schema) {
263
- throw new Error("outputSchema requires both name and schema");
264
- }
265
-
266
- return {
267
- type: "json_schema",
268
- json_schema: {
269
- name: schema.name,
270
- schema: schema.schema,
271
- ...(typeof schema.strict === "boolean" ? { strict: schema.strict } : {}),
272
- },
273
- };
274
  };
275
 
276
  export async function invokeLLM(params: InvokeParams): Promise<InvokeResult> {
277
- const apiKey = assertApiKey();
278
-
279
- const {
280
- messages,
281
- tools,
282
- toolChoice,
283
- tool_choice,
284
- outputSchema,
285
- output_schema,
286
- responseFormat,
287
- response_format,
288
- } = params;
289
-
290
  const model = params.model || "huihui-ai/Qwen2.5-72B-Instruct-abliterated";
 
 
 
291
 
292
- const payload: Record<string, unknown> = {
293
- model: model,
294
- messages: messages.map(normalizeMessage),
295
- };
296
-
297
- if (tools && tools.length > 0) {
298
- payload.tools = tools;
299
- }
300
-
301
- const normalizedToolChoice = normalizeToolChoice(
302
- toolChoice || tool_choice,
303
- tools
304
- );
305
- if (normalizedToolChoice) {
306
- payload.tool_choice = normalizedToolChoice;
307
- }
308
-
309
- payload.max_tokens = 2048;
310
-
311
- const normalizedResponseFormat = normalizeResponseFormat({
312
- responseFormat,
313
- response_format,
314
- outputSchema,
315
- output_schema,
316
- });
317
-
318
- if (normalizedResponseFormat) {
319
- payload.response_format = normalizedResponseFormat;
320
- }
321
-
322
- const apiUrl = resolveApiUrl(model);
323
- console.log(`[LLM] Invoking ${model} at ${apiUrl}`);
324
 
325
  const response = await fetch(apiUrl, {
326
  method: "POST",
327
  headers: {
328
- "content-type": "application/json",
329
- authorization: `Bearer ${apiKey}`,
330
- "x-use-cache": "false",
331
- "x-wait-for-model": "true",
332
  },
333
- body: JSON.stringify(payload),
 
 
 
 
 
334
  });
335
 
336
  if (!response.ok) {
337
  const errorText = await response.text();
338
  console.error(`[LLM Error] Status: ${response.status}, Body: ${errorText}`);
339
- throw new Error(
340
- `LLM invoke failed: ${response.status} ${response.statusText} – ${errorText}`
341
- );
342
  }
343
 
344
  return (await response.json()) as InvokeResult;
 
2
 
3
  export type Role = "system" | "user" | "assistant" | "tool" | "function";
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  export type Message = {
6
  role: Role;
7
+ content: string;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  };
9
 
 
 
 
 
 
10
  export type InvokeParams = {
11
  messages: Message[];
12
  model?: string;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  };
14
 
15
  export type InvokeResult = {
 
 
 
16
  choices: Array<{
 
17
  message: {
18
  role: Role;
19
+ content: string;
 
20
  };
 
21
  }>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  };
23
 
24
  export async function invokeLLM(params: InvokeParams): Promise<InvokeResult> {
25
+ const apiKey = ENV.forgeApiKey || process.env.HF_TOKEN || process.env.HF_ACCESS_TOKEN;
26
+
27
+ // تحديد النموذج: كوين هو الافتراضي، أو أي نموذج آخر يتم تمريره (مثل ديب سيك)
 
 
 
 
 
 
 
 
 
 
28
  const model = params.model || "huihui-ai/Qwen2.5-72B-Instruct-abliterated";
29
+
30
+ // الرابط المباشر الذي يعمل في مثالك
31
+ const apiUrl = `https://api-inference.huggingface.co/models/${model}/v1/chat/completions`;
32
 
33
+ console.log(`[LLM] Invoking ${model} directly at ${apiUrl}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  const response = await fetch(apiUrl, {
36
  method: "POST",
37
  headers: {
38
+ "Content-Type": "application/json",
39
+ "Authorization": `Bearer ${apiKey}`,
 
 
40
  },
41
+ body: JSON.stringify({
42
+ model: model,
43
+ messages: params.messages,
44
+ max_tokens: 2048,
45
+ temperature: 0.8,
46
+ }),
47
  });
48
 
49
  if (!response.ok) {
50
  const errorText = await response.text();
51
  console.error(`[LLM Error] Status: ${response.status}, Body: ${errorText}`);
52
+ throw new Error(`LLM invoke failed: ${response.status} - ${errorText}`);
 
 
53
  }
54
 
55
  return (await response.json()) as InvokeResult;