315 lines
11 KiB
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);
|
|
} |