Files
AlphaPulse/backend/src/alphapulse/models/ai.py
T
eezd 80d266dba7 Add three-stage cross-domain news research and reading briefings
Group original news into tagged fact packages, review each package with evidence and read-only research, and build complete 72-hour reading lists with independent model selections.

Add paused RSS/Atom subscriptions, news-scoped Skills, versioned analysis records, and locked deployment support for SearXNG. Preserve original news and keep live migration and legacy AI cleanup separately authorized.

Validate with 604 backend tests, 49 frontend tests, 19 CI control tests, lint, type checks, production build, and disposable PostgreSQL migration and three-stage smoke checks.
2026-10-31 22:08:59 +08:00

105 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from datetime import datetime
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from alphapulse.core.db import Base
class AiProvider(Base):
"""模型接入配置;api_key 只以密文存储"""
__tablename__ = "ai_providers"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(64), unique=True)
# openai:/v1/chat/completions;anthropic:/v1/messages
protocol: Mapped[str] = mapped_column(String(16))
base_url: Mapped[str] = mapped_column(String(255))
api_key_encrypted: Mapped[bytes] = mapped_column(LargeBinary)
api_key_hint: Mapped[str] = mapped_column(String(8))
model: Mapped[str] = mapped_column(String(128))
max_tokens: Mapped[int] = mapped_column(Integer, default=8192)
# 上下文上限(token),超过时拒绝发送而不是静默截断
context_window: Mapped[int] = mapped_column(Integer, default=200000)
# 默认思考强度;为空表示不传,由模型决定。Chat 可按轮覆盖
reasoning_effort: Mapped[str | None] = mapped_column(String(8))
is_default: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class SkillNode(Base):
"""skills 树:文件夹或 skill;内容在 skill_versions,生效的是 published_version_id 指向的版本"""
__tablename__ = "skill_nodes"
__table_args__ = (UniqueConstraint("parent_id", "name", postgresql_nulls_not_distinct=True),)
id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int | None] = mapped_column(ForeignKey("skill_nodes.id"))
# folder / skill
kind: Mapped[str] = mapped_column(String(8))
scope: Mapped[str] = mapped_column(String(16), default="shared", server_default="shared")
name: Mapped[str] = mapped_column(String(128))
sort_order: Mapped[int] = mapped_column(Integer, default=0)
priority: Mapped[int] = mapped_column(Integer, default=0)
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
published_version_id: Mapped[int | None] = mapped_column(ForeignKey("skill_versions.id", use_alter=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class SkillVersion(Base):
"""只追加不修改;回退即重新发布旧版本"""
__tablename__ = "skill_versions"
__table_args__ = (UniqueConstraint("skill_id", "version_no"),)
id: Mapped[int] = mapped_column(primary_key=True)
skill_id: Mapped[int] = mapped_column(ForeignKey("skill_nodes.id", ondelete="CASCADE"))
version_no: Mapped[int] = mapped_column(Integer)
content: Mapped[str] = mapped_column(Text)
# user / chat
source: Mapped[str] = mapped_column(String(8))
note: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class ContextVersion(Base):
"""Chat 的 CONTEXT.md;最新一行即当前内容"""
__tablename__ = "context_versions"
id: Mapped[int] = mapped_column(primary_key=True)
content: Mapped[str] = mapped_column(Text)
# user / chat / reset
source: Mapped[str] = mapped_column(String(8))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class ChatSession(Base):
__tablename__ = "chat_sessions"
id: Mapped[int] = mapped_column(primary_key=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# Reset 时结束;未结束的只有一个,即当前会话
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
class ChatMessage(Base):
"""content 为与协议无关的格式:
user: {"text"};assistant: {"text", "tool_calls": [{"id","name","arguments"}]};tool: {"tool_call_id","name","result"}
"""
__tablename__ = "chat_messages"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
session_id: Mapped[int] = mapped_column(ForeignKey("chat_sessions.id"), index=True)
# user / assistant / tool
role: Mapped[str] = mapped_column(String(16))
content: Mapped[dict] = mapped_column(JSONB)
provider_id: Mapped[int | None] = mapped_column(ForeignKey("ai_providers.id", ondelete="SET NULL"))
usage: Mapped[dict | None] = mapped_column(JSONB)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())