(res, response, enable_thinking, enable_web_search, requestBody = null, options = {})
| 98 | */ |
| 99 | const requiresToolCall = (toolChoice) => { |
| 100 | if (toolChoice === 'required') return true |
| 101 | if (toolChoice && typeof toolChoice === 'object' && toolChoice.type === 'function' && toolChoice.function?.name) { |
| 102 | return true |
| 103 | } |
| 104 | return false |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * 构建 tool_choice=required 重试时追加的强约束提示 |
| 109 | * @param {string|Object} toolChoice - OpenAI tool_choice |
| 110 | * @returns {string} 重试提示词 |
| 111 | */ |
| 112 | const buildRequiredRetryHint = (toolChoice) => { |
| 113 | if (toolChoice && typeof toolChoice === 'object' && toolChoice.function?.name) { |
| 114 | return `You did not call any tool in your previous reply. You MUST now call the tool \`${toolChoice.function.name}\` using the ${TOOL_CALL_OPEN}...${TOOL_CALL_CLOSE} format and nothing else.` |
| 115 | } |
| 116 | return `You did not call any tool in your previous reply. You MUST now call exactly one tool using the ${TOOL_CALL_OPEN}...${TOOL_CALL_CLOSE} format and nothing else.` |
| 117 | } |
| 118 | |
| 119 | const buildEmptyOutputRetryHint = () => [ |
| 120 | 'Your previous reply produced no visible final answer or executable tool call.', |
| 121 | `Continue the Agent task now. If any action remains, emit the required \`${TOOL_CALL_OPEN}\` block immediately with no preamble.`, |
| 122 | 'Only give a normal final answer when the task is actually complete; do not repeat hidden reasoning.' |
| 123 | ].join(' ') |
| 124 | |
| 125 | const buildMissingToolRetryHint = () => [ |
| 126 | 'Your previous reply described an action but did not execute any tool call.', |
| 127 | `Perform that action now by emitting the real \`${TOOL_CALL_OPEN}\` block immediately with no preamble.`, |
| 128 | 'Do not describe the action again or claim completion without a tool result.' |
| 129 | ].join(' ') |
| 130 | |
| 131 | const appendRetryHintToRequestBody = (requestBody, hint) => { |
| 132 | const messages = Array.isArray(requestBody?.messages) |
| 133 | ? requestBody.messages.map(message => ({ ...message })) |
| 134 | : [] |
| 135 | if (messages.length === 0) { |
| 136 | messages.push({ role: 'user', content: hint }) |
| 137 | } else { |
| 138 | const last = messages[messages.length - 1] |
| 139 | if (typeof last.content === 'string') { |
| 140 | last.content = `${last.content}\n\n# Tool-call retry\n${hint}` |
| 141 | } else if (Array.isArray(last.content)) { |
| 142 | const textPart = last.content.find(part => part?.type === 'text') |
| 143 | if (textPart) { |
| 144 | textPart.text = `${textPart.text || ''}\n\n# Tool-call retry\n${hint}` |
| 145 | } else { |
| 146 | last.content = [{ type: 'text', text: hint }, ...last.content] |
| 147 | } |
| 148 | } |
| 149 | } |
| 150 | return { ...requestBody, messages } |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * 处理流式响应 |
| 155 | * @param {object} res - Express 响应对象 |
| 156 | * @param {object} response - 上游响应流 |
| 157 | * @param {boolean} enable_thinking - 是否启用思考模式 |
no test coverage detected