mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-12 20:45:20 +02:00
test(podcasts): cover structured json parsing
This commit is contained in:
parent
0c92ee963e
commit
36c201f9e2
1 changed files with 68 additions and 0 deletions
68
surfsense_backend/tests/unit/podcasts/test_structured.py
Normal file
68
surfsense_backend/tests/unit/podcasts/test_structured.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Parsing a model's reply into a structured shape.
|
||||
|
||||
Agent LLMs wrap JSON in prose and markdown fences. ``invoke_json`` exists so
|
||||
every generation node tolerates that the same way. The LLM is an external
|
||||
boundary, so it is faked with a canned reply; the behavior under test is the
|
||||
parsing, not the model.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.podcasts.generation.structured import StructuredOutputError, invoke_json
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
class _Shape(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class _CannedLLM:
|
||||
"""A TTS-free stand-in for the chat model: replies with one fixed string."""
|
||||
|
||||
def __init__(self, reply: str) -> None:
|
||||
self._reply = reply
|
||||
|
||||
async def ainvoke(self, _messages):
|
||||
return SimpleReply(self._reply)
|
||||
|
||||
|
||||
class SimpleReply:
|
||||
def __init__(self, content: str) -> None:
|
||||
self.content = content
|
||||
|
||||
|
||||
async def _parse(reply: str) -> _Shape:
|
||||
return await invoke_json(_CannedLLM(reply), [], _Shape)
|
||||
|
||||
|
||||
async def test_parses_a_clean_json_reply():
|
||||
shape = await _parse('{"name": "alpha", "count": 3}')
|
||||
assert shape == _Shape(name="alpha", count=3)
|
||||
|
||||
|
||||
async def test_parses_json_wrapped_in_a_markdown_fence():
|
||||
reply = '```json\n{"name": "beta", "count": 7}\n```'
|
||||
shape = await _parse(reply)
|
||||
assert shape == _Shape(name="beta", count=7)
|
||||
|
||||
|
||||
async def test_extracts_json_embedded_in_prose():
|
||||
"""Reasoning models prepend/append chatter around the object."""
|
||||
reply = 'Sure, here you go: {"name": "gamma", "count": 1} — hope that helps!'
|
||||
shape = await _parse(reply)
|
||||
assert shape == _Shape(name="gamma", count=1)
|
||||
|
||||
|
||||
async def test_raises_when_there_is_no_json_object():
|
||||
with pytest.raises(StructuredOutputError):
|
||||
await _parse("I could not produce that.")
|
||||
|
||||
|
||||
async def test_raises_when_the_json_does_not_match_the_shape():
|
||||
with pytest.raises(StructuredOutputError):
|
||||
await _parse('{"name": "delta"}')
|
||||
Loading…
Add table
Add a link
Reference in a new issue