File size: 2,312 Bytes
31fa536
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""Step 1: LLM turns the topic/keywords/brief into optimized web-search queries."""
from __future__ import annotations

import json
import re
from typing import List

from huggingface_hub import InferenceClient

from . import config, llm

_SYSTEM = (
    "You are an SEO research assistant. Given a blog topic, its target keywords, and a "
    "brief, produce a small set of high-signal web search queries that will surface the "
    "most authoritative, information-rich sources to write the post from. Vary angle and "
    "specificity. Return ONLY a JSON array of query strings, nothing else."
)


def _fallback_terms(topic: str, primary: str, secondary: str) -> List[str]:
    base = [t for t in [topic, primary, secondary] if t]
    extra = [f"{topic} {primary}".strip(), f"{topic} guide", f"{topic} best practices"]
    seen, out = set(), []
    for t in base + extra:
        t = t.strip()
        if t and t.lower() not in seen:
            seen.add(t.lower())
            out.append(t)
    return out[: config.N_SEARCH_TERMS]


def generate_search_terms(
    client: InferenceClient,
    topic: str,
    primary_keyword: str,
    secondary_keyword: str,
    brief: str,
) -> List[str]:
    user = (
        f"Topic: {topic}\n"
        f"Primary keyword: {primary_keyword}\n"
        f"Secondary keyword: {secondary_keyword}\n"
        f"Brief: {brief}\n\n"
        f"Return {config.N_SEARCH_TERMS} search queries as a JSON array."
    )
    try:
        raw = llm.chat(
            client, config.MODEL_REASONING, _SYSTEM, user,
            max_tokens=400, temperature=0.4,
        )
        terms = _parse_terms(raw)
        if terms:
            return terms[: config.N_SEARCH_TERMS]
    except Exception:
        pass
    return _fallback_terms(topic, primary_keyword, secondary_keyword)


def _parse_terms(raw: str) -> List[str]:
    """Extract a list of strings from a (possibly fenced) LLM response."""
    match = re.search(r"\[.*\]", raw, re.DOTALL)
    if match:
        try:
            data = json.loads(match.group(0))
            return [str(t).strip() for t in data if str(t).strip()]
        except Exception:
            pass
    # line-based fallback
    lines = [re.sub(r'^[\s\-\*\d\.\)"]+', "", ln).strip().strip('"') for ln in raw.splitlines()]
    return [ln for ln in lines if ln]