Edit on GitHub

agent_search_gateway.llm_search_parser

Restricted Markdown parser for LLM search results.

 1"""Restricted Markdown parser for LLM search results."""
 2
 3from .errors import InputFailure, ParserFailure
 4from .models import SearchRecord
 5from .url_normalization import normalize_url
 6
 7_RESULT_HEADING = "## Result"
 8_URL_PREFIX = "URL:"
 9_ABSTRACT_PREFIX = "Abstract:"
10
11
12def parse_search_markdown(markdown: str) -> list[SearchRecord]:
13    lines = markdown.splitlines()
14    headings = [index for index, line in enumerate(lines) if line.strip() == _RESULT_HEADING]
15    if not headings:
16        raise ParserFailure("LLM search response contained no Result blocks")
17
18    records: list[SearchRecord] = []
19    for position, start in enumerate(headings):
20        end = headings[position + 1] if position + 1 < len(headings) else len(lines)
21        record = _parse_block(lines[start + 1 : end])
22        if record is not None:
23            records.append(record)
24    return records
25
26
27def _parse_block(lines: list[str]) -> SearchRecord | None:
28    urls = [
29        line.strip()[len(_URL_PREFIX) :].strip()
30        for line in lines
31        if line.strip().startswith(_URL_PREFIX)
32    ]
33    abstracts = [
34        line.strip()[len(_ABSTRACT_PREFIX) :].strip()
35        for line in lines
36        if line.strip().startswith(_ABSTRACT_PREFIX)
37    ]
38    if len(urls) != 1 or len(abstracts) != 1:
39        raise ParserFailure("Result block must contain exactly one URL and one Abstract")
40    if not urls[0]:
41        raise ParserFailure("Result block URL is empty")
42    if not abstracts[0]:
43        return None
44    try:
45        normalized = normalize_url(urls[0])
46    except InputFailure as exc:
47        raise ParserFailure("Result block URL is invalid") from exc
48    return SearchRecord(url=normalized, abstract=abstracts[0])
def parse_search_markdown(markdown: str) -> list[agent_search_gateway.models.SearchRecord]:
13def parse_search_markdown(markdown: str) -> list[SearchRecord]:
14    lines = markdown.splitlines()
15    headings = [index for index, line in enumerate(lines) if line.strip() == _RESULT_HEADING]
16    if not headings:
17        raise ParserFailure("LLM search response contained no Result blocks")
18
19    records: list[SearchRecord] = []
20    for position, start in enumerate(headings):
21        end = headings[position + 1] if position + 1 < len(headings) else len(lines)
22        record = _parse_block(lines[start + 1 : end])
23        if record is not None:
24            records.append(record)
25    return records