Files
leocrm/app/plugins/builtins/ai_assistant/schemas.py
T
Agent Zero b231c2d0d3
Check Cross-Plugin Imports / check (push) Has been cancelled
feat(B-SENS): Sensitive Data Boundary + AI/Data Exposure Policy + AIProvider Compliance
B-SENS: app/core/sensitive_data.py (NEU) — zentrale Sensitive-Field-Verwaltung
- SENSITIVE_FIELDS dict für contact/user/mail_account/system_settings
- is_sensitive(), sanitize_dict(), register_sensitive_fields()
- Integration: errors.py (Log-Redaction), audit.py (Audit-Masking), export_service.py (Export-Filter), embedding.py (Index-Filter)

B-DATA-POL: AI/Data Exposure Policy
- DATA_EXPOSURE_POLICY: pro Entity+Field welche Systeme erlaubt (llm_context/search/embeddings/rag/agent_memory/export)
- filter_for_llm_context/search/embeddings/export/rag/agent_memory()

B-AIPROV-COMP: AIProvider Compliance Metadata
- Migration 0119: 7 neue Spalten an ai_providers (region, hosting_type, dpa_status, retention_policy, training_on_customer_data, transfer_notice, allowed_data_classes)
- llm_client.py: get_provider_compliance() + check_data_class_allowed()

B-PRIV-TEST: 76 Tests in test_sensitive_data.py — alle grün
- Sensitive Fields, Exposure Policy, Provider Compliance, Secrets-always-blocked
- Keine Regression: 39 LLM-Client Tests grün
2026-08-13 20:39:32 +02:00

299 lines
8.8 KiB
Python

"""Pydantic schemas for the AI Assistant plugin."""
from __future__ import annotations
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
# ─── Providers ───
class AIProviderCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
provider_type: str = Field(..., min_length=1, max_length=50)
api_key: str = Field(default="", max_length=2000)
base_url: str = Field(default="", max_length=500)
is_active: bool = True
is_default: bool = False
config: dict[str, Any] = Field(default_factory=dict)
# Compliance metadata (B-AIPROV-COMP)
region: str = Field(default="unknown", max_length=20)
hosting_type: str = Field(default="cloud", max_length=30)
dpa_status: str = Field(default="none", max_length=20)
retention_policy: str = Field(default="")
training_on_customer_data: bool = False
transfer_notice: str = Field(default="")
allowed_data_classes: list[str] = Field(default_factory=list)
class AIProviderUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=100)
provider_type: str | None = Field(None, min_length=1, max_length=50)
api_key: str | None = Field(None, max_length=2000)
base_url: str | None = Field(None, max_length=500)
is_active: bool | None = None
is_default: bool | None = None
config: dict[str, Any] | None = None
# Compliance metadata (B-AIPROV-COMP)
region: str | None = Field(None, max_length=20)
hosting_type: str | None = Field(None, max_length=30)
dpa_status: str | None = Field(None, max_length=20)
retention_policy: str | None = None
training_on_customer_data: bool | None = None
transfer_notice: str | None = None
allowed_data_classes: list[str] | None = None
class AIProviderResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
name: str
provider_type: str
api_key: str # masked in routes
base_url: str
is_active: bool
is_default: bool
config: dict[str, Any]
# Compliance metadata (B-AIPROV-COMP)
region: str = "unknown"
hosting_type: str = "cloud"
dpa_status: str = "none"
retention_policy: str = ""
training_on_customer_data: bool = False
transfer_notice: str = ""
allowed_data_classes: list[str] = Field(default_factory=list)
created_at: datetime | None = None
updated_at: datetime | None = None
# ─── Models ───
class AIModelCreate(BaseModel):
provider_id: str
model_id: str = Field(..., min_length=1, max_length=200)
display_name: str = Field(..., min_length=1, max_length=200)
context_window: int = Field(default=4096, ge=1)
supports_tools: bool = False
supports_streaming: bool = True
is_active: bool = True
config: dict[str, Any] = Field(default_factory=dict)
class AIModelUpdate(BaseModel):
model_id: str | None = Field(None, min_length=1, max_length=200)
display_name: str | None = Field(None, min_length=1, max_length=200)
context_window: int | None = Field(None, ge=1)
supports_tools: bool | None = None
supports_streaming: bool | None = None
is_active: bool | None = None
config: dict[str, Any] | None = None
class AIModelResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
provider_id: str
model_id: str
display_name: str
context_window: int
supports_tools: bool
supports_streaming: bool
is_active: bool
config: dict[str, Any]
# ─── Presets ───
class AIPresetCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
model_id: str = Field(..., min_length=1, max_length=200)
provider_id: str | None = None
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
max_tokens: int = Field(default=2048, ge=1, le=128000)
top_p: float = Field(default=1.0, ge=0.0, le=1.0)
system_prompt: str = ""
config: dict[str, Any] = Field(default_factory=dict)
is_active: bool = True
class AIPresetUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=100)
model_id: str | None = Field(None, min_length=1, max_length=200)
provider_id: str | None = None
temperature: float | None = Field(None, ge=0.0, le=2.0)
max_tokens: int | None = Field(None, ge=1, le=128000)
top_p: float | None = Field(None, ge=0.0, le=1.0)
system_prompt: str | None = None
config: dict[str, Any] | None = None
is_active: bool | None = None
class AIPresetResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
name: str
model_id: str
provider_id: str | None = None
temperature: float
max_tokens: int
top_p: float
system_prompt: str
config: dict[str, Any]
is_active: bool
# ─── Agents ───
class AIAgentCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
description: str = ""
system_prompt: str = ""
preset_id: str | None = None
tool_ids: list[str] = Field(default_factory=list)
is_active: bool = True
config: dict[str, Any] = Field(default_factory=dict)
class AIAgentUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=100)
description: str | None = None
system_prompt: str | None = None
preset_id: str | None = None
tool_ids: list[str] | None = None
is_active: bool | None = None
config: dict[str, Any] | None = None
class AIAgentResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
name: str
description: str
system_prompt: str
preset_id: str | None = None
tool_ids: list[str]
is_default: bool
is_active: bool
config: dict[str, Any]
created_at: datetime | None = None
updated_at: datetime | None = None
# ─── Chat Sessions ───
class ChatSessionCreate(BaseModel):
title: str = Field(default="Neuer Chat", max_length=255)
agent_id: str | None = None
is_sidebar: bool = False
folder_id: str | None = None
class ChatSessionUpdate(BaseModel):
title: str | None = Field(None, max_length=255)
is_pinned: bool | None = None
agent_id: str | None = None
folder_id: str | None = None
sort_order: int | None = None
class ChatSessionResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
user_id: str
agent_id: str | None = None
title: str
is_pinned: bool
is_sidebar: bool
folder_id: str | None = None
sort_order: int = 0
created_at: datetime | None = None
updated_at: datetime | None = None
# ─── Chat Messages ───
class ChatMessageResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
session_id: str
role: str
content: str
tool_calls: list[dict[str, Any]] | None = None
tool_results: list[dict[str, Any]] | None = None
tokens: int
model_used: str
created_at: datetime | None = None
class ChatSendRequest(BaseModel):
content: str = Field(..., min_length=1)
agent_id: str | None = None # override session agent
# ─── Folders ───
class ChatFolderCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=255)
parent_id: str | None = None
class ChatFolderUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=255)
parent_id: str | None = None
sort_order: int | None = None
class ChatFolderResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
name: str
parent_id: str | None = None
user_id: str
sort_order: int = 0
created_at: datetime | None = None
# ─── Attachments ───
class ChatAttachmentResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
message_id: str | None = None
session_id: str
filename: str
mime_type: str
size_bytes: int
# ─── Tools ───
class AIToolResponse(BaseModel):
name: str
description: str
parameters: dict[str, Any]
plugin_name: str
required_permission: str | None = None
category: str
# ─── External Agent API Schemas (Phase 5.9) ───
class ExternalAgentRequest(BaseModel):
"""Request to run an AI agent from an external system."""
message: str = Field(..., min_length=1, description="The message/input for the agent")
context: dict[str, Any] = Field(default_factory=dict, description="Optional context data")
stream: bool = Field(default=False, description="Whether to stream the response")
class ExternalAgentResponse(BaseModel):
"""Response from an external agent execution."""
response: str = Field(default="", description="The agent's response text")
agent_id: str = Field(default="", description="ID of the agent that responded")
session_id: str = Field(default="", description="Chat session ID")
tokens_used: int = Field(default=0, description="Approximate token count")