121 lines
5.0 KiB
Python
121 lines
5.0 KiB
Python
"""LLM Provider / Model / RoutingRule / Usage。
|
||
|
||
支持多 Provider(openai / azure / 国产 LLM 等)统一管理;路由规则按优先级命中
|
||
provider + model;每次调用在 llm_usage 落 token / 成本。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from sqlalchemy import (
|
||
JSON,
|
||
Boolean,
|
||
DateTime,
|
||
ForeignKey,
|
||
Integer,
|
||
Numeric,
|
||
String,
|
||
func,
|
||
)
|
||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
|
||
from app.db.base import Base, TimestampMixin
|
||
|
||
|
||
class LLMProvider(Base, TimestampMixin):
|
||
__tablename__ = "llm_provider"
|
||
|
||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||
code: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
|
||
name: Mapped[str] = mapped_column(String(64), nullable=False)
|
||
# openai / azure / anthropic / custom
|
||
vendor: Mapped[str] = mapped_column(String(32), nullable=False, default="openai")
|
||
api_base: Mapped[str] = mapped_column(String(255), nullable=False)
|
||
# 加密存储;由 service 层做加解密
|
||
api_key_cipher: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||
status: Mapped[str] = mapped_column(String(16), default="ACTIVE", nullable=False)
|
||
|
||
|
||
class LLMModel(Base, TimestampMixin):
|
||
__tablename__ = "llm_model"
|
||
|
||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||
provider_id: Mapped[int] = mapped_column(
|
||
ForeignKey("llm_provider.id", ondelete="CASCADE"), nullable=False, index=True
|
||
)
|
||
# 对外展示名,如 "gpt-4o-mini"
|
||
model: Mapped[str] = mapped_column(String(128), nullable=False)
|
||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||
# 上下文窗口(tokens)
|
||
max_context: Mapped[int] = mapped_column(Integer, default=8192, nullable=False)
|
||
# 单价(每千 token,USD),用于成本粗估
|
||
price_input_per_1k: Mapped[float] = mapped_column(
|
||
Numeric(10, 6), default=0, nullable=False
|
||
)
|
||
price_output_per_1k: Mapped[float] = mapped_column(
|
||
Numeric(10, 6), default=0, nullable=False
|
||
)
|
||
status: Mapped[str] = mapped_column(String(16), default="ACTIVE", nullable=False)
|
||
|
||
provider: Mapped[LLMProvider] = relationship("LLMProvider", lazy="selectin")
|
||
|
||
|
||
class LLMRoutingRule(Base, TimestampMixin):
|
||
__tablename__ = "llm_routing_rule"
|
||
|
||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||
# 优先级:数字越小越先匹配
|
||
priority: Mapped[int] = mapped_column(Integer, default=100, nullable=False)
|
||
# 匹配条件(JSON dict):如 {"category": "social", "executor_type": "LOBSTER"};为空表示默认
|
||
conditions: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
||
model_id: Mapped[int] = mapped_column(
|
||
ForeignKey("llm_model.id", ondelete="RESTRICT"), nullable=False
|
||
)
|
||
enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||
|
||
model_obj: Mapped[LLMModel] = relationship("LLMModel", lazy="selectin")
|
||
|
||
|
||
class LLMUsage(Base):
|
||
"""每次 LLM 调用的用量记账。"""
|
||
|
||
__tablename__ = "llm_usage"
|
||
|
||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||
provider_id: Mapped[int] = mapped_column(
|
||
ForeignKey("llm_provider.id", ondelete="SET NULL"), nullable=True, index=True
|
||
)
|
||
model_id: Mapped[int] = mapped_column(
|
||
ForeignKey("llm_model.id", ondelete="SET NULL"), nullable=True, index=True
|
||
)
|
||
provider_code: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||
model: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||
|
||
operator_id: Mapped[int | None] = mapped_column(
|
||
ForeignKey("user.id", ondelete="SET NULL"), nullable=True, index=True
|
||
)
|
||
operator_name: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||
|
||
# 任务 / Skill / playground
|
||
source: Mapped[str] = mapped_column(String(16), default="playground", nullable=False)
|
||
task_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||
skill_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||
|
||
prompt_tokens: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||
completion_tokens: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||
total_tokens: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||
cost_usd: Mapped[float] = mapped_column(Numeric(12, 6), default=0, nullable=False)
|
||
|
||
# ok / failed
|
||
result: Mapped[str] = mapped_column(String(16), default="ok", nullable=False)
|
||
message: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||
cost_ms: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), server_default=func.now(), nullable=False, index=True
|
||
)
|