T09: KI-Copilot API + Hybrid Workflow Engine + LLM client + event-triggered workflows

- KI-Copilot: NL query → proposed actions, execute with RBAC, history, audit logging
- LLM client: mock mode (no API key) + OpenAI-compatible mode (AI_MODEL/AI_API_KEY)
- Action mapper: NL intent → API calls (create/update/delete/search company/contact)
- Workflow engine: step types (action/approval/notification/condition), JSONB steps
- Workflow lifecycle: pending → in_progress → completed/rejected/cancelled
- Event-triggered workflows: event bus → auto-start instances
- Code-engine workflows: onboarding on user.created event
- Approval timeout: auto-reject after configured hours
- 5 new tenant-scoped tables with RLS: ai_conversations, ai_messages, workflows, workflow_instances, workflow_step_history
- Migration 0004: all tables + RLS policies + tenant_id + indexes
- 238 tests pass (30 AC + 105 coverage + 103 existing), 84.12% T09 module coverage
- MissingGreenlet fix: safe accessor helpers for async ORM attribute access
This commit is contained in:
leocrm-bot
2026-06-29 02:44:13 +02:00
parent 7a5a48fb4c
commit 14bd4e33fb
31 changed files with 5884 additions and 3 deletions
+10
View File
@@ -1,3 +1,13 @@
"""Pydantic schemas package."""
from app.schemas.plugin import PluginInfo, PluginListResponse, PluginActionResponse, PluginUninstallResponse
from app.schemas.ai_copilot import (
CopilotQueryRequest, CopilotAction, CopilotQueryResponse,
CopilotExecuteRequest, CopilotExecuteResponse,
CopilotHistoryResponse, CopilotMessageResponse,
)
from app.schemas.workflow import (
WorkflowCreate, WorkflowUpdate, WorkflowResponse, WorkflowListResponse,
InstanceCreate, InstanceResponse, InstanceDetailResponse, InstanceListResponse,
AdvanceRequest, StepHistoryResponse, WorkflowStep,
)
+62
View File
@@ -0,0 +1,62 @@
"""AI Copilot schemas — query, execute, history."""
from __future__ import annotations
from pydantic import BaseModel, Field
class CopilotQueryRequest(BaseModel):
"""Natural language query to the AI copilot."""
query: str = Field(..., min_length=1, max_length=2000)
conversation_id: str | None = None
context: dict = Field(default_factory=dict)
class CopilotAction(BaseModel):
"""A proposed API action derived from NL input."""
method: str = Field(..., pattern="^(GET|POST|PATCH|DELETE)$")
path: str = Field(..., min_length=1)
body: dict | None = None
description: str = ""
confidence: float = Field(0.0, ge=0.0, le=1.0)
class CopilotQueryResponse(BaseModel):
"""Response from copilot query — proposed actions for user confirmation."""
conversation_id: str
message: str
proposed_actions: list[CopilotAction] = Field(default_factory=list)
class CopilotExecuteRequest(BaseModel):
"""Execute a proposed action after user confirmation."""
conversation_id: str
action: CopilotAction
class CopilotExecuteResponse(BaseModel):
"""Result of executing a proposed action."""
conversation_id: str
success: bool
status_code: int
data: dict | list | None = None
error: str | None = None
class CopilotMessageResponse(BaseModel):
"""A single message in conversation history."""
id: str
role: str
content: str
proposed_actions: list[dict] | None = None
executed_action: dict | None = None
execution_result: dict | None = None
created_at: str | None = None
class CopilotHistoryResponse(BaseModel):
"""Paginated conversation history."""
items: list[CopilotMessageResponse]
total: int
page: int
page_size: int
+105
View File
@@ -0,0 +1,105 @@
"""Workflow schemas — create, update, read, instance lifecycle, step history."""
from __future__ import annotations
from pydantic import BaseModel, Field
class WorkflowStep(BaseModel):
"""A single step in a workflow definition."""
name: str = Field(..., min_length=1, max_length=200)
type: str = Field(..., pattern="^(action|approval|notification|condition)$")
config: dict = Field(default_factory=dict)
description: str | None = None
class WorkflowCreate(BaseModel):
"""Create a new workflow definition."""
name: str = Field(..., min_length=1, max_length=200)
description: str | None = None
trigger_event: str | None = None
steps: list[WorkflowStep] = Field(..., min_length=1)
is_active: bool = True
class WorkflowUpdate(BaseModel):
"""Update an existing workflow definition."""
name: str | None = Field(None, max_length=200)
description: str | None = None
trigger_event: str | None = None
steps: list[WorkflowStep] | None = None
is_active: bool | None = None
class WorkflowResponse(BaseModel):
"""Workflow definition response."""
id: str
name: str
description: str | None = None
trigger_event: str | None = None
steps: list[dict]
is_active: bool
created_by: str | None = None
created_at: str | None = None
updated_at: str | None = None
class WorkflowListResponse(BaseModel):
"""Paginated workflow list."""
items: list[WorkflowResponse]
total: int
page: int
page_size: int
class InstanceCreate(BaseModel):
"""Create a new workflow instance."""
context: dict = Field(default_factory=dict)
timeout_hours: int | None = None
class InstanceResponse(BaseModel):
"""Workflow instance response with current state and history."""
id: str
workflow_id: str
status: str
current_step_index: int
context: dict
initiated_by: str | None = None
completed_at: str | None = None
timeout_hours: int | None = None
timeout_at: str | None = None
created_at: str | None = None
updated_at: str | None = None
class InstanceDetailResponse(InstanceResponse):
"""Instance with step history."""
history: list[dict] = Field(default_factory=list)
workflow_name: str | None = None
class InstanceListResponse(BaseModel):
"""Paginated instance list."""
items: list[InstanceResponse]
total: int
page: int
page_size: int
class AdvanceRequest(BaseModel):
"""Advance or reject a workflow instance step."""
decision: str = Field(..., pattern="^(approve|reject)$")
comment: str | None = None
class StepHistoryResponse(BaseModel):
"""Step history entry."""
id: str
instance_id: str
step_index: int
step_type: str
action: str
actor_id: str | None = None
details: dict | None = None
created_at: str | None = None