Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/arbiterAI/arbiterAI.h
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ struct CompletionRequest
std::optional<std::vector<std::string>> stop;
std::optional<std::vector<ToolDefinition>> tools; ///< Available tools for the model
std::optional<std::string> tool_choice; ///< Tool selection mode: "auto", "none", or specific tool name
std::optional<std::map<std::string, double>> logit_bias; ///< Token ID to bias value
};

inline void to_json(nlohmann::json &j, const CompletionRequest &r)
Expand Down Expand Up @@ -429,6 +430,7 @@ inline void from_json(const nlohmann::json &j, Usage &u)
struct CompletionResponse
{
std::string text;
std::string reasoningContent; ///< Chain-of-thought / reasoning (e.g. DeepSeek reasoning_content)
std::string model;
Usage usage;
std::string provider; // "openai", "anthropic", etc.
Expand All @@ -449,6 +451,7 @@ inline void to_json(nlohmann::json &j, const CompletionResponse &r)
{"finish_reason", r.finishReason},
{"from_cache", r.fromCache}
};
if (!r.reasoningContent.empty()) j["reasoning_content"] = r.reasoningContent;
if (!r.toolCalls.empty()) j["tool_calls"] = r.toolCalls;
}

Expand All @@ -458,6 +461,7 @@ inline void from_json(const nlohmann::json &j, CompletionResponse &r)
j.at("model").get_to(r.model);
j.at("usage").get_to(r.usage);
j.at("provider").get_to(r.provider);
if (j.contains("reasoning_content")) j.at("reasoning_content").get_to(r.reasoningContent);
if (j.contains("cost")) j.at("cost").get_to(r.cost);
if (j.contains("tool_calls")) j.at("tool_calls").get_to(r.toolCalls);
if (j.contains("finish_reason")) j.at("finish_reason").get_to(r.finishReason);
Expand Down
24 changes: 21 additions & 3 deletions src/arbiterAI/providers/deepseek.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,31 @@ ErrorCode Deepseek::parseResponse(const cpr::Response &rawResponse,
// Extract the response text from the first choice
if(!jsonResponse.contains("choices")||
jsonResponse["choices"].empty()||
!jsonResponse["choices"][0].contains("message")||
!jsonResponse["choices"][0]["message"].contains("content"))
!jsonResponse["choices"][0].contains("message"))
{
return ErrorCode::InvalidResponse;
}

response.text=jsonResponse["choices"][0]["message"]["content"];
const auto &message = jsonResponse["choices"][0]["message"];

// Extract reasoning_content (chain-of-thought)
if(message.contains("reasoning_content") && !message["reasoning_content"].is_null())
{
response.reasoningContent = message["reasoning_content"].get<std::string>();
}

// Extract content
if(message.contains("content") && !message["content"].is_null())
{
response.text = message["content"].get<std::string>();
}

// Fallback: use reasoning as text if content is empty
if(response.text.empty() && !response.reasoningContent.empty())
{
response.text = response.reasoningContent;
}

response.provider="deepseek";

if(jsonResponse.contains("model"))
Expand Down
27 changes: 22 additions & 5 deletions src/arbiterAI/providers/openai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ ErrorCode OpenAI::parseResponse(const cpr::Response &rawResponse,
response.finishReason = choice["finish_reason"].get<std::string>();
}

// Extract reasoning_content (chain-of-thought from reasoning models)
if(message.contains("reasoning_content") && !message["reasoning_content"].is_null())
{
response.reasoningContent = message["reasoning_content"].get<std::string>();
}

// Extract content (may be empty/null for tool_calls responses)
if(message.contains("content") && !message["content"].is_null())
{
Expand Down Expand Up @@ -327,6 +333,13 @@ ErrorCode OpenAI::parseResponse(const cpr::Response &rawResponse,
response.usage.completion_tokens=jsonResponse["usage"]["completion_tokens"];
}

// If content is empty but we have reasoning, use reasoning as the text
// so callers that only read .text still get the model output
if(response.text.empty() && !response.reasoningContent.empty() && response.toolCalls.empty())
{
response.text = response.reasoningContent;
}

return ErrorCode::Success;
}

Expand Down Expand Up @@ -366,12 +379,16 @@ ErrorCode OpenAI::streamingCompletion(const CompletionRequest &request,

auto json=nlohmann::json::parse(jsonStr);
if(json.contains("choices")&&!json["choices"].empty()&&
json["choices"][0].contains("delta")&&
json["choices"][0]["delta"].contains("content"))
json["choices"][0].contains("delta"))
{

std::string content=json["choices"][0]["delta"]["content"];
callback(content);
const auto &delta = json["choices"][0]["delta"];
std::string chunk;
if(delta.contains("reasoning_content") && !delta["reasoning_content"].is_null())
chunk += delta["reasoning_content"].get<std::string>();
if(delta.contains("content") && !delta["content"].is_null())
chunk += delta["content"].get<std::string>();
if(!chunk.empty())
callback(chunk);
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion src/arbiterAI/providers/openrouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,17 @@ ErrorCode OpenRouter_LLM::parseResponse(const cpr::Response &rawResponse, Comple
{
nlohmann::json jsonResponse=nlohmann::json::parse(rawResponse.text);
const auto &choice=jsonResponse["choices"][0];
response.text=choice["message"]["content"];
const auto &message=choice["message"];

if(message.contains("reasoning_content") && !message["reasoning_content"].is_null())
response.reasoningContent=message["reasoning_content"].get<std::string>();

if(message.contains("content") && !message["content"].is_null())
response.text=message["content"].get<std::string>();

if(response.text.empty() && !response.reasoningContent.empty())
response.text=response.reasoningContent;

response.model=jsonResponse["model"];
if(jsonResponse.contains("usage"))
{
Expand Down
Loading