( messages: ChatMessage[], tools: ToolDef[], config: LLMConfig, )
| 208 | } |
| 209 | |
| 210 | async function chatOpenAI( |
| 211 | messages: ChatMessage[], |
| 212 | tools: ToolDef[], |
| 213 | config: LLMConfig, |
| 214 | ): Promise<LLMResponse> { |
| 215 | const body: Record<string, unknown> = { |
| 216 | model: config.model, |
| 217 | messages, |
| 218 | }; |
| 219 | if (tools.length > 0) { |
| 220 | body.tools = tools; |
| 221 | } |
| 222 | |
| 223 | const targetUrl = joinUrl(config.baseUrl, getOpenAICompletionsPath(config.baseUrl)); |
| 224 | const toolNames = Array.isArray(tools) ? tools.map((t) => t.function?.name).filter(Boolean) : []; |
| 225 | logger.info('ToolLog', 'LLM Request: toolCount=', tools.length, 'toolNames=', toolNames); |
| 226 | logger.info('LLM', 'Request:', { |
| 227 | targetUrl, |
| 228 | model: config.model, |
| 229 | messageCount: messages.length, |
| 230 | toolCount: tools.length, |
| 231 | }); |
| 232 | const headers: Record<string, string> = { |
| 233 | 'Content-Type': 'application/json', |
| 234 | 'X-LLM-Target-URL': targetUrl, |
| 235 | ...parseCustomHeaders(config.customHeaders), |
| 236 | }; |
| 237 | if (config.apiKey.trim()) { |
| 238 | headers.Authorization = `Bearer ${config.apiKey}`; |
| 239 | } |
| 240 | const res = await fetch('/api/llm-proxy', { |
| 241 | method: 'POST', |
| 242 | headers, |
| 243 | body: JSON.stringify(body), |
| 244 | }); |
| 245 | |
| 246 | logger.info('LLM', 'Response status:', res.status); |
| 247 | const text = await res.text(); |
| 248 | logger.info('LLM', 'Response body:', text.slice(0, 500)); |
| 249 | |
| 250 | if (!res.ok) { |
| 251 | throw new Error(`LLM API error ${res.status}: ${text}`); |
| 252 | } |
| 253 | |
| 254 | const data = JSON.parse(text); |
| 255 | const choice = data.choices?.[0]?.message; |
| 256 | const parsedInline = extractInlineToolCalls(choice?.content || ''); |
| 257 | const toolCalls = choice?.tool_calls?.length ? choice.tool_calls : parsedInline.toolCalls; |
| 258 | const calledNames = toolCalls |
| 259 | .map((tc: { function?: { name?: string } }) => tc.function?.name) |
| 260 | .filter(Boolean); |
| 261 | logger.info( |
| 262 | 'ToolLog', |
| 263 | 'LLM Response: toolCalls count=', |
| 264 | toolCalls.length, |
| 265 | 'calledNames=', |
| 266 | calledNames, |
| 267 | ); |
no test coverage detected