Add user_message on AIRequest, ResponseKind/image bytes on AIResponse, and image_prompt so clients can persist image context in chat history. Co-authored-by: Cursor <cursoragent@cursor.com>
313 lines
18 KiB
Protocol Buffer
313 lines
18 KiB
Protocol Buffer
syntax = "proto3";
|
||
|
||
package ai;
|
||
|
||
option go_package = "git.be.kaien.ru/l-ai/l-ai-core-proto/proto";
|
||
|
||
// AIRequest содержит запрос к AI модели
|
||
message AIRequest {
|
||
string text = 1; // Текст запроса
|
||
string bot_username = 2; // Username бота для упоминаний
|
||
string model_name = 3; // Название модели (по умолчанию "deepseek-v4-flash")
|
||
bool enable_reasoning = 4; // Включить режим рассуждений (по умолчанию false)
|
||
int64 global_user_id = 5; // Global user ID для получения доп. данных (профиль, воспоминания)
|
||
AnswerGenerationOptions options = 6; // Дополнительные опции генерации ответа
|
||
int64 telegram_user_id = 7; // Telegram user ID для применения пользовательских настроек (стиль ответа)
|
||
int64 steam_user_id = 8; // SteamID64 отправителя для живых Steam-диалогов (l-ai-steam) - тот же "сигнал живого чата", что telegram_user_id, для второй платформы
|
||
bool supports_deferred = 9; // Клиент умеет обрабатывать status=PENDING + GetAnswerJob (async тяжёлые тулзы). Старые клиенты не ставят — тяжёлые тулзы исполняются синхронно, как раньше.
|
||
// Структурированные воспоминания, которые клиент уже вложил в text (для debug-trace).
|
||
// Core сохраняет их as-is на ai_response_traces — не перезапрашивает memories-сервис.
|
||
repeated UsedMemory used_memories = 10;
|
||
// Raw user message for intent classification (no history/memories/tools).
|
||
// Live chat clients (Telegram/Steam) send this; when empty, image intent
|
||
// detection is skipped and the request is treated as normal chat.
|
||
string user_message = 11;
|
||
}
|
||
|
||
// UsedMemory — снимок одного воспоминания, участвовавшего в генерации ответа.
|
||
message UsedMemory {
|
||
int64 id = 1; // ID в l-ai-memories (для будущего delete с debug-страницы)
|
||
int64 user_id = 2;
|
||
string username = 3;
|
||
string key = 4;
|
||
string value = 5;
|
||
string category = 6;
|
||
string priority = 7;
|
||
string source_label = 8; // контекст от клиента, напр. "автор сообщения (@nick)"
|
||
}
|
||
|
||
// AnswerGenerationOptions содержит дополнительные опции для генерации ответа
|
||
message AnswerGenerationOptions {
|
||
bool include_user_memories = 1; // Включить воспоминания о пользователе в промпт
|
||
}
|
||
|
||
// ResponseKind — тип синхронного ответа GenerateAnswer (кроме deferred jobs).
|
||
enum ResponseKind {
|
||
RESPONSE_KIND_UNSPECIFIED = 0; // трактуется как TEXT (обратная совместимость)
|
||
RESPONSE_KIND_TEXT = 1; // content — текстовый ответ
|
||
RESPONSE_KIND_IMAGE = 2; // image_base64 — сгенерированное изображение
|
||
}
|
||
|
||
// AIResponse содержит ответ от AI модели
|
||
message AIResponse {
|
||
string content = 1; // Текст ответа (при status=PENDING — interim-сообщение)
|
||
string error = 2; // Текст ошибки, если произошла
|
||
double duration_ms = 3; // Время выполнения в миллисекундах
|
||
string reasoning_content = 4; // Текст рассуждений (если включён режим reasoning)
|
||
AnswerStatus status = 5; // COMPLETE (или UNSPECIFIED для старых клиентов) — content финальный;
|
||
// PENDING — запущена долгая job, content это interim, финал тянуть через GetAnswerJob(job_id)
|
||
string job_id = 6; // Идентификатор отложенной job (только при status=PENDING)
|
||
string response_id = 7; // UUID (hex) debug-trace; доступен уже при PENDING
|
||
ResponseKind kind = 8; // TEXT (default) или IMAGE
|
||
string image_base64 = 9; // base64-encoded image bytes when kind=IMAGE
|
||
string image_mime_type = 10; // e.g. image/png when kind=IMAGE
|
||
string image_prompt = 11; // final Muse Image prompt when kind=IMAGE (for client-side history)
|
||
}
|
||
|
||
// AnswerStatus описывает, готов ли ответ синхронно или отложен в job.
|
||
enum AnswerStatus {
|
||
ANSWER_STATUS_UNSPECIFIED = 0; // трактуется как COMPLETE (обратная совместимость)
|
||
ANSWER_STATUS_COMPLETE = 1; // content — финальный ответ
|
||
ANSWER_STATUS_PENDING = 2; // запущена долгая job; поллить GetAnswerJob(job_id)
|
||
}
|
||
|
||
// GetAnswerJobRequest запрашивает статус отложенной генерации ответа.
|
||
message GetAnswerJobRequest {
|
||
string job_id = 1;
|
||
}
|
||
|
||
// AnswerJobState — состояние отложенной генерации ответа.
|
||
// Значения 0-4 исторические (GenerateAnswer deferred). Значения 5-7 добавлены для
|
||
// durable RunAgent jobs; старые клиенты их не создают и потому не увидят. RUNNING
|
||
// для старого клиента семантически эквивалентен PENDING (job ещё не терминальна).
|
||
enum AnswerJobState {
|
||
ANSWER_JOB_STATE_UNSPECIFIED = 0;
|
||
ANSWER_JOB_STATE_PENDING = 1; // ещё не взята в работу / выполняется (evolver работает, модель дописывает)
|
||
ANSWER_JOB_STATE_DONE = 2; // готово, content заполнен
|
||
ANSWER_JOB_STATE_FAILED = 3; // ошибка, error заполнен
|
||
ANSWER_JOB_STATE_NOT_FOUND = 4; // неизвестный/протухший job_id (напр. после рестарта core)
|
||
ANSWER_JOB_STATE_RUNNING = 5; // взята воркером (lease активен), tool loop исполняется
|
||
ANSWER_JOB_STATE_CANCELLED = 6; // отменена через CancelAnswerJob
|
||
ANSWER_JOB_STATE_EXPIRED = 7; // истёк expiration до достижения терминального состояния
|
||
}
|
||
|
||
// AnswerJobResponse — результат опроса отложенной генерации.
|
||
message AnswerJobResponse {
|
||
AnswerJobState state = 1;
|
||
string content = 2; // финальный ответ, когда DONE
|
||
string error = 3; // причина, когда FAILED
|
||
double duration_ms = 4; // суммарное время генерации, когда DONE
|
||
// Authoritative usage завершённой deferred RunAgent job — тот же счётчик, что
|
||
// возвращает synchronous RunAgent (реальные iterations/tool calls core, не
|
||
// self-reported из ответа модели). Для legacy GenerateAnswer jobs не заполняется.
|
||
AgentUsage agent_usage = 5;
|
||
string response_id = 6; // тот же debug-trace id, что в AIResponse при PENDING
|
||
}
|
||
|
||
// ToolCallTrace — один вызов тула во время GenerateAnswer (args + результат для модели).
|
||
message ToolCallTrace {
|
||
string call_id = 1;
|
||
string name = 2;
|
||
string arguments_json = 3;
|
||
string result = 4;
|
||
string error = 5;
|
||
bool deferred = 6; // тяжёлый тул через l-evolver
|
||
}
|
||
|
||
// GetAIResponseTraceRequest — запрос debug-снимка генерации по UUID.
|
||
message GetAIResponseTraceRequest {
|
||
string id = 1; // response_id (hex UUID без дефисов)
|
||
}
|
||
|
||
// GetAIResponseTraceResponse — снимок того, что ушло в модель / вернулось.
|
||
message GetAIResponseTraceResponse {
|
||
string id = 1;
|
||
string bot_username = 2;
|
||
string model_name = 3;
|
||
int64 telegram_user_id = 4;
|
||
int64 global_user_id = 5;
|
||
string system_prompt = 6;
|
||
string user_prompt = 7;
|
||
repeated UsedMemory memories = 8;
|
||
repeated ToolCallTrace tool_calls = 9;
|
||
string response = 10;
|
||
string reasoning_content = 11;
|
||
string error = 12;
|
||
double duration_ms = 13;
|
||
int64 created_at_unix = 14;
|
||
bool found = 15; // false — нет такого id / истёк TTL
|
||
}
|
||
|
||
// CreateSessionRequest для создания новой AI сессии
|
||
message CreateSessionRequest {
|
||
string user_id = 1; // Идентификатор пользователя
|
||
string bot_username = 2; // Username бота для упоминаний
|
||
string model_name = 3; // Название модели (по умолчанию "deepseek-v4-flash")
|
||
int64 ttl_seconds = 4; // Время жизни сессии в секундах (по умолчанию 3600 = 1 час)
|
||
}
|
||
|
||
// CreateSessionResponse содержит ID созданной сессии
|
||
message CreateSessionResponse {
|
||
string session_id = 1; // ID сессии
|
||
string status = 2; // Статус сессии
|
||
string error = 3; // Текст ошибки, если произошла
|
||
}
|
||
|
||
// AskInSessionRequest для запроса в рамках сессии
|
||
message AskInSessionRequest {
|
||
string session_id = 1; // ID сессии
|
||
string text = 2; // Текст запроса
|
||
bool enable_reasoning = 3; // Включить режим рассуждений (по умолчанию false)
|
||
int64 global_user_id = 4; // Global user ID для получения воспоминаний
|
||
AnswerGenerationOptions options = 5; // Дополнительные опции генерации
|
||
}
|
||
|
||
// SessionStatusResponse содержит статус сессии и ответ AI
|
||
message SessionStatusResponse {
|
||
string session_id = 1; // ID сессии
|
||
string status = 2; // Статус сессии (active, expired, closed)
|
||
string content = 3; // Текст ответа от AI
|
||
string error = 4; // Текст ошибки, если произошла
|
||
double duration_ms = 5; // Время выполнения в миллисекундах
|
||
string reasoning_content = 6; // Текст рассуждений (если включён режим reasoning)
|
||
}
|
||
|
||
// StatelessMessage одно сообщение для stateless запроса (role + content)
|
||
message StatelessMessage {
|
||
string role = 1; // "system", "user", "assistant"
|
||
string content = 2; // Текст сообщения
|
||
}
|
||
|
||
// StatelessRequest для stateless генерации с полным контролем над messages
|
||
message StatelessRequest {
|
||
repeated StatelessMessage messages = 1; // Массив сообщений (system + history + user)
|
||
string model_name = 2; // Название модели (по умолчанию "deepseek-v4-flash")
|
||
}
|
||
|
||
// HealthCheckRequest для проверки здоровья сервиса
|
||
message HealthCheckRequest {}
|
||
|
||
// HealthCheckResponse для ответа на проверку здоровья
|
||
message HealthCheckResponse {
|
||
bool serving = 1;
|
||
string version = 2;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// RunAgent: надёжный агентный вызов (server-side tool filtering, deferred jobs).
|
||
// В отличие от GenerateAnswer, НЕ подмешивает Telegram/Steam-персонализацию и не
|
||
// требует telegram_user_id/steam_user_id. Вызывающая сторона полностью владеет
|
||
// массивом messages (включая system). Требует service-to-service авторизации
|
||
// (Bearer service-token в gRPC metadata) — agent_id/tool_profile сами по себе не
|
||
// являются авторизацией.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// AgentToolLoopOptions задаёт лимиты tool loop. Значения клиента могут только
|
||
// сузить серверные потолки (max_tool_iterations/max_total_tool_calls ≤ server max).
|
||
message AgentToolLoopOptions {
|
||
bool tools_enabled = 1; // false — модель отвечает без tools
|
||
uint32 max_tool_iterations = 2; // потолок round-trip'ов модель↔tools (0 = серверный default)
|
||
uint32 max_total_tool_calls = 3; // потолок РЕАЛЬНЫХ tool call'ов, не итераций (0 = серверный default)
|
||
uint32 tool_timeout_seconds = 4; // таймаут одного tool call (0 = серверный default)
|
||
uint32 total_timeout_seconds = 5; // wall-clock таймаут всего вызова (0 = серверный default), независим от budget
|
||
}
|
||
|
||
// AgentOutputFormat — требуемый формат финального ответа.
|
||
enum AgentOutputFormat {
|
||
AGENT_OUTPUT_FORMAT_UNSPECIFIED = 0; // трактуется как TEXT
|
||
AGENT_OUTPUT_FORMAT_TEXT = 1; // произвольный текст
|
||
AGENT_OUTPUT_FORMAT_JSON = 2; // должен быть валидный JSON (без schema-проверки)
|
||
AGENT_OUTPUT_FORMAT_JSON_SCHEMA = 3; // валидный JSON, проходящий json_schema
|
||
}
|
||
|
||
// AgentResponseContract описывает требования к структурированному ответу.
|
||
message AgentResponseContract {
|
||
AgentOutputFormat format = 1;
|
||
string json_schema = 2; // JSON Schema (draft-07), обязателен при format=JSON_SCHEMA
|
||
bool repair_invalid_output = 3; // при провале валидации — один repair-запрос модели (без новых tools)
|
||
}
|
||
|
||
// AgentRequest — запрос агентного вызова.
|
||
message AgentRequest {
|
||
repeated StatelessMessage messages = 1; // полный контекст, включая system
|
||
string model_name = 2; // модель (по умолчанию deepseek-v4-flash)
|
||
bool enable_reasoning = 3; // reasoning не попадает в обычный content
|
||
|
||
string agent_id = 4; // идентификатор логического агента (для логов/метрик, НЕ авторизация)
|
||
string tool_profile = 5; // серверный профиль tools (НЕ авторизация; проверяется против credential)
|
||
repeated string requested_tools = 6; // сужение профиля: effective = profile.allowed ∩ requested; пусто = все allowed
|
||
|
||
AgentToolLoopOptions tool_loop = 7;
|
||
AgentResponseContract response_contract = 8;
|
||
|
||
string request_id = 9; // сквозной идентификатор для трассировки
|
||
string idempotency_key = 10; // повторный RunAgent с тем же ключом не создаёт дубликат job
|
||
bool supports_deferred = 11; // клиент умеет status=PENDING + GetAnswerJob для тяжёлых операций
|
||
|
||
map<string, string> metadata = 12; // произвольные метки (не логируются как секреты)
|
||
}
|
||
|
||
// AgentUsage — счётчики использования за вызов.
|
||
message AgentUsage {
|
||
uint32 tool_iterations = 1; // сколько round-trip'ов модель↔tools произошло
|
||
uint32 total_tool_calls = 2; // сколько РЕАЛЬНЫХ tool call'ов исполнено (3 calls в одной итерации = 3)
|
||
repeated string tools_used = 3;
|
||
uint64 input_tokens = 4;
|
||
uint64 output_tokens = 5;
|
||
}
|
||
|
||
// AgentResponse — результат агентного вызова.
|
||
message AgentResponse {
|
||
AnswerStatus status = 1; // COMPLETE — content финальный; PENDING — тянуть через GetAnswerJob(job_id)
|
||
string content = 2;
|
||
string error = 3;
|
||
double duration_ms = 4;
|
||
string job_id = 5; // при status=PENDING
|
||
AgentUsage usage = 6;
|
||
}
|
||
|
||
// CancelAnswerJobRequest — запрос отмены отложенной job (идемпотентный).
|
||
message CancelAnswerJobRequest {
|
||
string job_id = 1;
|
||
string reason = 2; // причина отмены (сохраняется), опционально
|
||
}
|
||
|
||
// CancelAnswerJobResponse — результат отмены.
|
||
message CancelAnswerJobResponse {
|
||
AnswerJobState state = 1; // состояние job после запроса (CANCELLED, либо терминальное, если уже завершилась)
|
||
bool cancelled = 2; // true — отмена применена этим вызовом; false — job уже была терминальна/отменена
|
||
string error = 3;
|
||
}
|
||
|
||
// AIService - основной сервис для работы с AI
|
||
service AIService {
|
||
// RunAgent — надёжный агентный вызов с серверной фильтрацией tools и deferred
|
||
// jobs. Требует service-to-service авторизации.
|
||
rpc RunAgent(AgentRequest) returns (AgentResponse);
|
||
|
||
// CancelAnswerJob отменяет отложенную job (идемпотентно). Требует авторизации.
|
||
rpc CancelAnswerJob(CancelAnswerJobRequest) returns (CancelAnswerJobResponse);
|
||
|
||
// GenerateAnswer генерирует ответ на запрос
|
||
rpc GenerateAnswer(AIRequest) returns (AIResponse);
|
||
|
||
// GenerateStateless генерирует ответ из массива messages (system+history+user)
|
||
rpc GenerateStateless(StatelessRequest) returns (AIResponse);
|
||
|
||
// HealthCheck проверяет здоровье сервиса
|
||
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
|
||
|
||
// CreateSession созда новую AI сессию
|
||
rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse);
|
||
|
||
// AskInSession отправляет запрос в рамках существующей сессии
|
||
rpc AskInSession(AskInSessionRequest) returns (SessionStatusResponse);
|
||
|
||
// GetAnswerJob возвращает статус отложенной генерации ответа (когда GenerateAnswer
|
||
// вернул status=PENDING). Клиент поллит его до DONE/FAILED и постит финал.
|
||
rpc GetAnswerJob(GetAnswerJobRequest) returns (AnswerJobResponse);
|
||
|
||
// GetAIResponseTrace возвращает debug-снимок генерации (промпты, memories, tool calls).
|
||
rpc GetAIResponseTrace(GetAIResponseTraceRequest) returns (GetAIResponseTraceResponse);
|
||
}
|