- 创建通用提供商架构(llm_provider.h/c) - 支持四个提供商:Anthropic、OpenAI、SiliconFlow、Volcengine - 添加提供商特定的API密钥和Base URL配置 - 扩展CLI命令:set_siliconflow_key/url、set_volcengine_key/url - 更新mimi_secrets.h.example配置模板 - 更新README.md文档说明 - 每个提供商支持独立的NVS存储配置
This commit is contained in:
298
main/llm/llm_provider.c
Normal file
298
main/llm/llm_provider.c
Normal file
@@ -0,0 +1,298 @@
|
||||
#include "llm_provider.h"
|
||||
#include "mimi_config.h"
|
||||
#include "nvs.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.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) {
|
||||
/* Load API key for current provider */
|
||||
const char *api_key = llm_provider_get_api_key(s_current_provider->name);
|
||||
if (api_key) {
|
||||
strncpy(s_api_key, api_key, sizeof(s_api_key) - 1);
|
||||
s_api_key[sizeof(s_api_key) - 1] = '\0';
|
||||
} else {
|
||||
s_api_key[0] = '\0';
|
||||
}
|
||||
|
||||
/* Load Base URL for current provider */
|
||||
const char *base_url = llm_provider_get_base_url(s_current_provider->name);
|
||||
if (base_url) {
|
||||
strncpy(s_base_url, base_url, sizeof(s_base_url) - 1);
|
||||
s_base_url[sizeof(s_base_url) - 1] = '\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);
|
||||
}
|
||||
68
main/llm/llm_provider.h
Normal file
68
main/llm/llm_provider.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Forward declaration for esp_http_client_handle_t */
|
||||
typedef struct esp_http_client* esp_http_client_handle_t;
|
||||
|
||||
/* Provider configuration structure */
|
||||
typedef struct {
|
||||
const char *name; /* Provider name, e.g., "anthropic", "openai", "siliconflow", "volcengine" */
|
||||
const char *default_api_url; /* Default API URL */
|
||||
const char *default_host; /* Default hostname */
|
||||
const char *default_path; /* Default API path */
|
||||
bool is_openai_compatible; /* Whether this provider uses OpenAI-compatible API */
|
||||
} llm_provider_config_t;
|
||||
|
||||
/* Provider registry - all supported providers */
|
||||
extern const llm_provider_config_t llm_providers[];
|
||||
extern const int llm_provider_count;
|
||||
|
||||
/* Find provider configuration by name */
|
||||
const llm_provider_config_t *llm_provider_find(const char *name);
|
||||
|
||||
/* Get current provider configuration */
|
||||
const llm_provider_config_t *llm_provider_current(void);
|
||||
|
||||
/* Set current provider by name */
|
||||
void llm_provider_set_current(const char *name);
|
||||
|
||||
/* Get current provider name */
|
||||
const char *llm_provider_current_name(void);
|
||||
|
||||
/* Check if current provider is OpenAI-compatible */
|
||||
bool llm_provider_is_openai_compatible(void);
|
||||
|
||||
/* Get API URL for current provider (with dynamic Base URL support) */
|
||||
const char *llm_provider_api_url(void);
|
||||
|
||||
/* Get hostname for current provider */
|
||||
const char *llm_provider_host(void);
|
||||
|
||||
/* Get API path for current provider */
|
||||
const char *llm_provider_path(void);
|
||||
|
||||
/* Provider-specific API key and Base URL management */
|
||||
void llm_provider_set_api_key(const char *provider_name, const char *api_key);
|
||||
void llm_provider_set_base_url(const char *provider_name, const char *base_url);
|
||||
const char *llm_provider_get_api_key(const char *provider_name);
|
||||
const char *llm_provider_get_base_url(const char *provider_name);
|
||||
|
||||
/* Initialize provider system (load from NVS) */
|
||||
void llm_provider_init(void);
|
||||
|
||||
/* Save provider configuration to NVS */
|
||||
void llm_provider_save_config(const char *provider_name);
|
||||
|
||||
/* Common authentication header setup for OpenAI-compatible providers */
|
||||
void llm_provider_set_auth_headers(esp_http_client_handle_t client, const char *api_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "llm_proxy.h"
|
||||
#include "llm_provider.h"
|
||||
#include "mimi_config.h"
|
||||
#include "proxy/http_proxy.h"
|
||||
|
||||
@@ -184,28 +185,31 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
|
||||
static bool provider_is_openai(void)
|
||||
{
|
||||
return strcmp(s_provider, "openai") == 0;
|
||||
return llm_provider_is_openai_compatible();
|
||||
}
|
||||
|
||||
static const char *llm_api_url(void)
|
||||
{
|
||||
return provider_is_openai() ? MIMI_OPENAI_API_URL : MIMI_LLM_API_URL;
|
||||
return llm_provider_api_url();
|
||||
}
|
||||
|
||||
static const char *llm_api_host(void)
|
||||
{
|
||||
return provider_is_openai() ? "api.openai.com" : "api.anthropic.com";
|
||||
return llm_provider_host();
|
||||
}
|
||||
|
||||
static const char *llm_api_path(void)
|
||||
{
|
||||
return provider_is_openai() ? "/v1/chat/completions" : "/v1/messages";
|
||||
return llm_provider_path();
|
||||
}
|
||||
|
||||
/* ── Init ─────────────────────────────────────────────────────── */
|
||||
|
||||
esp_err_t llm_proxy_init(void)
|
||||
{
|
||||
/* Initialize provider system */
|
||||
llm_provider_init();
|
||||
|
||||
/* Start with build-time defaults */
|
||||
if (MIMI_SECRET_API_KEY[0] != '\0') {
|
||||
safe_copy(s_api_key, sizeof(s_api_key), MIMI_SECRET_API_KEY);
|
||||
@@ -215,6 +219,8 @@ esp_err_t llm_proxy_init(void)
|
||||
}
|
||||
if (MIMI_SECRET_MODEL_PROVIDER[0] != '\0') {
|
||||
safe_copy(s_provider, sizeof(s_provider), MIMI_SECRET_MODEL_PROVIDER);
|
||||
/* Set current provider based on build-time default */
|
||||
llm_provider_set_current(s_provider);
|
||||
}
|
||||
|
||||
/* NVS overrides take highest priority (set via CLI) */
|
||||
@@ -234,10 +240,18 @@ esp_err_t llm_proxy_init(void)
|
||||
len = sizeof(provider_tmp);
|
||||
if (nvs_get_str(nvs, MIMI_NVS_KEY_PROVIDER, provider_tmp, &len) == ESP_OK && provider_tmp[0]) {
|
||||
safe_copy(s_provider, sizeof(s_provider), provider_tmp);
|
||||
/* Set current provider based on NVS override */
|
||||
llm_provider_set_current(s_provider);
|
||||
}
|
||||
nvs_close(nvs);
|
||||
}
|
||||
|
||||
/* Load provider-specific API key if available */
|
||||
const char *provider_api_key = llm_provider_get_api_key(s_provider);
|
||||
if (provider_api_key && provider_api_key[0]) {
|
||||
safe_copy(s_api_key, sizeof(s_api_key), provider_api_key);
|
||||
}
|
||||
|
||||
if (s_api_key[0]) {
|
||||
ESP_LOGI(TAG, "LLM proxy initialized (provider: %s, model: %s)", s_provider, s_model);
|
||||
} else {
|
||||
@@ -265,13 +279,15 @@ static esp_err_t llm_http_direct(const char *post_data, resp_buf_t *rb, int *out
|
||||
|
||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||
if (provider_is_openai()) {
|
||||
|
||||
/* Use provider-specific authentication */
|
||||
if (llm_provider_is_openai_compatible()) {
|
||||
/* OpenAI-compatible providers use Bearer token authentication */
|
||||
if (s_api_key[0]) {
|
||||
char auth[LLM_API_KEY_MAX_LEN + 16];
|
||||
snprintf(auth, sizeof(auth), "Bearer %s", s_api_key);
|
||||
esp_http_client_set_header(client, "Authorization", auth);
|
||||
llm_provider_set_auth_headers(client, s_api_key);
|
||||
}
|
||||
} else {
|
||||
/* Anthropic uses x-api-key authentication */
|
||||
esp_http_client_set_header(client, "x-api-key", s_api_key);
|
||||
esp_http_client_set_header(client, "anthropic-version", MIMI_LLM_API_VERSION);
|
||||
}
|
||||
@@ -293,7 +309,9 @@ static esp_err_t llm_http_via_proxy(const char *post_data, resp_buf_t *rb, int *
|
||||
int body_len = strlen(post_data);
|
||||
char header[1024];
|
||||
int hlen = 0;
|
||||
if (provider_is_openai()) {
|
||||
|
||||
if (llm_provider_is_openai_compatible()) {
|
||||
/* OpenAI-compatible providers use Bearer token authentication */
|
||||
hlen = snprintf(header, sizeof(header),
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %s\r\n"
|
||||
@@ -303,6 +321,7 @@ static esp_err_t llm_http_via_proxy(const char *post_data, resp_buf_t *rb, int *
|
||||
"Connection: close\r\n\r\n",
|
||||
llm_api_path(), llm_api_host(), s_api_key, body_len);
|
||||
} else {
|
||||
/* Anthropic uses x-api-key authentication */
|
||||
hlen = snprintf(header, sizeof(header),
|
||||
"POST %s HTTP/1.1\r\n"
|
||||
"Host: %s\r\n"
|
||||
@@ -559,13 +578,13 @@ esp_err_t llm_chat_tools(const char *system_prompt,
|
||||
/* Build request body (non-streaming) */
|
||||
cJSON *body = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(body, "model", s_model);
|
||||
if (provider_is_openai()) {
|
||||
if (llm_provider_is_openai_compatible()) {
|
||||
cJSON_AddNumberToObject(body, "max_completion_tokens", MIMI_LLM_MAX_TOKENS);
|
||||
} else {
|
||||
cJSON_AddNumberToObject(body, "max_tokens", MIMI_LLM_MAX_TOKENS);
|
||||
}
|
||||
|
||||
if (provider_is_openai()) {
|
||||
if (llm_provider_is_openai_compatible()) {
|
||||
cJSON *openai_msgs = convert_messages_openai(system_prompt, messages);
|
||||
cJSON_AddItemToObject(body, "messages", openai_msgs);
|
||||
|
||||
@@ -635,7 +654,7 @@ esp_err_t llm_chat_tools(const char *system_prompt,
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (provider_is_openai()) {
|
||||
if (llm_provider_is_openai_compatible()) {
|
||||
cJSON *choices = cJSON_GetObjectItem(root, "choices");
|
||||
cJSON *choice0 = choices && cJSON_IsArray(choices) ? cJSON_GetArrayItem(choices, 0) : NULL;
|
||||
if (choice0) {
|
||||
@@ -780,7 +799,11 @@ esp_err_t llm_set_api_key(const char *api_key)
|
||||
nvs_close(nvs);
|
||||
|
||||
safe_copy(s_api_key, sizeof(s_api_key), api_key);
|
||||
ESP_LOGI(TAG, "API key saved");
|
||||
|
||||
/* Also save to provider-specific NVS key */
|
||||
llm_provider_set_api_key(s_provider, api_key);
|
||||
|
||||
ESP_LOGI(TAG, "API key saved for provider: %s", s_provider);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -806,6 +829,43 @@ esp_err_t llm_set_provider(const char *provider)
|
||||
nvs_close(nvs);
|
||||
|
||||
safe_copy(s_provider, sizeof(s_provider), provider);
|
||||
|
||||
/* Update current provider in the provider system */
|
||||
llm_provider_set_current(provider);
|
||||
|
||||
/* Load provider-specific API key if available */
|
||||
const char *provider_api_key = llm_provider_get_api_key(provider);
|
||||
if (provider_api_key && provider_api_key[0]) {
|
||||
safe_copy(s_api_key, sizeof(s_api_key), provider_api_key);
|
||||
} else {
|
||||
s_api_key[0] = '\0';
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Provider set to: %s", s_provider);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t llm_set_base_url(const char *provider, const char *base_url)
|
||||
{
|
||||
if (!provider || !base_url) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
/* Save to provider-specific NVS key */
|
||||
llm_provider_set_base_url(provider, base_url);
|
||||
|
||||
/* If this is the current provider, update in-memory Base URL */
|
||||
if (strcmp(provider, s_provider) == 0) {
|
||||
/* Reload provider configuration to pick up new Base URL */
|
||||
llm_provider_init();
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Base URL set for provider: %s", provider);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const char *llm_get_base_url(const char *provider)
|
||||
{
|
||||
if (!provider) return NULL;
|
||||
|
||||
/* Get Base URL from provider system */
|
||||
return llm_provider_get_base_url(provider);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,16 @@ esp_err_t llm_set_provider(const char *provider);
|
||||
*/
|
||||
esp_err_t llm_set_model(const char *model);
|
||||
|
||||
/**
|
||||
* Save the Base URL for a provider to NVS.
|
||||
*/
|
||||
esp_err_t llm_set_base_url(const char *provider, const char *base_url);
|
||||
|
||||
/**
|
||||
* Get the Base URL for a provider from NVS.
|
||||
*/
|
||||
const char *llm_get_base_url(const char *provider);
|
||||
|
||||
/* ── Tool Use Support ──────────────────────────────────────────── */
|
||||
|
||||
typedef struct {
|
||||
|
||||
Reference in New Issue
Block a user