"question":{"type":"string","description":"The question, as a customer would ask it."},
"answer":{"type":"string","description":"A clear, self-contained answer. Simple Markdown allowed (numbered steps, bullet lists, **bold**, links from the content); no headings or images."},
"category":{"type":"string","description":"Short topic grouping, e.g. 'Getting started', 'Billing'."},
},
"required":["question","answer","category"],
"additionalProperties":False,
},
},
},
"required":["faqs"],
"additionalProperties":False,
}
_GROQ_FAQ_INSTRUCTION=(
"\n\nCRITICAL OUTPUT RULE: You MUST end your turn by calling the `json` tool exactly once "
"with the extracted FAQs. Never write the FAQs as plain text — always deliver them through "
"the `json` tool call."
)
_JSON_ONLY_INSTRUCTION=(
"\n\nOUTPUT FORMAT: Respond with ONLY a JSON object of the shape "
"""Native structured output came back in a shape we can't parse — signals
the plain-JSON fallback (distinct from a legitimately empty FAQ list)."""
def_first_json_object(text:str)->dict:
"""The first {...} object in a text blob (tolerates code fences, prose and
truncation via the shared lenient parser — the providers that need this
fallback are exactly the ones likely to emit imperfect JSON). Raises
ValueError when nothing parseable exists — a batch failure upstream."""
start=text.find("{")
ifstart==-1:
raiseValueError("no JSON object in model output")
data=lenient_json_load(text[start:])
ifnotisinstance(data,dict):
raiseValueError("no JSON object in model output")
returndata
_GENERATE_INSTRUCTIONS="""You turn product documentation into a public help-center FAQ.
Read the CONTENT below and draft frequently-asked questions a customer would realistically ask, each with a clear answer.
Rules:
- Ground every answer strictly in the CONTENT. Never invent facts, prices, limits or URLs that are not present.
- Write questions the way a customer would phrase them, and answers in second person ("you"), plain language.
- Answers may use simple Markdown when it helps: numbered steps, bullet lists, **bold**, and links that appear in the CONTENT. No headings, no images, no invented URLs. Keep short answers to 1-4 sentences.
- Extract between 1 and {max_faqs} FAQs; prefer the most useful ones. If the content contains nothing FAQ-worthy, return an empty list.
- Assign each FAQ a short category. {category_rule}
- Do NOT produce any question that duplicates or trivially rephrases one of the EXISTING QUESTIONS listed below."""
_IMPORT_INSTRUCTIONS="""You are migrating an existing FAQ/help-center web page into a structured FAQ list.
Read the PAGE TEXT below and extract every question-and-answer pair that is actually present on the page.
Rules:
- Preserve the original wording as closely as possible; only trim navigation debris, repeated headings and boilerplate.
- Preserve the page's list/step structure in answers as Markdown (numbered steps, bullet lists, **bold**, links present in the PAGE TEXT).
- Only extract real Q&A pairs — skip marketing copy, navigation links and unrelated text.
- Use the page's own section headings as categories where they exist; otherwise use "General".
- Extract at most {max_faqs} pairs per call.
- Do NOT extract any question that duplicates one of the EXISTING QUESTIONS listed below."""
classFAQGeneratorAgent:
"""Stateless one-shot LLM extraction of FAQs from text, using the org's
configured model. Groq gets the shared `json`-tool structured-output path;
every other provider uses agno's native response_model."""