mcp: document handlers and route embed_text via libKI (OllamaProvider) with synchronous QFuture wait; ki: respect OLLAMA_BASE for tags/generate/embeddings
This commit is contained in:
parent
50ecdcf56b
commit
6ff51da0e2
|
|
@ -33,9 +33,15 @@ KICapabilities* OllamaProvider::caps() const
|
|||
return m_caps;
|
||||
}
|
||||
|
||||
static QString ollamaBaseUrl() {
|
||||
const QByteArray env = qgetenv("OLLAMA_BASE");
|
||||
if (!env.isEmpty()) return QString::fromLocal8Bit(env);
|
||||
return QStringLiteral("http://localhost:11434");
|
||||
}
|
||||
|
||||
void OllamaProvider::reload()
|
||||
{
|
||||
QNetworkRequest req{QUrl(QStringLiteral("http://localhost:11434/api/tags"))};
|
||||
QNetworkRequest req{QUrl(ollamaBaseUrl() + QStringLiteral("/api/tags"))};
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
auto rep = m_manager->get(req);
|
||||
connect(rep, &QNetworkReply::finished, this, [this, rep] {
|
||||
|
|
@ -56,7 +62,7 @@ void OllamaProvider::reload()
|
|||
|
||||
QFuture<KIReply*> OllamaProvider::chat(const KIThread& thread, const KIChatOptions& opts)
|
||||
{
|
||||
QNetworkRequest req{QUrl(QStringLiteral("http://localhost:11434/api/generate"))};
|
||||
QNetworkRequest req{QUrl(ollamaBaseUrl() + QStringLiteral("/api/generate"))};
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
|
||||
QJsonObject data;
|
||||
|
|
@ -118,7 +124,7 @@ QFuture<KIEmbeddingResult> OllamaProvider::embed(const QStringList& texts, const
|
|||
acc->vectors.resize(texts.size());
|
||||
acc->remaining = texts.size();
|
||||
|
||||
const QUrl url(QStringLiteral("http://localhost:11434/api/embeddings"));
|
||||
const QUrl url(ollamaBaseUrl() + QStringLiteral("/api/embeddings"));
|
||||
for (int i = 0; i < texts.size(); ++i) {
|
||||
QNetworkRequest req{url};
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
|
|
|
|||
|
|
@ -15,8 +15,20 @@
|
|||
#include <vector>
|
||||
|
||||
#include "PgDal.hpp"
|
||||
// libKI (central embedding + model provider)
|
||||
#include "Client/KIClient.h"
|
||||
#include "Provider/OllamaProvider.h"
|
||||
#include "Embedding/KIEmbedding.h"
|
||||
#include <QEventLoop>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
namespace Handlers {
|
||||
|
||||
/**
|
||||
* upsert_memory
|
||||
* Request: { "namespace": string, "items": [ { "id?": string, "text": string, "tags?": string[], "embedding?": number[] } ] }
|
||||
* Response: { "upserted": int, "ids?": string[], "status": "ok" }
|
||||
*/
|
||||
namespace detail {
|
||||
|
||||
inline ki::PgDal& database() {
|
||||
|
|
@ -443,6 +455,11 @@ inline std::string upsert_memory(const std::string& reqJson) {
|
|||
return os.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* search_memory
|
||||
* Request: { "namespace": string, "query": { "text?": string, "embedding?": number[], "k?": int } }
|
||||
* Response: { "matches": [ { "id": string, "score": number, "text?": string } ] }
|
||||
*/
|
||||
inline std::string search_memory(const std::string& reqJson) {
|
||||
const std::string nsName = detail::extract_string_field(reqJson, "namespace");
|
||||
if (nsName.empty()) {
|
||||
|
|
@ -500,6 +517,11 @@ inline std::string search_memory(const std::string& reqJson) {
|
|||
return detail::serialize_matches(matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* save_context
|
||||
* Request: { "namespace": string, "key?": string, "content": any, "tags?": string[], "ttl_seconds?": int }
|
||||
* Response: { "id": string, "created_at": iso8601 }
|
||||
*/
|
||||
inline std::string save_context(const std::string& reqJson) {
|
||||
const std::string nsName = detail::extract_string_field(reqJson, "namespace");
|
||||
if (nsName.empty()) {
|
||||
|
|
@ -570,6 +592,11 @@ inline std::string save_context(const std::string& reqJson) {
|
|||
return os.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* recall_context
|
||||
* Request: { "namespace": string, "key?": string, "tags?": string[], "limit?": int, "since?": iso8601 }
|
||||
* Response: { "items": [ { "id": string, "key?": string, "content": any, "tags": string[], "created_at": iso8601 } ] }
|
||||
*/
|
||||
inline std::string recall_context(const std::string& reqJson) {
|
||||
const std::string nsName = detail::extract_string_field(reqJson, "namespace");
|
||||
if (nsName.empty()) {
|
||||
|
|
@ -634,15 +661,65 @@ inline std::string recall_context(const std::string& reqJson) {
|
|||
return os.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* embed_text
|
||||
* Request: { "namespace": string, "model?": string, "texts": string[] }
|
||||
* Response: { "model": string, "vectors": number[][] }
|
||||
*
|
||||
* Implementation: delegates to libKI (OllamaProvider) for local embeddings.
|
||||
*/
|
||||
inline std::string embed_text(const std::string& reqJson) {
|
||||
const std::string nsName = detail::extract_string_field(reqJson, "namespace");
|
||||
if (nsName.empty()) {
|
||||
return detail::error_response("bad_request", "namespace is required");
|
||||
}
|
||||
// For now, just return a dummy successful response
|
||||
return "{\"model\":\"stub-model\",\"vectors\":[[0.1,0.2,0.3]]}";
|
||||
|
||||
// Parse inputs
|
||||
std::string model = detail::extract_string_field(reqJson, "model");
|
||||
auto texts = detail::parse_string_array(reqJson, "texts");
|
||||
if (texts.empty()) {
|
||||
return detail::error_response("bad_request", "texts must contain at least one string");
|
||||
}
|
||||
|
||||
// libKI: synchronous wait on QFuture
|
||||
KI::KIClient client;
|
||||
KI::OllamaProvider provider;
|
||||
client.setProvider(&provider);
|
||||
KI::KIEmbedOptions opts; if (!model.empty()) opts.model = QString::fromStdString(model);
|
||||
|
||||
QStringList qtexts; qtexts.reserve(static_cast<int>(texts.size()));
|
||||
for (const auto &t : texts) qtexts.push_back(QString::fromStdString(t));
|
||||
|
||||
QEventLoop loop;
|
||||
QFuture<KI::KIEmbeddingResult> fut = client.embed(qtexts, opts);
|
||||
QFutureWatcher<KI::KIEmbeddingResult> watcher;
|
||||
QObject::connect(&watcher, &QFutureWatcher<KI::KIEmbeddingResult>::finished, &loop, &QEventLoop::quit);
|
||||
watcher.setFuture(fut);
|
||||
loop.exec();
|
||||
const KI::KIEmbeddingResult result = watcher.result();
|
||||
|
||||
// Serialize
|
||||
std::ostringstream os;
|
||||
os << "{\"model\":\"" << detail::json_escape(result.model.toStdString()) << "\",\"vectors\":[";
|
||||
for (int i = 0; i < result.vectors.size(); ++i) {
|
||||
if (i) os << ',';
|
||||
os << '[';
|
||||
const auto &vec = result.vectors[i];
|
||||
for (int j = 0; j < vec.size(); ++j) {
|
||||
if (j) os << ',';
|
||||
os.setf(std::ios::fixed); os << std::setprecision(6) << vec[j];
|
||||
}
|
||||
os << ']';
|
||||
}
|
||||
os << "]}";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* warm_cache (stub)
|
||||
* Request: { "namespace": string }
|
||||
* Response: { "queued": int }
|
||||
*/
|
||||
inline std::string warm_cache(const std::string& reqJson) {
|
||||
const std::string nsName = detail::extract_string_field(reqJson, "namespace");
|
||||
if (nsName.empty()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue