|
6 | 6 | #include <variant> |
7 | 7 | #include <vector> |
8 | 8 |
|
9 | | -#include "core/LLMTypes.h" |
| 9 | +// Forward declarations to avoid circular dependency |
| 10 | +struct LLMRequest; |
| 11 | +struct LLMResponse; |
| 12 | + |
| 13 | +// Define LLMUsage here to avoid circular dependency |
| 14 | +struct LLMUsage { |
| 15 | + int inputTokens = 0; |
| 16 | + int outputTokens = 0; |
| 17 | + |
| 18 | + int totalTokens() const { return inputTokens + outputTokens; } |
| 19 | + |
| 20 | + std::string toString() const { |
| 21 | + return "LLMUsage { inputTokens: " + std::to_string(inputTokens) + |
| 22 | + ", outputTokens: " + std::to_string(outputTokens) + |
| 23 | + ", totalTokens: " + std::to_string(totalTokens()) + " }"; |
| 24 | + } |
| 25 | +}; |
10 | 26 |
|
11 | 27 | using json = nlohmann::json; |
12 | 28 |
|
@@ -1037,100 +1053,7 @@ const std::vector<std::string> CHAT_COMPLETION_MODELS = {"gpt-4", "gpt-4-turbo", |
1037 | 1053 | * These will be implemented in the source files |
1038 | 1054 | */ |
1039 | 1055 |
|
1040 | | -// ResponsesRequest conversion methods |
1041 | | -inline ResponsesRequest ResponsesRequest::fromLLMRequest(const LLMRequest& request) { |
1042 | | - ResponsesRequest responsesReq; |
1043 | | - responsesReq.model = request.config.model; |
1044 | | - |
1045 | | - // Map prompt to OpenAI instructions field |
1046 | | - if (!request.prompt.empty()) { |
1047 | | - responsesReq.instructions = request.prompt; |
1048 | | - } |
1049 | | - |
1050 | | - // Map context to OpenAI inputValues |
1051 | | - if (!request.context.empty()) { |
1052 | | - // Convert context (vector of json) to InputMessages |
1053 | | - std::vector<InputMessage> messages; |
1054 | | - |
1055 | | - for (const auto& contextItem : request.context) { |
1056 | | - // Case 1: Single JSON object with role/content |
1057 | | - if (contextItem.is_object() && contextItem.contains("role") && |
1058 | | - contextItem.contains("content")) { |
1059 | | - InputMessage msg; |
1060 | | - msg.role = InputMessage::stringToRole(contextItem["role"].get<std::string>()); |
1061 | | - msg.content = contextItem["content"].get<std::string>(); |
1062 | | - messages.push_back(msg); |
1063 | | - continue; |
1064 | | - } |
1065 | | - |
1066 | | - // Case 2: Array of message-like objects [{role, content}, ...] |
1067 | | - if (contextItem.is_array()) { |
1068 | | - for (const auto& item : contextItem) { |
1069 | | - if (item.is_object() && item.contains("role") && item.contains("content")) { |
1070 | | - InputMessage msg; |
1071 | | - msg.role = InputMessage::stringToRole(item["role"].get<std::string>()); |
1072 | | - msg.content = item["content"].get<std::string>(); |
1073 | | - messages.push_back(msg); |
1074 | | - } |
1075 | | - } |
1076 | | - continue; |
1077 | | - } |
1078 | | - |
1079 | | - // Fallback: stringify unknown item as a user message |
1080 | | - InputMessage msg; |
1081 | | - msg.role = InputMessage::Role::User; |
1082 | | - msg.content = contextItem.dump(); |
1083 | | - messages.push_back(msg); |
1084 | | - } |
1085 | | - |
1086 | | - responsesReq.input = ResponsesInput::fromContentList(messages); |
1087 | | - } else if (!request.prompt.empty()) { |
1088 | | - // If context is empty but prompt is present, use prompt as input |
1089 | | - responsesReq.input = ResponsesInput::fromText(request.prompt); |
1090 | | - } else { |
1091 | | - // If both context and prompt are empty, do not set input at all |
1092 | | - responsesReq.input = std::nullopt; |
1093 | | - } |
1094 | | - responsesReq.toolChoice = |
1095 | | - ToolChoiceMode::Auto; // Explicitly initialize to fix cppcheck warning |
1096 | | - if (request.config.maxTokens.has_value() && *request.config.maxTokens > 0) { |
1097 | | - responsesReq.maxOutputTokens = *request.config.maxTokens; |
1098 | | - } |
1099 | | - // Only set temperature if it's provided and valid |
1100 | | - if (request.config.temperature.has_value() && *request.config.temperature >= 0.0f) { |
1101 | | - responsesReq.temperature = static_cast<double>(*request.config.temperature); |
1102 | | - } |
1103 | | - if (!request.previousResponseId.empty()) { |
1104 | | - responsesReq.previousResponseID = request.previousResponseId; |
1105 | | - } |
1106 | | - |
1107 | | - // Handle JSON schema for structured outputs |
1108 | | - if (request.config.schemaObject.has_value()) { |
1109 | | - // Use the structured schema object directly |
1110 | | - const json& schemaJson = request.config.schemaObject.value(); |
1111 | | - // Use the function name as schema name (like aideas-core does) |
1112 | | - std::string schemaName = request.config.functionName; |
1113 | | - if (schemaName.empty()) { |
1114 | | - schemaName = "response_schema"; |
1115 | | - } |
1116 | | - responsesReq.text = TextOutputConfig(schemaName, schemaJson, true); |
1117 | | - } else if (!request.config.jsonSchema.empty()) { |
1118 | | - // Fallback to string schema for backward compatibility |
1119 | | - try { |
1120 | | - json schemaJson = json::parse(request.config.jsonSchema); |
1121 | | - // Use the function name as schema name (like aideas-core does) |
1122 | | - std::string schemaName = request.config.functionName; |
1123 | | - if (schemaName.empty()) { |
1124 | | - schemaName = "response_schema"; |
1125 | | - } |
1126 | | - responsesReq.text = TextOutputConfig(schemaName, schemaJson, true); |
1127 | | - } catch (const std::exception& e) { |
1128 | | - throw std::runtime_error("Invalid JSON schema: " + std::string(e.what())); |
1129 | | - } |
1130 | | - } |
1131 | | - |
1132 | | - return responsesReq; |
1133 | | -} |
| 1056 | +// Conversion methods - implementations moved to .cpp file to avoid circular dependency |
1134 | 1057 |
|
1135 | 1058 | inline ResponsesRequest ResponsesRequest::fromJson(const json& j) { |
1136 | 1059 | ResponsesRequest req; |
@@ -1246,57 +1169,7 @@ inline ResponsesResponse ResponsesResponse::fromJson(const json& j) { |
1246 | 1169 | return resp; |
1247 | 1170 | } |
1248 | 1171 |
|
1249 | | -inline LLMResponse ResponsesResponse::toLLMResponse(bool expectStructuredOutput) const { |
1250 | | - LLMResponse llmResp; |
1251 | | - llmResp.success = (status == ResponseStatus::Completed); |
1252 | | - llmResp.responseId = id; |
1253 | | - llmResp.usage = usage; |
1254 | | - |
1255 | | - if (hasError()) { |
1256 | | - llmResp.errorMessage = error->dump(); |
1257 | | - } else { |
1258 | | - // Extract text output from the response |
1259 | | - std::string textOutput = getOutputText(); |
1260 | | - if (!textOutput.empty()) { |
1261 | | - if (expectStructuredOutput) { |
1262 | | - // Parse as JSON for structured output |
1263 | | - llmResp.result = json::parse(textOutput); |
1264 | | - } else { |
1265 | | - // Wrap free-form text in text field |
1266 | | - llmResp.result = json{{"text", textOutput}}; |
1267 | | - } |
1268 | | - } else { |
1269 | | - llmResp.result = json::object(); |
1270 | | - } |
1271 | | - |
1272 | | - // Add function calls if any |
1273 | | - auto functionCalls = getFunctionCalls(); |
1274 | | - if (!functionCalls.empty()) { |
1275 | | - json calls = json::array(); |
1276 | | - std::transform(functionCalls.begin(), functionCalls.end(), std::back_inserter(calls), |
1277 | | - [](const FunctionCall& call) { |
1278 | | - return json{{"id", call.id}, |
1279 | | - {"name", call.name}, |
1280 | | - {"arguments", call.arguments}}; |
1281 | | - }); |
1282 | | - llmResp.result["function_calls"] = calls; |
1283 | | - } |
1284 | | - |
1285 | | - // Add images if any |
1286 | | - auto images = getImageGenerations(); |
1287 | | - if (!images.empty()) { |
1288 | | - json imageArray = json::array(); |
1289 | | - for (const auto& img : images) { |
1290 | | - if (img.result) { |
1291 | | - imageArray.push_back(*img.result); |
1292 | | - } |
1293 | | - } |
1294 | | - llmResp.result["images"] = imageArray; |
1295 | | - } |
1296 | | - } |
1297 | | - |
1298 | | - return llmResp; |
1299 | | -} |
| 1172 | +// Implementation moved to .cpp file |
1300 | 1173 |
|
1301 | 1174 | // Convenience methods for ResponsesResponse |
1302 | 1175 | inline std::string ResponsesResponse::getOutputText() const { |
@@ -1338,49 +1211,7 @@ inline std::vector<ImageGenerationCall> ResponsesResponse::getImageGenerations() |
1338 | 1211 | } |
1339 | 1212 |
|
1340 | 1213 | // ChatCompletionRequest conversion methods |
1341 | | -inline ChatCompletionRequest ChatCompletionRequest::fromLLMRequest(const LLMRequest& request) { |
1342 | | - ChatCompletionRequest chatReq; |
1343 | | - chatReq.model = request.config.model; |
1344 | | - |
1345 | | - // Convert prompt to messages |
1346 | | - if (!request.prompt.empty()) { |
1347 | | - ChatMessage userMsg; |
1348 | | - userMsg.role = "user"; |
1349 | | - userMsg.content = request.prompt; |
1350 | | - chatReq.messages.push_back(userMsg); |
1351 | | - } |
1352 | | - |
1353 | | - if (request.config.maxTokens.has_value() && *request.config.maxTokens > 0) { |
1354 | | - chatReq.maxTokens = *request.config.maxTokens; |
1355 | | - } |
1356 | | - // Only set temperature if it's provided and valid |
1357 | | - if (request.config.temperature.has_value() && *request.config.temperature >= 0.0f) { |
1358 | | - chatReq.temperature = static_cast<double>(*request.config.temperature); |
1359 | | - } |
1360 | | - |
1361 | | - return chatReq; |
1362 | | -} |
1363 | | - |
1364 | | -inline LLMRequest ChatCompletionRequest::toLLMRequest() const { |
1365 | | - LLMRequestConfig config; |
1366 | | - config.client = "openai"; |
1367 | | - config.model = model; |
1368 | | - if (temperature) config.temperature = static_cast<float>(*temperature); |
1369 | | - if (maxTokens) config.maxTokens = *maxTokens; |
1370 | | - |
1371 | | - std::string prompt; |
1372 | | - if (!messages.empty()) { |
1373 | | - // Use the last user message as prompt |
1374 | | - for (auto it = messages.rbegin(); it != messages.rend(); ++it) { |
1375 | | - if (it->role == "user") { |
1376 | | - prompt = it->content; |
1377 | | - break; |
1378 | | - } |
1379 | | - } |
1380 | | - } |
1381 | | - |
1382 | | - return LLMRequest(config, prompt); |
1383 | | -} |
| 1214 | +// Implementation moved to .cpp file |
1384 | 1215 |
|
1385 | 1216 | // ChatCompletionResponse conversion methods |
1386 | 1217 | inline ChatCompletionResponse ChatCompletionResponse::fromJson(const json& j) { |
@@ -1411,41 +1242,12 @@ inline ChatCompletionResponse ChatCompletionResponse::fromJson(const json& j) { |
1411 | 1242 | return resp; |
1412 | 1243 | } |
1413 | 1244 |
|
1414 | | -inline LLMResponse ChatCompletionResponse::toLLMResponse(bool expectStructuredOutput) const { |
1415 | | - LLMResponse llmResp; |
1416 | | - llmResp.success = !choices.empty(); |
1417 | | - llmResp.responseId = id; |
1418 | | - llmResp.usage = usage; |
1419 | | - |
1420 | | - if (!choices.empty()) { |
1421 | | - llmResp.result = json::object(); |
1422 | | - llmResp.result["text"] = choices[0].message.content; |
1423 | | - if (choices[0].message.toolCalls) { |
1424 | | - llmResp.result["tool_calls"] = *choices[0].message.toolCalls; |
1425 | | - } |
1426 | | - } else { |
1427 | | - llmResp.errorMessage = "No choices returned"; |
1428 | | - } |
1429 | | - |
1430 | | - return llmResp; |
1431 | | -} |
| 1245 | +// Implementation moved to .cpp file |
1432 | 1246 |
|
1433 | 1247 | /** |
1434 | 1248 | * Utility functions implementation |
1435 | 1249 | */ |
1436 | | -inline ApiType detectApiType(const LLMRequest& request) { |
1437 | | - const std::string& model = request.config.model; |
1438 | | - |
1439 | | - // Check if it's a Responses API model |
1440 | | - if (std::any_of( |
1441 | | - RESPONSES_MODELS.begin(), RESPONSES_MODELS.end(), |
1442 | | - [&model](const std::string& responsesModel) { return model == responsesModel; })) { |
1443 | | - return ApiType::RESPONSES; |
1444 | | - } |
1445 | | - |
1446 | | - // Default to Chat Completions for most models |
1447 | | - return ApiType::CHAT_COMPLETIONS; |
1448 | | -} |
| 1250 | +// Implementation moved to .cpp file to avoid circular dependency |
1449 | 1251 |
|
1450 | 1252 | inline bool supportsResponses(const std::string& model) { |
1451 | 1253 | return std::any_of( |
|
0 commit comments