feat: add AI endpoint manager plugin with configuration and routing capabilities
Some checks failed
Some checks failed
- Introduced `ai_endpoint_mgr` plugin to manage multiple AI provider endpoints. - Added configuration reference documentation for `config.toml`. - Implemented endpoint loading, active endpoint switching, and model mutation. - Included error handling for missing endpoints and configuration failures. - Developed unit tests covering various scenarios including error paths and concurrency.
This commit is contained in:
@@ -43,6 +43,7 @@ class SseSession;
|
||||
// 插件服务 vtable 的全局指针,在启动时从主机查询获取。
|
||||
static const dstalk_ai_service_t* g_ai = nullptr;
|
||||
static const dstalk_session_service_t* g_session = nullptr;
|
||||
static const dstalk_ai_endpoint_mgr_t* g_endpoint_mgr = nullptr; // I08: AI endpoint manager(可选)/ optional
|
||||
|
||||
// ---- 运行时状态 / Runtime state ----
|
||||
// g_quit signals the main loop to exit (set by Ctrl+C).
|
||||
@@ -208,8 +209,19 @@ static void run_chat_worker(
|
||||
};
|
||||
|
||||
// 调用流式 AI 聊天 / Call streaming AI chat
|
||||
dstalk_chat_result_t result = g_ai->chat_stream(
|
||||
history, history_count, nullptr, token_cb, &cb_data);
|
||||
// I08: 优先通过 endpoint_mgr 路由,fallback 到 g_ai / prefer endpoint_mgr, fallback to g_ai
|
||||
dstalk_chat_result_t result = {};
|
||||
const bool use_mgr = (g_endpoint_mgr && g_endpoint_mgr->count() > 0);
|
||||
if (use_mgr) {
|
||||
result = g_endpoint_mgr->chat_stream(
|
||||
nullptr, history, history_count, nullptr, token_cb, &cb_data);
|
||||
} else if (g_ai) {
|
||||
result = g_ai->chat_stream(
|
||||
history, history_count, nullptr, token_cb, &cb_data);
|
||||
} else {
|
||||
result.ok = 0;
|
||||
result.error = dstalk_strdup("AI service unavailable");
|
||||
}
|
||||
|
||||
// 将 AI 回复加入会话 / Add AI reply to session
|
||||
if (result.ok) {
|
||||
@@ -221,7 +233,11 @@ static void run_chat_worker(
|
||||
bool ok = result.ok;
|
||||
std::string content_copy = result.content ? result.content : "";
|
||||
std::string error_copy = result.error ? result.error : "";
|
||||
g_ai->free_result(&result);
|
||||
|
||||
// I08: 根据路由来源释放 result / free result based on routing source
|
||||
if (use_mgr) g_endpoint_mgr->free_result(&result);
|
||||
else if (g_ai) g_ai->free_result(&result);
|
||||
else if (result.error) dstalk_free((void*)result.error);
|
||||
|
||||
asio::post(ioc, [weak_sse, ok, content_copy, error_copy]() {
|
||||
if (auto sse = weak_sse.lock()) {
|
||||
@@ -373,6 +389,23 @@ private:
|
||||
if (model) st["model"] = std::string(model);
|
||||
st["status"] = "running";
|
||||
|
||||
// I08/I09: endpoint manager 状态 / endpoint manager status
|
||||
if (g_endpoint_mgr) {
|
||||
st["endpoint_mgr_available"] = true;
|
||||
st["endpoint_count"] = g_endpoint_mgr->count();
|
||||
const char* active = g_endpoint_mgr->get_active();
|
||||
if (active) st["active_endpoint"] = std::string(active);
|
||||
char* list_json = g_endpoint_mgr->list_json();
|
||||
if (list_json) {
|
||||
boost::system::error_code ec;
|
||||
auto jv = boost::json::parse(list_json, ec);
|
||||
if (!ec) st["endpoints"] = std::move(jv);
|
||||
dstalk_free(list_json);
|
||||
}
|
||||
} else {
|
||||
st["endpoint_mgr_available"] = false;
|
||||
}
|
||||
|
||||
auto self = shared_from_this();
|
||||
http::response<http::string_body> res{http::status::ok, request_.version()};
|
||||
res.set("Access-Control-Allow-Origin", "*");
|
||||
@@ -510,6 +543,9 @@ int main(int argc, char* argv[])
|
||||
if (!ai_provider) ai_provider = "ai_openai";
|
||||
g_ai = static_cast<const dstalk_ai_service_t*>(dstalk_service_query(ai_provider, 1));
|
||||
g_session = static_cast<const dstalk_session_service_t*>(dstalk_service_query("session", 1));
|
||||
// I08: 查询 AI endpoint manager(可选服务)/ query AI endpoint manager (optional service)
|
||||
g_endpoint_mgr = static_cast<const dstalk_ai_endpoint_mgr_t*>(
|
||||
dstalk_service_query("ai_endpoint_mgr", 1));
|
||||
|
||||
if (!g_ai) {
|
||||
std::fprintf(stderr, "[dstalk_web] AI service not found (check plugins directory)\n");
|
||||
|
||||
Reference in New Issue
Block a user