diff --git a/README.md b/README.md index 56bd10e..50544dd 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - HTTP API на FastAPI; - Bearer JWT с проверкой подписи, issuer, audience и срока жизни; -- контракт `portable-agent/contracts` версии `2.1.0`; +- контракт `portable-agent/contracts` версии `3.0.0`; - простой MVC-подобный каркас; - внешний порт `IntentModel` и локальная `DemoIntentModel` для разработки без AI-сервиса; - результат с `proposal` или `clarification` для первого действия `calendar.create_event`; @@ -82,8 +82,9 @@ uv run mkdocs build --strict ## Первый сценарий Сервис принимает только `calendar.create_event`. Для готового предложения нужны `title`, `startAt`, -`endAt` и `timeZone`; исполнитель первой версии называется `fake-calendar`. Если полей не хватает, -ответ содержит `clarification`, а `proposal` остаётся `null`. Готовое предложение всегда содержит +`endAt` и `timeZone`. Доступны `fake-calendar` и `google-calendar`; demo-модель выбирает Google, если +клиент передал его в `availableConnectors`, иначе использует fake. Если полей не хватает, ответ +содержит `clarification`, а `proposal` остаётся `null`. Готовое предложение всегда содержит `requiresApproval: true`. Внешний запрос использует поля `text`, `timeZone` и `availableConnectors`. Проверенная копия схемы diff --git a/contracts/agent-runtime-api.yaml b/contracts/agent-runtime-api.yaml index 678bc21..44619f0 100644 --- a/contracts/agent-runtime-api.yaml +++ b/contracts/agent-runtime-api.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: Portable Agent Runtime API - version: 2.1.0 + version: 3.0.0 description: Подготовка предложения действия из текста пользователя. license: name: Apache-2.0 @@ -77,7 +77,7 @@ components: uniqueItems: true items: type: string - enum: [fake-calendar] + enum: [fake-calendar, google-calendar] default: [] ProposalResponse: type: object @@ -112,7 +112,7 @@ components: enum: [calendar.create_event] connector: type: string - enum: [fake-calendar] + enum: [fake-calendar, google-calendar] payload: $ref: "#/components/schemas/CalendarCreateEventPayload" explanation: diff --git a/docs/architecture.md b/docs/architecture.md index 04c4dfe..be8e96f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -66,6 +66,6 @@ config собирает реализации; main подключает controll ## Внешний контракт -HTTP API следует `portable-agent/contracts` версии `2.1.0`: `text`, `timeZone`, +HTTP API следует `portable-agent/contracts` версии `3.0.0`: `text`, `timeZone`, `availableConnectors`, `proposalId`, `requiresApproval` и `missingFields`. Копия релизной схемы лежит в `contracts/`; contract-тест проверяет по ней настоящий запрос и ответ. diff --git a/docs/development.md b/docs/development.md index 561245b..3410fe7 100644 --- a/docs/development.md +++ b/docs/development.md @@ -43,7 +43,7 @@ uv run mkdocs build --strict При обновлении общего API сначала выпусти релиз в `portable-agent/contracts`, затем выполни: ```powershell -.\scripts\update-contract.ps1 -Version 2.1.0 +.\scripts\update-contract.ps1 -Version 3.0.0 ``` Ручное копирование схемы запрещено: команда проверяет checksum и GitHub attestation. diff --git a/docs/index.md b/docs/index.md index d1f12ce..68d5d9b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,12 +32,12 @@ Completions API и проверяет JSON-ответ через внутрен ## Текущий продуктовый срез -Разрешено только действие `calendar.create_event` через `fake-calendar`. Обязательны название, -начало, конец и часовой пояс. Результат содержит либо `proposal`, либо `clarification`; обе части -могут быть `null`, если модель не нашла действие или коннектор недоступен. Создание встречи всегда -требует явного подтверждения в `action-service`. +Разрешено только действие `calendar.create_event` через `fake-calendar` или `google-calendar`. +Обязательны название, начало, конец и часовой пояс. Результат содержит либо `proposal`, либо +`clarification`; обе части могут быть `null`, если модель не нашла действие или коннектор недоступен. +Создание встречи всегда требует явного подтверждения в `action-service`. -Публичный API соответствует `portable-agent/contracts` версии `2.1.0`. Поля называются `text`, +Публичный API соответствует `portable-agent/contracts` версии `3.0.0`. Поля называются `text`, `timeZone`, `availableConnectors`, `proposalId`, `requiresApproval` и `missingFields`. Перед созданием предложения сервис проверяет payload: типы и длину полей, формат времени и часового diff --git a/src/portable_agent/models/proposal.py b/src/portable_agent/models/proposal.py index 84b12c8..d6258ad 100644 --- a/src/portable_agent/models/proposal.py +++ b/src/portable_agent/models/proposal.py @@ -19,6 +19,9 @@ class ModelReply(BaseModel): explanation: str = Field(min_length=1, max_length=500) +CalendarConnector = Literal["fake-calendar", "google-calendar"] + + Email = Annotated[ str, Field(max_length=254, pattern=r"^[^@\s]+@[^@\s]+\.[^@\s]+$"), @@ -67,7 +70,7 @@ class ActionPlan(BaseModel): proposal_id: UUID = Field(default_factory=uuid4, alias="proposalId") kind: Literal["calendar.create_event"] - connector: Literal["fake-calendar"] + connector: CalendarConnector payload: dict[str, Any] explanation: str risk: Risk diff --git a/src/portable_agent/repositories/model_repository.py b/src/portable_agent/repositories/model_repository.py index 20bfb48..10b3be2 100644 --- a/src/portable_agent/repositories/model_repository.py +++ b/src/portable_agent/repositories/model_repository.py @@ -18,6 +18,17 @@ async def propose(self, text: str, context: UserContext) -> ModelReply | None: if "встреч" not in normalized_text and "календар" not in normalized_text: return None + connector = next( + ( + name + for name in ("google-calendar", "fake-calendar") + if name in context.available_tools + ), + None, + ) + if connector is None: + return None + match = re.fullmatch( # The Russian demo command intentionally uses a Cyrillic preposition. r'Создай встречу "(?P