Add FastAPI backend with agent system
This commit is contained in:
2
backend/app/schemas/__init__.py
Normal file
2
backend/app/schemas/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# Schemas package - import directly from submodules
|
||||
# e.g.: from app.schemas.auth import UserCreate
|
||||
55
backend/app/schemas/agent.py
Normal file
55
backend/app/schemas/agent.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AgentCreate(BaseModel):
|
||||
name: str
|
||||
role: str
|
||||
description: str | None = None
|
||||
system_prompt: str
|
||||
|
||||
|
||||
class AgentOut(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
role: str
|
||||
description: str | None
|
||||
is_active: bool
|
||||
is_default: bool
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AgentMessageOut(BaseModel):
|
||||
id: str
|
||||
agent_id: str
|
||||
conversation_id: str
|
||||
role: str
|
||||
content: str
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AgentStats(BaseModel):
|
||||
agent_id: str
|
||||
call_count: int
|
||||
current_task: str | None
|
||||
status: str # active | idle | disabled
|
||||
|
||||
|
||||
class AgentConfigUpdate(BaseModel):
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
system_prompt: str | None = None
|
||||
enabled: bool | None = None
|
||||
|
||||
|
||||
class AgentConfigOut(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
role: str
|
||||
description: str | None
|
||||
system_prompt: str
|
||||
enabled: bool
|
||||
is_active: bool
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
26
backend/app/schemas/auth.py
Normal file
26
backend/app/schemas/auth.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
full_name: str | None = None
|
||||
|
||||
|
||||
class UserOut(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
full_name: str | None
|
||||
is_active: bool
|
||||
is_superuser: bool
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
sub: str
|
||||
45
backend/app/schemas/conversation.py
Normal file
45
backend/app/schemas/conversation.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class MessageCreate(BaseModel):
|
||||
content: str
|
||||
|
||||
|
||||
class MessageOut(BaseModel):
|
||||
id: str
|
||||
role: str
|
||||
content: str
|
||||
model: str | None
|
||||
tokens_used: int | None
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ConversationCreate(BaseModel):
|
||||
title: str | None = None
|
||||
|
||||
|
||||
class ConversationOut(BaseModel):
|
||||
id: str
|
||||
title: str | None
|
||||
message_count: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
message: str
|
||||
conversation_id: str | None = None
|
||||
agent_id: str | None = None
|
||||
file_ids: list[str] = [] # 新增
|
||||
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
conversation_id: str
|
||||
message_id: str
|
||||
content: str
|
||||
agent_name: str
|
||||
40
backend/app/schemas/document.py
Normal file
40
backend/app/schemas/document.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DocumentOut(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
filename: str
|
||||
file_type: str
|
||||
file_size: int
|
||||
summary: str | None
|
||||
chunk_count: int
|
||||
is_indexed: bool
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class DocumentChunkOut(BaseModel):
|
||||
id: str
|
||||
chunk_index: int
|
||||
content: str
|
||||
metadata_: str | None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class SearchRequest(BaseModel):
|
||||
query: str
|
||||
top_k: int = 5
|
||||
user_id: str
|
||||
|
||||
|
||||
class SearchResult(BaseModel):
|
||||
chunk_id: str
|
||||
document_id: str
|
||||
document_title: str
|
||||
content: str
|
||||
score: float
|
||||
metadata_: str | None
|
||||
39
backend/app/schemas/folder.py
Normal file
39
backend/app/schemas/folder.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
class FolderCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
parent_id: Optional[str] = None
|
||||
|
||||
@field_validator('name')
|
||||
@classmethod
|
||||
def validate_name(cls, v):
|
||||
forbidden = '/\\*?:'
|
||||
for c in forbidden:
|
||||
if c in v:
|
||||
raise ValueError(f'Folder name cannot contain: {forbidden}')
|
||||
return v
|
||||
|
||||
class FolderUpdate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
|
||||
class FolderOut(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
parent_id: Optional[str]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
class FolderTreeOut(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
parent_id: Optional[str]
|
||||
children: List["FolderTreeOut"] = []
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
# 递归模型需要 forward ref
|
||||
FolderTreeOut.model_rebuild()
|
||||
37
backend/app/schemas/forum.py
Normal file
37
backend/app/schemas/forum.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ForumPostCreate(BaseModel):
|
||||
title: str
|
||||
content: str
|
||||
category: str | None = "discussion"
|
||||
|
||||
|
||||
class ForumPostOut(BaseModel):
|
||||
id: str
|
||||
user_id: str
|
||||
title: str
|
||||
content: str
|
||||
category: str | None
|
||||
is_executed: bool
|
||||
execution_result: str | None
|
||||
reply_count: int
|
||||
created_at: str
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ForumReplyCreate(BaseModel):
|
||||
content: str
|
||||
|
||||
|
||||
class ForumReplyOut(BaseModel):
|
||||
id: str
|
||||
post_id: str
|
||||
user_id: str | None
|
||||
agent_id: str | None
|
||||
content: str
|
||||
is_ai_reply: bool
|
||||
created_at: str
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
66
backend/app/schemas/graph.py
Normal file
66
backend/app/schemas/graph.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class TagProperties(BaseModel):
|
||||
tag_path: str = Field(..., description="完整标签路径,如 '编程语言/Python/异步'")
|
||||
short_name: str = Field(..., description="显示名称,如 '异步'")
|
||||
level: int = Field(..., ge=1, description="层级深度,1为顶级")
|
||||
parent_path: str | None = Field(None, description="父路径,如 '编程语言/Python'")
|
||||
description: str | None = Field(None, description="AI生成的标签描述")
|
||||
color: str | None = Field(None, description="标签颜色,如 '#FF5733'")
|
||||
|
||||
|
||||
class KGNodeOut(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
entity_type: str
|
||||
description: str | None
|
||||
properties_: dict | None
|
||||
importance: float
|
||||
created_at: str
|
||||
# 新增:如果是 tag 节点,返回 tag 属性
|
||||
tag_properties: TagProperties | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
def model_post_init(self, __context):
|
||||
if self.entity_type == "tag" and self.properties_:
|
||||
self.tag_properties = TagProperties(**self.properties_)
|
||||
|
||||
|
||||
class KGEdgeOut(BaseModel):
|
||||
id: str
|
||||
source_id: str
|
||||
target_id: str
|
||||
relation_type: str
|
||||
weight: float
|
||||
properties_: dict | None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class GraphOut(BaseModel):
|
||||
nodes: list[KGNodeOut]
|
||||
edges: list[KGEdgeOut]
|
||||
|
||||
|
||||
class KGBuildRequest(BaseModel):
|
||||
user_id: str
|
||||
document_ids: list[str] | None = None # None = 全量重建
|
||||
|
||||
|
||||
class TagExtractRequest(BaseModel):
|
||||
content: str = Field(..., min_length=10)
|
||||
user_id: str
|
||||
|
||||
|
||||
class TagExtractResponse(BaseModel):
|
||||
tags: list[TagProperties]
|
||||
tag_count: int
|
||||
|
||||
|
||||
class RelatedContentRequest(BaseModel):
|
||||
tag_ids: list[str]
|
||||
user_id: str
|
||||
limit: int = 10
|
||||
58
backend/app/schemas/settings.py
Normal file
58
backend/app/schemas/settings.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Literal, List
|
||||
from app.schemas.auth import UserOut
|
||||
|
||||
# LLM Provider 类型
|
||||
LLMProviderType = Literal["openai", "claude", "ollama", "deepseek", "custom"]
|
||||
LLMType = Literal["chat", "vlm", "embedding", "rerank"]
|
||||
|
||||
|
||||
# 单个模型配置
|
||||
class LLMModelConfig(BaseModel):
|
||||
name: str = "" # 模型名称/别名,用于标识
|
||||
provider: LLMProviderType = "openai"
|
||||
model: str = ""
|
||||
base_url: str = ""
|
||||
api_key: str = ""
|
||||
enabled: bool = True # 是否启用
|
||||
|
||||
|
||||
# LLM 配置输入 - 每种类型支持多个模型
|
||||
class LLMConfigIn(BaseModel):
|
||||
chat: Optional[List[LLMModelConfig]] = []
|
||||
vlm: Optional[List[LLMModelConfig]] = []
|
||||
embedding: Optional[List[LLMModelConfig]] = []
|
||||
rerank: Optional[List[LLMModelConfig]] = []
|
||||
|
||||
|
||||
# 定时任务配置
|
||||
class SchedulerConfigIn(BaseModel):
|
||||
daily_plan_time: Optional[str] = "08:00"
|
||||
forum_scan_interval_minutes: Optional[int] = 30
|
||||
todo_ai_generate_time: Optional[str] = "08:00"
|
||||
enabled: Optional[bool] = True
|
||||
|
||||
|
||||
# 用户资料更新
|
||||
class ProfileUpdateIn(BaseModel):
|
||||
full_name: Optional[str] = Field(None, min_length=2, max_length=50)
|
||||
password: Optional[str] = Field(None, min_length=8)
|
||||
current_password: Optional[str] = None
|
||||
|
||||
|
||||
# 完整设置输出
|
||||
class SettingsOut(BaseModel):
|
||||
profile: UserOut
|
||||
llm_config: Optional[dict] = None
|
||||
scheduler_config: Optional[dict] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
# 测试 LLM 连接请求
|
||||
class LLMTestIn(BaseModel):
|
||||
type: LLMType
|
||||
provider: LLMProviderType
|
||||
model: str
|
||||
base_url: str
|
||||
api_key: str
|
||||
82
backend/app/schemas/stats.py
Normal file
82
backend/app/schemas/stats.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# ===== System Health =====
|
||||
class SystemHealth(BaseModel):
|
||||
uptime_seconds: int
|
||||
cpu_percent: float
|
||||
memory_used_mb: float
|
||||
memory_total_mb: float
|
||||
memory_percent: float
|
||||
disk_used_gb: float
|
||||
disk_total_gb: float
|
||||
disk_percent: float
|
||||
active_users_24h: int
|
||||
|
||||
|
||||
# ===== Daily Stats Base =====
|
||||
class DailyStatItem(BaseModel):
|
||||
date: str
|
||||
count: int
|
||||
|
||||
|
||||
class DailyTokenStatItem(BaseModel):
|
||||
date: str
|
||||
input_tokens: int
|
||||
output_tokens: int
|
||||
|
||||
|
||||
# ===== Conversation Stats =====
|
||||
class ConversationStats(BaseModel):
|
||||
daily_conversations: list[DailyStatItem]
|
||||
daily_messages: list[DailyStatItem]
|
||||
daily_input_tokens: list[DailyTokenStatItem]
|
||||
daily_output_tokens: list[DailyTokenStatItem]
|
||||
totals: dict
|
||||
|
||||
|
||||
# ===== Knowledge Stats =====
|
||||
class KnowledgeStats(BaseModel):
|
||||
daily_new_tags: list[DailyStatItem]
|
||||
daily_documents: list[DailyStatItem]
|
||||
daily_knowledge_queries: list[DailyStatItem]
|
||||
daily_tag_relations: list[DailyStatItem]
|
||||
totals: dict
|
||||
|
||||
|
||||
# ===== Kanban Stats =====
|
||||
class KanbanStats(BaseModel):
|
||||
daily_new_tasks: list[DailyStatItem]
|
||||
daily_completed_tasks: list[DailyStatItem]
|
||||
daily_completion_rate: list[DailyStatItem]
|
||||
current_pending_tasks: int
|
||||
totals: dict
|
||||
|
||||
|
||||
# ===== Community Stats =====
|
||||
class CommunityStats(BaseModel):
|
||||
daily_posts: list[DailyStatItem]
|
||||
daily_replies: list[DailyStatItem]
|
||||
daily_ai_executions: list[DailyStatItem]
|
||||
daily_agent_calls: list[DailyStatItem]
|
||||
totals: dict
|
||||
|
||||
|
||||
# ===== Personal Insights =====
|
||||
class HourlyActivity(BaseModel):
|
||||
hour: int
|
||||
count: int
|
||||
|
||||
|
||||
class TagUsage(BaseModel):
|
||||
tag_path: str
|
||||
usage_count: int
|
||||
|
||||
|
||||
class PersonalInsights(BaseModel):
|
||||
hourly_activity: list[HourlyActivity]
|
||||
top_tags: list[TagUsage]
|
||||
token_trend_percent: float
|
||||
this_month_tokens: int
|
||||
last_month_tokens: int
|
||||
39
backend/app/schemas/task.py
Normal file
39
backend/app/schemas/task.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from app.models.task import TaskStatus, TaskPriority
|
||||
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: str | None = None
|
||||
priority: TaskPriority = TaskPriority.MEDIUM
|
||||
due_date: datetime | None = None
|
||||
tags: list[str] | None = None
|
||||
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
status: TaskStatus | None = None
|
||||
priority: TaskPriority | None = None
|
||||
due_date: datetime | None = None
|
||||
tags: list[str] | None = None
|
||||
|
||||
|
||||
class TaskOut(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
description: str | None
|
||||
status: TaskStatus
|
||||
priority: TaskPriority
|
||||
due_date: datetime | None
|
||||
completed_at: datetime | None
|
||||
tags: str | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class DailyPlanRequest(BaseModel):
|
||||
user_id: str
|
||||
40
backend/app/schemas/todo.py
Normal file
40
backend/app/schemas/todo.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from app.models.todo import TodoSource
|
||||
|
||||
|
||||
class TodoCreate(BaseModel):
|
||||
title: str
|
||||
|
||||
|
||||
class TodoUpdate(BaseModel):
|
||||
title: str | None = None
|
||||
is_completed: bool | None = None
|
||||
|
||||
|
||||
class TodoOut(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
is_completed: bool
|
||||
source: TodoSource
|
||||
source_detail: str | None
|
||||
todo_date: str
|
||||
completed_at: datetime | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class TodoListOut(BaseModel):
|
||||
items: list[TodoOut]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
class TodoSummaryOut(BaseModel):
|
||||
date: str
|
||||
total: int
|
||||
completed: int
|
||||
pending: int
|
||||
Reference in New Issue
Block a user