Merge pull request #584 from luojiyin1987/fix/ai-suggestion-cache-key

fix(ai): include semantic request inputs in suggestion cache key
This commit is contained in:
Lu Fan(SonnyCalcr) authored and GitHub committed 2026-10-03 00:41:49 +08:00
commit f7a78183ee
6 files changed
+102 -9

No files matched your search

+2
View File
@@ -147,6 +147,8 @@ endif()
set(SRC_FILES
"./src/ai/ai_assistant.cpp"
"./src/ai/ai_assistant.h"
"./src/ai/ai_assistant_cache_key.cpp"
"./src/ai/ai_assistant_cache_key.h"
"./src/clipboard/clipboard_history.cpp"
"./src/clipboard/clipboard_history.h"
"./src/cloud/cloud_ime.cpp"
+3 -9
View File
@@ -1,4 +1,5 @@
#include "ai_assistant.h"
#include "ai_assistant_cache_key.h"
#include <curl/curl.h>
#include <nlohmann/json.hpp>
@@ -24,13 +25,6 @@ std::chrono::steady_clock::time_point g_last_input;
AiAssistant::ApplyCallback g_callback;
std::unordered_map<std::string, std::string> g_candidate_cache;
std::string BuildCacheKey(const AiAssistant::Request &request)
{
nlohmann::json segments = request.pinyin_segments;
return request.config.provider + "\n" + request.config.endpoint + "\n" + request.config.model + "\n" +
segments.dump();
}
size_t WriteResponse(char *data, size_t size, size_t count, void *user)
{
static_cast<std::string *>(user)->append(data, size * count);
@@ -135,7 +129,7 @@ void WorkerLoop()
break;
observed = g_generation.load();
auto request = g_latest;
const auto cached = g_candidate_cache.find(BuildCacheKey(request));
const auto cached = g_candidate_cache.find(AiAssistant::detail::BuildSuggestionCacheKey(request));
if (!request.identity.empty() && cached != g_candidate_cache.end())
{
const std::string candidate = cached->second;
@@ -164,7 +158,7 @@ void WorkerLoop()
{
{
std::lock_guard cache_lock(g_mutex);
g_candidate_cache[BuildCacheKey(request)] = candidate;
g_candidate_cache[AiAssistant::detail::BuildSuggestionCacheKey(request)] = candidate;
}
(void)0;
g_callback(candidate, request.identity, observed);
+19
View File
@@ -0,0 +1,19 @@
#include "ai_assistant_cache_key.h"
#include <nlohmann/json.hpp>
namespace AiAssistant::detail
{
std::string BuildSuggestionCacheKey(const Request &request)
{
return nlohmann::json{{"provider", request.config.provider},
{"endpoint", request.config.endpoint},
{"model", request.config.model},
{"prompt", request.config.prompt},
{"pinyin_segments", request.pinyin_segments},
{"context", request.context},
{"candidate_limit", request.config.candidate_limit}}
// context 与 prompt 是自由文本;键在无 try 的工作线程里构建,非法 UTF-8 不能抛出
.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
}
} // namespace AiAssistant::detail
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "ai_assistant.h"
#include <string>
namespace AiAssistant::detail
{
std::string BuildSuggestionCacheKey(const Request &request);
} // namespace AiAssistant::detail
+2
View File
@@ -36,6 +36,8 @@ set(TEST_SOURCE_FILES
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_input_key_policy.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_candidate_ui_owner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_async_request_origin.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_ai_assistant_cache_key.cpp"
"${CMAKE_SOURCE_DIR}/src/ai/ai_assistant_cache_key.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_candidate_ui_action_policy.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_candidate_translation_policy.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_candidate_ui_state.cpp"
@@ -0,0 +1,66 @@
#include "ai/ai_assistant_cache_key.h"
#include "tests/includes/test_framework.h"
namespace
{
AiAssistant::Request MakeRequest()
{
AiAssistant::Request request;
request.pinyin_segments = {"shang", "hai"};
request.context = "我准备去";
request.config.provider = "deepseek";
request.config.endpoint = "https://api.example.com/chat/completions";
request.config.model = "example-model";
request.config.prompt = "Return the best candidate.";
request.config.candidate_limit = 3;
return request;
}
} // namespace
TEST_CASE(ai_suggestion_cache_hits_for_the_same_semantic_request)
{
const auto request = MakeRequest();
const auto same_request = request;
REQUIRE(AiAssistant::detail::BuildSuggestionCacheKey(request) ==
AiAssistant::detail::BuildSuggestionCacheKey(same_request));
}
TEST_CASE(ai_suggestion_cache_misses_when_context_changes)
{
const auto request = MakeRequest();
auto changed_request = request;
changed_request.context = "他受到了严重";
REQUIRE(AiAssistant::detail::BuildSuggestionCacheKey(request) !=
AiAssistant::detail::BuildSuggestionCacheKey(changed_request));
}
TEST_CASE(ai_suggestion_cache_misses_when_prompt_changes)
{
const auto request = MakeRequest();
auto changed_request = request;
changed_request.config.prompt = "Prefer place names.";
REQUIRE(AiAssistant::detail::BuildSuggestionCacheKey(request) !=
AiAssistant::detail::BuildSuggestionCacheKey(changed_request));
}
TEST_CASE(ai_suggestion_cache_key_tolerates_invalid_utf8)
{
auto request = MakeRequest();
request.context = std::string("\xE6\x88", 2);
request.config.prompt = std::string("\xFF", 1);
REQUIRE(!AiAssistant::detail::BuildSuggestionCacheKey(request).empty());
}
TEST_CASE(ai_suggestion_cache_misses_when_candidate_limit_changes)
{
const auto request = MakeRequest();
auto changed_request = request;
changed_request.config.candidate_limit = 1;
REQUIRE(AiAssistant::detail::BuildSuggestionCacheKey(request) !=
AiAssistant::detail::BuildSuggestionCacheKey(changed_request));
}