mirror of
https://git.jabber.space/devs/cproof.git
synced 2026-07-19 04:26:22 +00:00
(broken) add json parsing for ai
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <glib.h>
|
||||
#include <json-glib/json-glib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Default providers */
|
||||
@@ -582,87 +583,109 @@ _build_json_payload(AISession* session, const gchar* prompt)
|
||||
return json_payload;
|
||||
}
|
||||
|
||||
static gchar*
|
||||
_parse_ai_response(const gchar* response_json)
|
||||
/** @brief Parse AI response JSON and extract content string.
|
||||
*
|
||||
* Tries multiple JSON paths to handle different provider response formats:
|
||||
* 1. Perplexity /v1/agent: {"output":[{"content":[{"text":"..."}]}]}
|
||||
* 2. OpenAI /v1/chat/completions: {"choices":[{"message":{"content":"..."}}]}
|
||||
* 3. Fallback: root-level "text" field
|
||||
* 4. Fallback: root-level "content" field
|
||||
*
|
||||
* @param response_json The JSON response string
|
||||
* @return Newly allocated content string, or NULL on failure
|
||||
*/
|
||||
gchar*
|
||||
ai_parse_response(const gchar* response_json)
|
||||
{
|
||||
if (!response_json || strlen(response_json) == 0)
|
||||
if (!response_json || strlen(response_json) == 0) {
|
||||
log_warning("[AI-THREAD] Empty or NULL response JSON");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Try Perplexity /v1/agent format first: look for "text":"..." inside output array
|
||||
* Response: {"output":[{"content":[{"text":"...","type":"output_text"}]}]} */
|
||||
const gchar* text_start = strstr(response_json, "\"text\":\"");
|
||||
if (text_start) {
|
||||
text_start += strlen("\"text\":\"");
|
||||
const gchar* p = text_start;
|
||||
while (*p) {
|
||||
if (*p == '\\' && *(p + 1) == '"') {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
if (*p == '"') {
|
||||
gsize len = p - text_start;
|
||||
gchar* result = g_new0(gchar, len + 1);
|
||||
gchar* out = result;
|
||||
const gchar* in = text_start;
|
||||
while (in < p) {
|
||||
if (*in == '\\' && *(in + 1) == '"') {
|
||||
*out++ = '"';
|
||||
in += 2;
|
||||
} else if (*in == '\\' && *(in + 1) == 'n') {
|
||||
*out++ = '\n';
|
||||
in += 2;
|
||||
} else {
|
||||
*out++ = *in++;
|
||||
/* Parse JSON using json-glib */
|
||||
auto_gchar gchar* error_msg = NULL;
|
||||
JsonParser* parser = json_parser_new();
|
||||
if (!json_parser_load_from_data(parser, response_json, -1, &error_msg)) {
|
||||
log_warning("[AI-THREAD] Failed to parse AI response JSON: %s", error_msg ? error_msg : "unknown error");
|
||||
g_object_unref(parser);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JsonNode* root = json_parser_get_root(parser);
|
||||
if (!root || !JSON_IS_OBJECT(json_node_get_object(root))) {
|
||||
log_warning("[AI-THREAD] Invalid AI response JSON: root is not an object");
|
||||
g_object_unref(parser);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JsonObject* root_obj = json_node_get_object(root);
|
||||
gchar* content = NULL;
|
||||
|
||||
/* Try 1: Perplexity /v1/agent format
|
||||
* {"output":[{"content":[{"text":"...","type":"output_text"}]}]} */
|
||||
JsonNode* output_node = json_object_get_member(root_obj, "output");
|
||||
if (output_node && JSON_IS_ARRAY(json_node_get_array(output_node))) {
|
||||
JsonArray* output_arr = json_node_get_array(output_node);
|
||||
if (json_array_get_length(output_arr) > 0) {
|
||||
JsonObject* first_output = json_array_get_object_element(output_arr, 0);
|
||||
JsonNode* content_node = json_object_get_member(first_output, "content");
|
||||
if (content_node && JSON_IS_ARRAY(json_node_get_array(content_node))) {
|
||||
JsonArray* content_arr = json_node_get_array(content_node);
|
||||
if (json_array_get_length(content_arr) > 0) {
|
||||
JsonObject* first_content = json_array_get_object_element(content_arr, 0);
|
||||
JsonNode* text_node = json_object_get_member(first_content, "text");
|
||||
if (text_node && JSON_IS_VALUE(text_node)) {
|
||||
content = g_strdup(json_node_get_string(text_node));
|
||||
}
|
||||
}
|
||||
*out = '\0';
|
||||
return result;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try legacy OpenAI format: "content":"..."
|
||||
* Response: {"choices":[{"message":{"content":"..."}}]} */
|
||||
const gchar* content_start = strstr(response_json, "\"content\":\"");
|
||||
if (!content_start)
|
||||
return NULL;
|
||||
|
||||
content_start += strlen("\"content\":\"");
|
||||
|
||||
/* Find the closing quote, accounting for escaped quotes */
|
||||
const gchar* p = content_start;
|
||||
while (*p) {
|
||||
if (*p == '\\' && *(p + 1) == '"') {
|
||||
/* Escaped quote, skip both characters */
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
if (*p == '"') {
|
||||
/* Found unescaped closing quote */
|
||||
gsize len = p - content_start;
|
||||
/* Unescape the content: convert \" back to " */
|
||||
gchar* result = g_new0(gchar, len + 1);
|
||||
gchar* out = result;
|
||||
const gchar* in = content_start;
|
||||
while (in < p) {
|
||||
if (*in == '\\' && *(in + 1) == '"') {
|
||||
*out++ = '"';
|
||||
in += 2;
|
||||
} else if (*in == '\\' && *(in + 1) == 'n') {
|
||||
*out++ = '\n';
|
||||
in += 2;
|
||||
} else {
|
||||
*out++ = *in++;
|
||||
/* Try 2: OpenAI /v1/chat/completions format
|
||||
* {"choices":[{"message":{"content":"..."}}]} */
|
||||
if (!content) {
|
||||
JsonNode* choices_node = json_object_get_member(root_obj, "choices");
|
||||
if (choices_node && JSON_IS_ARRAY(json_node_get_array(choices_node))) {
|
||||
JsonArray* choices_arr = json_node_get_array(choices_node);
|
||||
if (json_array_get_length(choices_arr) > 0) {
|
||||
JsonObject* first_choice = json_array_get_object_element(choices_arr, 0);
|
||||
JsonNode* message_node = json_object_get_member(first_choice, "message");
|
||||
if (message_node && JSON_IS_OBJECT(json_node_get_object(message_node))) {
|
||||
JsonObject* message = json_node_get_object(message_node);
|
||||
JsonNode* content_member = json_object_get_member(message, "content");
|
||||
if (content_member && JSON_IS_VALUE(content_member)) {
|
||||
content = g_strdup(json_node_get_string(content_member));
|
||||
}
|
||||
}
|
||||
}
|
||||
*out = '\0';
|
||||
return result;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
/* Try 3: Fallback - look for "text" field anywhere in the response
|
||||
* This handles edge cases where the response shape differs */
|
||||
if (!content) {
|
||||
JsonNode* text_node = json_object_get_member(root_obj, "text");
|
||||
if (text_node && JSON_IS_VALUE(text_node)) {
|
||||
content = g_strdup(json_node_get_string(text_node));
|
||||
}
|
||||
}
|
||||
|
||||
/* Try 4: Fallback - look for "content" field at root level */
|
||||
if (!content) {
|
||||
JsonNode* content_node = json_object_get_member(root_obj, "content");
|
||||
if (content_node && JSON_IS_VALUE(content_node)) {
|
||||
content = g_strdup(json_node_get_string(content_node));
|
||||
}
|
||||
}
|
||||
|
||||
g_object_unref(parser);
|
||||
|
||||
if (!content) {
|
||||
log_warning("[AI-THREAD] Could not extract content from AI response");
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
|
||||
Reference in New Issue
Block a user