feat: 核心模块增强 — 加密 + 运行时配置 + 日志缓冲
- crypto.py: API Key 加密/解密工具 - runtime_config.py: 运行时动态配置管理 - log_buffer.py: 内存日志缓冲区 - config.py: 新增加密配置项 - http_client.py: 增强重试和错误处理
This commit is contained in:
+68
-21
@@ -11,6 +11,8 @@ from pathlib import Path
|
||||
from threading import Lock
|
||||
|
||||
from src.core.config import settings
|
||||
from sqlalchemy import select
|
||||
|
||||
from src.db.base import AsyncSessionLocal
|
||||
from src.db.models import Match, Prediction
|
||||
from src.db.unit_of_work import get_uow
|
||||
@@ -89,6 +91,8 @@ class PredictResult:
|
||||
prompt_version: str
|
||||
pred_home_goals: float | None
|
||||
pred_away_goals: float | None
|
||||
alt_pred_home_goals: int | None
|
||||
alt_pred_away_goals: int | None
|
||||
pred_1x2: str | None
|
||||
subjective_confidence: float | None
|
||||
reasoning: str | None
|
||||
@@ -97,6 +101,43 @@ class PredictResult:
|
||||
raw: dict | None
|
||||
|
||||
|
||||
async def _upsert_prediction(
|
||||
session,
|
||||
*,
|
||||
match_id: int,
|
||||
provider_name: str,
|
||||
model: str,
|
||||
mode: str,
|
||||
values: dict,
|
||||
) -> Prediction:
|
||||
"""按 (match, provider, model) 唯一约束写入预测。
|
||||
|
||||
已存在且未结算 → 覆盖更新(重新预测语义);已结算 → 拒绝(保护评估数据)。
|
||||
"""
|
||||
existing = (
|
||||
await session.execute(
|
||||
select(Prediction).where(
|
||||
Prediction.match_id == match_id,
|
||||
Prediction.provider == provider_name,
|
||||
Prediction.model == model,
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if existing is not None and existing.settled:
|
||||
raise ValueError("该比赛已有已结算的预测,不能重新预测")
|
||||
|
||||
pred = existing if existing is not None else Prediction(
|
||||
match_id=match_id, provider=provider_name, model=model,
|
||||
)
|
||||
pred.mode = mode
|
||||
for k, v in values.items():
|
||||
setattr(pred, k, v)
|
||||
if existing is None:
|
||||
session.add(pred)
|
||||
await session.flush() # 拿到自增 id;事务由 UnitOfWork 退出时提交
|
||||
return pred
|
||||
|
||||
|
||||
async def predict_match(
|
||||
match_id: int,
|
||||
*,
|
||||
@@ -140,7 +181,7 @@ async def _predict_single(
|
||||
) -> PredictResult:
|
||||
"""单次调用路径(原有实现)。"""
|
||||
if provider is None:
|
||||
provider = get_default_provider()
|
||||
provider = await get_default_provider()
|
||||
if model:
|
||||
provider.model = model
|
||||
version = prompt_version or "v1"
|
||||
@@ -172,7 +213,7 @@ async def _predict_single(
|
||||
user=user_prompt,
|
||||
json_mode=True,
|
||||
temperature=0.3,
|
||||
max_tokens=800,
|
||||
max_tokens=4096, # 推理模型的 reasoning 也计入输出 token,需留足余量
|
||||
)
|
||||
|
||||
if resp.error:
|
||||
@@ -194,28 +235,32 @@ async def _predict_single(
|
||||
if m is None:
|
||||
raise ValueError(f"match {match_id} not found")
|
||||
|
||||
pred = Prediction(
|
||||
pred = await _upsert_prediction(
|
||||
session,
|
||||
match_id=match_id,
|
||||
provider=settings.LLM_PROVIDER,
|
||||
provider_name=settings.LLM_PROVIDER,
|
||||
model=provider.model,
|
||||
prompt_version=version,
|
||||
prompt_tokens=resp.prompt_tokens,
|
||||
completion_tokens=resp.completion_tokens,
|
||||
latency_ms=resp.latency_ms,
|
||||
pred_home_goals=validated.pred_home_goals,
|
||||
pred_away_goals=validated.pred_away_goals,
|
||||
pred_1x2=validated.pred_1x2,
|
||||
subjective_confidence=validated.subjective_confidence,
|
||||
reasoning=validated.reasoning,
|
||||
raw_response=resp.raw,
|
||||
status="success",
|
||||
match_kickoff_at=match_kickoff_at,
|
||||
prediction_cutoff_at=prediction_cutoff_at,
|
||||
prediction_created_at=now,
|
||||
input_hash=input_hash,
|
||||
mode="single",
|
||||
values={
|
||||
"prompt_version": version,
|
||||
"prompt_tokens": resp.prompt_tokens,
|
||||
"completion_tokens": resp.completion_tokens,
|
||||
"latency_ms": resp.latency_ms,
|
||||
"pred_home_goals": validated.pred_home_goals,
|
||||
"pred_away_goals": validated.pred_away_goals,
|
||||
"alt_pred_home_goals": validated.alt_pred_home_goals,
|
||||
"alt_pred_away_goals": validated.alt_pred_away_goals,
|
||||
"pred_1x2": validated.pred_1x2,
|
||||
"subjective_confidence": validated.subjective_confidence,
|
||||
"reasoning": validated.reasoning,
|
||||
"raw_response": resp.raw,
|
||||
"status": "success",
|
||||
"match_kickoff_at": match_kickoff_at,
|
||||
"prediction_cutoff_at": prediction_cutoff_at,
|
||||
"prediction_created_at": now,
|
||||
"input_hash": input_hash,
|
||||
},
|
||||
)
|
||||
session.add(pred)
|
||||
await session.refresh(pred)
|
||||
|
||||
result = PredictResult(
|
||||
prediction_id=pred.id,
|
||||
@@ -224,6 +269,8 @@ async def _predict_single(
|
||||
prompt_version=version,
|
||||
pred_home_goals=pred.pred_home_goals,
|
||||
pred_away_goals=pred.pred_away_goals,
|
||||
alt_pred_home_goals=pred.alt_pred_home_goals,
|
||||
alt_pred_away_goals=pred.alt_pred_away_goals,
|
||||
pred_1x2=pred.pred_1x2,
|
||||
subjective_confidence=pred.subjective_confidence,
|
||||
reasoning=pred.reasoning,
|
||||
|
||||
Reference in New Issue
Block a user