Add parser builder: one-shot DeepSeek profile for Telegram extract_mode=profile.
Generate static HeuristicProfile in CA admin, preview and run without LLM on each post via shared interpreter in CP batch and listener. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"""Admin API: construct static telegram parsers from a sample post."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from contracts.heuristic_profile import HeuristicProfile
|
||||
from contracts.sources import TelegramSourceConfig
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import ParseJob
|
||||
from ..schemas import ParseJobRead
|
||||
from ..services.jobs import enqueue_job
|
||||
from ..services import parser_builder as builder
|
||||
|
||||
router = APIRouter(prefix="/admin/parser-builder", tags=["parser-builder"])
|
||||
|
||||
|
||||
class GenerateRequest(BaseModel):
|
||||
sample_post: str = Field(min_length=1)
|
||||
|
||||
|
||||
class GenerateResponse(BaseModel):
|
||||
profile: dict[str, Any]
|
||||
|
||||
|
||||
class PreviewRequest(BaseModel):
|
||||
sample_post: str = Field(min_length=1)
|
||||
profile: dict[str, Any]
|
||||
|
||||
|
||||
class PreviewResponse(BaseModel):
|
||||
fields: dict[str, str]
|
||||
|
||||
|
||||
class CreateProfileJobRequest(BaseModel):
|
||||
channel: str = Field(min_length=1)
|
||||
profile: dict[str, Any]
|
||||
sample_post: str | None = None
|
||||
limit: int = Field(default=100, ge=1, le=1000)
|
||||
interval_seconds: int = Field(default=3600, ge=60, le=604800)
|
||||
is_active: bool = True
|
||||
|
||||
|
||||
@router.get("/target-fields")
|
||||
def target_fields():
|
||||
return {"fields": builder.get_target_fields()}
|
||||
|
||||
|
||||
@router.post("/generate", response_model=GenerateResponse)
|
||||
async def generate_profile(payload: GenerateRequest):
|
||||
if not builder.deepseek_enabled():
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="DEEPSEEK_API_KEY is not set on ca-api. Add it to .env for parser generation.",
|
||||
)
|
||||
try:
|
||||
profile = await builder.generate_profile(payload.sample_post)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
detail=f"DeepSeek generate failed: {exc}",
|
||||
) from exc
|
||||
return GenerateResponse(profile=profile.model_dump())
|
||||
|
||||
|
||||
@router.post("/preview", response_model=PreviewResponse)
|
||||
def preview_profile(payload: PreviewRequest):
|
||||
try:
|
||||
HeuristicProfile.model_validate(payload.profile)
|
||||
fields = builder.preview_with_profile(payload.sample_post, payload.profile)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return PreviewResponse(fields=fields)
|
||||
|
||||
|
||||
@router.post("/jobs", response_model=ParseJobRead, status_code=201)
|
||||
def create_profile_job(payload: CreateProfileJobRequest, db: Session = Depends(get_db)):
|
||||
try:
|
||||
cfg = TelegramSourceConfig(
|
||||
channel=payload.channel.strip(),
|
||||
limit=payload.limit,
|
||||
extract_mode="profile",
|
||||
heuristic_profile=payload.profile,
|
||||
sample_post=payload.sample_post,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
job = ParseJob(
|
||||
source_type="telegram",
|
||||
source_config=cfg.model_dump(),
|
||||
interval_seconds=payload.interval_seconds,
|
||||
is_active=payload.is_active,
|
||||
status="queued",
|
||||
)
|
||||
db.add(job)
|
||||
db.commit()
|
||||
db.refresh(job)
|
||||
|
||||
enqueue_job(job.id, job.source_type, job.source_config)
|
||||
return job
|
||||
Reference in New Issue
Block a user