Files
mimiclaw/main/llm/llm_provider.c

315 lines
11 KiB
C

#include "llm_provider.h"
#include "mimi_config.h"
#include "nvs.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_http_client.h"
static const char *TAG = "llm_provider";
#define LLM_API_KEY_MAX_LEN 320
/* Provider registry - all supported providers */
const llm_provider_config_t llm_providers[] = {
{
.name = "anthropic",
.default_api_url = MIMI_LLM_API_URL,
.default_host = "api.anthropic.com",
.default_path = "/v1/messages",
.is_openai_compatible = false,
},
{
.name = "openai",
.default_api_url = MIMI_OPENAI_API_URL,
.default_host = "api.openai.com",
.default_path = "/v1/chat/completions",
.is_openai_compatible = true,
},
{
.name = "siliconflow",
.default_api_url = MIMI_SILICONFLOW_API_URL,
.default_host = "api.siliconflow.cn",
.default_path = "/v1/chat/completions",
.is_openai_compatible = true,
},
{
.name = "volcengine",
.default_api_url = MIMI_VOLCENGINE_API_URL,
.default_host = "ark.cn-beijing.volces.com",
.default_path = "/v1/chat/completions",
.is_openai_compatible = true,
},
};
const int llm_provider_count = sizeof(llm_providers) / sizeof(llm_providers[0]);
/* Current provider state */
static const llm_provider_config_t *s_current_provider = &llm_providers[0]; /* Default to anthropic */
static char s_api_key[LLM_API_KEY_MAX_LEN] = {0};
static char s_base_url[256] = {0};
/* Helper function to get NVS key for provider API key */
static const char *get_provider_api_key_nvs_key(const char *provider_name) {
if (strcmp(provider_name, "anthropic") == 0) return MIMI_NVS_KEY_ANTHROPIC_API_KEY;
if (strcmp(provider_name, "openai") == 0) return MIMI_NVS_KEY_OPENAI_API_KEY;
if (strcmp(provider_name, "siliconflow") == 0) return MIMI_NVS_KEY_SILICONFLOW_API_KEY;
if (strcmp(provider_name, "volcengine") == 0) return MIMI_NVS_KEY_VOLCENGINE_API_KEY;
return NULL;
}
/* Helper function to get NVS key for provider Base URL */
static const char *get_provider_base_url_nvs_key(const char *provider_name) {
if (strcmp(provider_name, "anthropic") == 0) return MIMI_NVS_KEY_ANTHROPIC_BASE_URL;
if (strcmp(provider_name, "openai") == 0) return MIMI_NVS_KEY_OPENAI_BASE_URL;
if (strcmp(provider_name, "siliconflow") == 0) return MIMI_NVS_KEY_SILICONFLOW_BASE_URL;
if (strcmp(provider_name, "volcengine") == 0) return MIMI_NVS_KEY_VOLCENGINE_BASE_URL;
return NULL;
}
/* Find provider configuration by name */
const llm_provider_config_t *llm_provider_find(const char *name) {
if (!name) return NULL;
for (int i = 0; i < llm_provider_count; i++) {
if (strcmp(llm_providers[i].name, name) == 0) {
return &llm_providers[i];
}
}
return NULL;
}
/* Get current provider configuration */
const llm_provider_config_t *llm_provider_current(void) {
return s_current_provider;
}
/* Set current provider by name */
void llm_provider_set_current(const char *name) {
const llm_provider_config_t *provider = llm_provider_find(name);
if (provider) {
s_current_provider = provider;
/* Load provider-specific configuration */
llm_provider_init();
ESP_LOGI(TAG, "Current provider set to: %s", name);
} else {
ESP_LOGW(TAG, "Unknown provider: %s", name);
}
}
/* Get current provider name */
const char *llm_provider_current_name(void) {
return s_current_provider ? s_current_provider->name : "unknown";
}
/* Check if current provider is OpenAI-compatible */
bool llm_provider_is_openai_compatible(void) {
return s_current_provider ? s_current_provider->is_openai_compatible : false;
}
/* Get API URL for current provider (with dynamic Base URL support) */
const char *llm_provider_api_url(void) {
if (s_base_url[0] != '\0') {
/* Use configured Base URL */
static char full_url[512];
snprintf(full_url, sizeof(full_url), "%s%s", s_base_url, s_current_provider->default_path);
return full_url;
}
/* Use default API URL */
return s_current_provider ? s_current_provider->default_api_url : "";
}
/* Get hostname for current provider */
const char *llm_provider_host(void) {
if (s_base_url[0] != '\0') {
/* Extract hostname from Base URL */
static char hostname[256];
const char *start = s_base_url;
if (strncmp(start, "https://", 8) == 0) start += 8;
else if (strncmp(start, "http://", 7) == 0) start += 7;
const char *end = strchr(start, '/');
if (end) {
size_t len = end - start;
if (len < sizeof(hostname)) {
memcpy(hostname, start, len);
hostname[len] = '\0';
return hostname;
}
}
/* No path, copy whole string */
strncpy(hostname, start, sizeof(hostname) - 1);
hostname[sizeof(hostname) - 1] = '\0';
return hostname;
}
/* Use default host */
return s_current_provider ? s_current_provider->default_host : "";
}
/* Get API path for current provider */
const char *llm_provider_path(void) {
return s_current_provider ? s_current_provider->default_path : "";
}
/* Provider-specific API key management */
void llm_provider_set_api_key(const char *provider_name, const char *api_key) {
if (!provider_name || !api_key) return;
const char *nvs_key = get_provider_api_key_nvs_key(provider_name);
if (!nvs_key) {
ESP_LOGW(TAG, "No NVS key for provider: %s", provider_name);
return;
}
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs) == ESP_OK) {
nvs_set_str(nvs, nvs_key, api_key);
nvs_commit(nvs);
nvs_close(nvs);
ESP_LOGI(TAG, "API key saved for provider: %s", provider_name);
}
/* If this is the current provider, update in-memory key */
if (strcmp(provider_name, s_current_provider->name) == 0) {
strncpy(s_api_key, api_key, sizeof(s_api_key) - 1);
s_api_key[sizeof(s_api_key) - 1] = '\0';
}
}
/* Provider-specific Base URL management */
void llm_provider_set_base_url(const char *provider_name, const char *base_url) {
if (!provider_name || !base_url) return;
const char *nvs_key = get_provider_base_url_nvs_key(provider_name);
if (!nvs_key) {
ESP_LOGW(TAG, "No NVS key for provider: %s", provider_name);
return;
}
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READWRITE, &nvs) == ESP_OK) {
nvs_set_str(nvs, nvs_key, base_url);
nvs_commit(nvs);
nvs_close(nvs);
ESP_LOGI(TAG, "Base URL saved for provider: %s", provider_name);
}
/* If this is the current provider, update in-memory Base URL */
if (strcmp(provider_name, s_current_provider->name) == 0) {
strncpy(s_base_url, base_url, sizeof(s_base_url) - 1);
s_base_url[sizeof(s_base_url) - 1] = '\0';
}
}
/* Get API key for a provider */
const char *llm_provider_get_api_key(const char *provider_name) {
if (!provider_name) return NULL;
/* If this is the current provider, return in-memory key */
if (strcmp(provider_name, s_current_provider->name) == 0) {
return s_api_key;
}
/* Otherwise, load from NVS */
static char key_buffer[LLM_API_KEY_MAX_LEN] = {0};
const char *nvs_key = get_provider_api_key_nvs_key(provider_name);
if (!nvs_key) return NULL;
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(key_buffer);
if (nvs_get_str(nvs, nvs_key, key_buffer, &len) == ESP_OK && key_buffer[0]) {
nvs_close(nvs);
return key_buffer;
}
nvs_close(nvs);
}
return NULL;
}
/* Get Base URL for a provider */
const char *llm_provider_get_base_url(const char *provider_name) {
if (!provider_name) return NULL;
/* If this is the current provider, return in-memory Base URL */
if (strcmp(provider_name, s_current_provider->name) == 0) {
return s_base_url[0] ? s_base_url : NULL;
}
/* Otherwise, load from NVS */
static char url_buffer[256] = {0};
const char *nvs_key = get_provider_base_url_nvs_key(provider_name);
if (!nvs_key) return NULL;
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(url_buffer);
if (nvs_get_str(nvs, nvs_key, url_buffer, &len) == ESP_OK && url_buffer[0]) {
nvs_close(nvs);
return url_buffer;
}
nvs_close(nvs);
}
return NULL;
}
/* Initialize provider system (load from NVS) */
void llm_provider_init(void) {
const char *nvs_key = get_provider_api_key_nvs_key(s_current_provider->name);
if (nvs_key) {
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(s_api_key);
if (nvs_get_str(nvs, nvs_key, s_api_key, &len) != ESP_OK || !s_api_key[0]) {
s_api_key[0] = '\0';
}
nvs_close(nvs);
} else {
s_api_key[0] = '\0';
}
} else {
s_api_key[0] = '\0';
}
/* Load Base URL for current provider directly from NVS */
const char *url_nvs_key = get_provider_base_url_nvs_key(s_current_provider->name);
if (url_nvs_key) {
nvs_handle_t nvs;
if (nvs_open(MIMI_NVS_LLM, NVS_READONLY, &nvs) == ESP_OK) {
size_t len = sizeof(s_base_url);
if (nvs_get_str(nvs, url_nvs_key, s_base_url, &len) != ESP_OK || !s_base_url[0]) {
s_base_url[0] = '\0';
}
nvs_close(nvs);
} else {
s_base_url[0] = '\0';
}
} else {
s_base_url[0] = '\0';
}
ESP_LOGI(TAG, "Provider initialized: %s (API key: %s, Base URL: %s)",
s_current_provider->name,
s_api_key[0] ? "set" : "not set",
s_base_url[0] ? s_base_url : "default");
}
/* Save provider configuration to NVS */
void llm_provider_save_config(const char *provider_name) {
/* This function is intentionally left empty -
individual set functions already save to NVS */
}
/* Common authentication header setup for OpenAI-compatible providers */
void llm_provider_set_auth_headers(esp_http_client_handle_t client, const char *api_key) {
if (!client || !api_key) return;
/* All OpenAI-compatible providers use Bearer token authentication */
char auth[LLM_API_KEY_MAX_LEN + 16];
snprintf(auth, sizeof(auth), "Bearer %s", api_key);
esp_http_client_set_header(client, "Authorization", auth);
}