Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

114 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Text block generator Markdown body.
Also exposes :func:`generate_bridge_text` so the compiler can attach a short
1-2 sentence transition to *any* block's payload (not as a separate block).
Prompts live in ``deeptutor/book/prompts/{en,zh}/text.yaml``.
"""
from __future__ import annotations
from typing import Any
from ..models import BlockType, SourceAnchor
from ._llm_writer import llm_text
from ._prompts import get_book_prompt, load_book_prompts
from ._rag_helpers import optional_rag_lookup
from .base import BlockContext, BlockGenerator
_NONE_LABEL = {"zh": "(无)", "en": "(none)"}
def _format_objectives(objectives: list[str]) -> str:
return "\n".join(f"- {o}" for o in objectives) or "(none)"
def _none_label(language: str) -> str:
return _NONE_LABEL.get(language, _NONE_LABEL["en"])
class TextGenerator(BlockGenerator):
block_type = BlockType.TEXT
async def _generate(
self, ctx: BlockContext
) -> tuple[dict[str, Any], list[SourceAnchor], dict[str, Any]]:
params = ctx.block.params
chapter_title = params.get("chapter_title", ctx.chapter.title)
chapter_summary = params.get("chapter_summary", ctx.chapter.summary)
objectives: list[str] = params.get("objectives") or ctx.chapter.learning_objectives
role: str = str(params.get("role") or "explanation")
previous_block_summary: str = str(params.get("previous_block_summary") or "")
rag = await optional_rag_lookup(
query=f"{chapter_title}: {role}. Objectives: {'; '.join(objectives)}",
ctx=ctx,
)
prompts = load_book_prompts("text", ctx.language)
none_label = _none_label(ctx.language)
rag_section = f"\n[Relevant source material]\n{rag.text}\n" if rag.text else ""
prev_section = (
f"\n[Previous block recap]\n{previous_block_summary}\n"
if previous_block_summary
else ""
)
user_prompt = get_book_prompt(prompts, "user_template").format(
chapter_title=chapter_title,
chapter_summary=chapter_summary or none_label,
objectives_block=_format_objectives(objectives),
role=role,
previous_section=prev_section,
rag_section=rag_section,
)
body = await llm_text(
user_prompt=user_prompt,
system_prompt=get_book_prompt(prompts, "system"),
max_tokens=1400,
temperature=0.45,
language=ctx.language,
)
return (
{
"format": "markdown",
"body": body,
"role": role,
},
rag.anchors,
{"used_rag": rag.used, "kb": ctx.primary_kb},
)
async def generate_bridge_text(
*,
chapter_title: str,
previous_block_summary: str,
next_block_hint: str,
language: str,
) -> str:
"""Produce 1-2 short Markdown sentences that bridge two adjacent blocks.
Used by :class:`deeptutor.book.compiler.BookCompiler` to attach a plain
transition paragraph onto a target block's ``payload['bridge_text']``,
instead of materialising a dedicated bridge block.
"""
prompts = load_book_prompts("text", language)
none_label = _none_label(language)
user_prompt = get_book_prompt(prompts, "bridge_user_template").format(
chapter_title=chapter_title,
previous_block_summary=previous_block_summary or none_label,
next_block_hint=next_block_hint or none_label,
)
body = await llm_text(
user_prompt=user_prompt,
system_prompt=get_book_prompt(prompts, "bridge_system"),
max_tokens=300,
temperature=0.5,
language=language,
)
return body.strip()
__all__ = ["TextGenerator", "generate_bridge_text"]