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 тяжёлые тулзы). Старые клиенты не ставят — тяжёлые тулзы исполняются синхронно, как раньше. } // AnswerGenerationOptions содержит дополнительные опции для генерации ответа message AnswerGenerationOptions { bool include_user_memories = 1; // Включить воспоминания о пользователе в промпт } // 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) } // 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 } // 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 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); }