summarizer.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. """Task summarization utilities."""
  2. from __future__ import annotations
  3. from collections.abc import Callable, Iterator
  4. from typing import Tuple
  5. from hello_agents import ToolAwareSimpleAgent
  6. from models import SummaryState, TodoItem
  7. from config import Configuration
  8. from utils import strip_thinking_tokens
  9. from services.notes import build_note_guidance
  10. from services.text_processing import strip_tool_calls
  11. class SummarizationService:
  12. """Handles synchronous and streaming task summarization."""
  13. def __init__(
  14. self,
  15. summarizer_factory: Callable[[], ToolAwareSimpleAgent],
  16. config: Configuration,
  17. ) -> None:
  18. self._agent_factory = summarizer_factory
  19. self._config = config
  20. def summarize_task(self, state: SummaryState, task: TodoItem, context: str) -> str:
  21. """Generate a task-specific summary using the summarizer agent."""
  22. prompt = self._build_prompt(state, task, context)
  23. agent = self._agent_factory()
  24. try:
  25. response = agent.run(prompt)
  26. finally:
  27. agent.clear_history()
  28. summary_text = response.strip()
  29. if self._config.strip_thinking_tokens:
  30. summary_text = strip_thinking_tokens(summary_text)
  31. summary_text = strip_tool_calls(summary_text).strip()
  32. return summary_text or "暂无可用信息"
  33. def stream_task_summary(
  34. self, state: SummaryState, task: TodoItem, context: str
  35. ) -> Tuple[Iterator[str], Callable[[], str]]:
  36. """Stream the summary text for a task while collecting full output."""
  37. prompt = self._build_prompt(state, task, context)
  38. remove_thinking = self._config.strip_thinking_tokens
  39. raw_buffer = ""
  40. visible_output = ""
  41. emit_index = 0
  42. agent = self._agent_factory()
  43. def flush_visible() -> Iterator[str]:
  44. nonlocal emit_index, raw_buffer
  45. while True:
  46. start = raw_buffer.find("<think>", emit_index)
  47. if start == -1:
  48. if emit_index < len(raw_buffer):
  49. segment = raw_buffer[emit_index:]
  50. emit_index = len(raw_buffer)
  51. if segment:
  52. yield segment
  53. break
  54. if start > emit_index:
  55. segment = raw_buffer[emit_index:start]
  56. emit_index = start
  57. if segment:
  58. yield segment
  59. end = raw_buffer.find("</think>", start)
  60. if end == -1:
  61. break
  62. emit_index = end + len("</think>")
  63. def generator() -> Iterator[str]:
  64. nonlocal raw_buffer, visible_output, emit_index
  65. try:
  66. for chunk in agent.stream_run(prompt):
  67. raw_buffer += chunk
  68. if remove_thinking:
  69. for segment in flush_visible():
  70. visible_output += segment
  71. if segment:
  72. yield segment
  73. else:
  74. visible_output += chunk
  75. if chunk:
  76. yield chunk
  77. finally:
  78. if remove_thinking:
  79. for segment in flush_visible():
  80. visible_output += segment
  81. if segment:
  82. yield segment
  83. agent.clear_history()
  84. def get_summary() -> str:
  85. if remove_thinking:
  86. cleaned = strip_thinking_tokens(visible_output)
  87. else:
  88. cleaned = visible_output
  89. return strip_tool_calls(cleaned).strip()
  90. return generator(), get_summary
  91. def _build_prompt(self, state: SummaryState, task: TodoItem, context: str) -> str:
  92. """Construct the summarization prompt shared by both modes."""
  93. return (
  94. f"任务主题:{state.research_topic}\n"
  95. f"任务名称:{task.title}\n"
  96. f"任务目标:{task.intent}\n"
  97. f"检索查询:{task.query}\n"
  98. f"任务上下文:\n{context}\n"
  99. f"{build_note_guidance(task)}\n"
  100. "请按照以上协作要求先同步笔记,然后返回一份面向用户的 Markdown 总结(仍遵循任务总结模板)。"
  101. )