Skip to content

Commit 13c4f10

Browse files
committed
operations are wrapped in try except
fix WhatHasBeenDoneReportGenerator generation summary
1 parent d8c7b7b commit 13c4f10

File tree

7 files changed

+321
-185
lines changed

7 files changed

+321
-185
lines changed

osa_tool/config/prompts/analysis.toml

Lines changed: 21 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -49,131 +49,39 @@ RULES:
4949
5050
"""
5151

52-
after_report_text_prompt = """
53-
DEFINITIONS:
54-
55-
translate_dirs — Translate directory names to English
56-
convert_notebooks — Convert Jupyter notebooks to Python scripts
57-
translate_readme — Translate README file to English
58-
ensure_license — Ensure a LICENSE file is present
59-
community_docs — Add or update community documentation files
60-
docstring — Add or update docstrings in source code
61-
report — Generate refactoring report
62-
readme — Create a README file
63-
requirements — Add or update requirements file
64-
organize — Reorganize project structure
65-
about — Add project description or about section
66-
validate_paper — Validate academic paper files
67-
validate_doc — Validate documentation files
68-
generate_workflows — Generate CI or automation workflows
69-
include_tests — Add or include tests
70-
include_black — Add Black code formatter
71-
include_pep8 — Add PEP8 compliance checks
72-
include_autopep8 — Add autopep8 formatter
73-
include_fix_pep8 — Automatically fix PEP8 issues
74-
include_pypi — Prepare project for PyPI publishing
75-
python_versions — Specify supported Python versions
76-
pep8_tool — Configure PEP8 checking tool
77-
use_poetry — Use Poetry for dependency management
78-
include_codecov — Add Codecov integration
79-
80-
81-
INPUT DATA (tasks list):
82-
83-
{tasks_list}
52+
after_report_summary_from_events_prompt = """
53+
INPUT DATA (operations list):
8454
85-
TASK:
55+
{operations}
8656
87-
Write a connected textual summary of the repository refactoring.
57+
TASK:
58+
Write a short factual summary of what was performed.
8859
8960
REQUIREMENTS:
90-
- Write approximately two short paragraphs
91-
- Use plain text only
61+
- Use only operations with performed == Yes
62+
- Base the wording on the provided events and result fields
63+
- Keep it concise (about two short paragraphs)
9264
- Do not use markdown
9365
- Do not use bullet points or lists
94-
- Do NOT mention tasks that were not performed
95-
- Do NOT explain why something was not done
96-
- Do NOT use modal or speculative language (avoid words like "may", "might", "likely")
97-
- Describe only actions that were actually performed
98-
- Use neutral but natural technical language
99-
- Write in confident past tense
100-
- Base the text ONLY on tasks with value "Yes"
101-
102-
STYLE:
103-
- Clear and readable
104-
- Slightly narrative, but professional
105-
- No bureaucratic or legalistic phrasing
106-
- No assumptions or interpretation beyond task definitions
10766
10867
OUTPUT:
109-
Return only the text summary.
110-
Do not add any explanations or extra text.
68+
Return ONLY JSON matching the system message schema.
11169
"""
11270

113-
after_report_blocks_prompt = """
114-
TASK DEFINITIONS:
115-
116-
translate_dirs — Translate directory names to English
117-
convert_notebooks — Convert Jupyter notebooks to Python scripts
118-
translate_readme — Translate README file to English
119-
ensure_license — Ensure a LICENSE file is present
120-
community_docs — Add or update community documentation files
121-
docstring — Add or update docstrings in source code
122-
report — Generate refactoring report
123-
readme — Create a README file
124-
requirements — Add or update requirements file
125-
organize — Reorganize project structure
126-
about — Add project description or about section
127-
validate_paper — Validate academic paper files
128-
validate_doc — Validate documentation files
129-
generate_workflows — Generate CI or automation workflows
130-
include_tests — Add or include tests
131-
include_black — Add Black code formatter
132-
include_pep8 — Add PEP8 compliance checks
133-
include_autopep8 — Add autopep8 formatter
134-
include_fix_pep8 — Automatically fix PEP8 issues
135-
include_pypi — Prepare project for PyPI publishing
136-
python_versions — Specify supported Python versions
137-
pep8_tool — Configure PEP8 checking tool
138-
use_poetry — Use Poetry for dependency management
139-
include_codecov — Add Codecov integration
140-
141-
142-
INPUT DATA (tasks list):
143-
144-
{tasks_list}
145-
146-
TASK:
147-
148-
You must summarize the result of repository refactoring based ONLY on the tasks listed above.
149-
150-
Group tasks into logical blocks.
151-
Each logical block must contain:
152-
- a short name (plain sentence)
153-
- a short description (plain sentence)
154-
- a list of task numbers that belong to this block
71+
after_report_blocks_from_events_prompt = """
72+
INPUT DATA (operations list):
15573
156-
OUTPUT FORMAT:
74+
{operations}
15775
158-
Return ONLY valid JSON.
159-
No extra text, no explanations, no markdown.
160-
161-
JSON structure:
162-
[
163-
{{
164-
"name": "Block name as a plain sentence",
165-
"description": "Short description as a plain sentence",
166-
"tasks": [1, 2]
167-
}}
168-
]
169-
170-
RULES (IMPORTANT):
76+
TASK:
77+
Group performed operations into logical blocks.
17178
172-
1. Use double quotes only
173-
2. Output must be valid JSON
174-
3. Use ONLY task numbers from the input list
175-
4. Every task from 1 to 7 MUST be used exactly once
176-
5. Do NOT invent new tasks
177-
7. "tasks" must contain only numbers
79+
REQUIREMENTS:
80+
- Include ONLY operations with performed == Yes
81+
- Each performed operation must appear in exactly one block
82+
- "tasks" must list operation names exactly as provided (strings)
83+
- Do not invent operations
17884
85+
OUTPUT:
86+
Return ONLY JSON matching the system message schema.
17987
"""

osa_tool/config/prompts/system_messages.toml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,52 @@ Strict rules:
174174
{{
175175
"summary": "text",
176176
}}
177+
"""
178+
179+
after_report_summary = """
180+
You are an assistant that writes concise and professional factual summary of made changes to repository.
181+
182+
Input will contain a list of operations with:
183+
- performed flag (Yes/No)
184+
- normalized result payload
185+
- operation events (kind, target, optional data)
186+
187+
Strict rules:
188+
- You MUST return a valid JSON object.
189+
- The JSON MUST conform exactly to the following schema:
190+
191+
{{
192+
"summary": "text"
193+
}}
194+
195+
Content rules:
196+
- Only describe actions that were actually performed (performed == Yes).
197+
- Base claims on the provided events and result fields only.
198+
- Do NOT invent files, changes, or outcomes not present in the input.
199+
- Write in confident past tense.
200+
"""
201+
202+
after_report_blocks = """
203+
You group performed operations into logical blocks.
204+
205+
Input will contain a list of operations with performed flag (Yes/No) and details.
206+
207+
Strict rules:
208+
- You MUST return valid JSON.
209+
- The JSON MUST be an array of objects with this schema:
210+
211+
[
212+
{{
213+
"name": "short block name",
214+
"description": "short block description",
215+
"tasks": ["Operation name 1", "Operation name 2"]
216+
}}
217+
]
218+
219+
Grouping rules:
220+
- Use ONLY operation names exactly as provided in the input list.
221+
- Include ONLY operations where performed == Yes.
222+
- Every performed operation must appear in exactly one block.
223+
- Do NOT include operations with performed == No.
224+
- Do NOT add extra text outside JSON.
177225
"""

osa_tool/operations/analysis/repository_report/report_generator.py

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import os
22

3+
from langchain_core.output_parsers import PydanticOutputParser
34
from pydantic import ValidationError
45

56
from osa_tool.config.settings import ConfigManager
67
from osa_tool.core.git.metadata import RepositoryMetadata
78
from osa_tool.core.llm.llm import ModelHandler, ModelHandlerFactory
9+
from osa_tool.core.models.event import OperationEvent
810
from osa_tool.operations.analysis.repository_report.response_validation import (
911
RepositoryReport,
1012
RepositoryStructure,
@@ -13,6 +15,8 @@
1315
OverallAssessment,
1416
AfterReportBlock,
1517
AfterReport,
18+
AfterReportSummary,
19+
AfterReportBlocksPlan,
1620
)
1721
from osa_tool.tools.repository_analysis.sourcerank import SourceRank
1822
from osa_tool.utils.logger import logger
@@ -90,11 +94,17 @@ def _extract_presence_files(self) -> list[str]:
9094

9195

9296
class AfterReportTextGenerator:
93-
def __init__(self, config_manger: ConfigManager, what_has_been_done: list[tuple[str, bool]]) -> None:
97+
def __init__(
98+
self,
99+
config_manger: ConfigManager,
100+
completed_tasks: list[tuple[str, bool]],
101+
task_results: dict[str, dict] | None = None,
102+
) -> None:
94103
self.config_manager = config_manger
95104
self.model_settings = self.config_manager.get_model_settings("general")
96-
self.prompts = self.config_manager.config.prompts
97-
self.what_has_been_done = what_has_been_done
105+
self.prompts = self.config_manager.get_prompts()
106+
self.completed_tasks = completed_tasks
107+
self.task_results = task_results or {}
98108
self.model_handler: ModelHandler = ModelHandlerFactory.build(self.model_settings)
99109

100110
def make_request(self) -> AfterReport:
@@ -104,32 +114,90 @@ def make_request(self) -> AfterReport:
104114
Returns:
105115
The generated OSA work summary response from the model.
106116
"""
107-
formatted_tasks = "\n".join(
108-
f"Task {i}. {n}: {'Yes' if d else 'No'}" for i, (n, d) in enumerate(self.what_has_been_done)
109-
)
110-
json_prompt = PromptBuilder.render(
111-
self.prompts.get("analysis.after_report_blocks_prompt"),
112-
tasks_list=formatted_tasks,
113-
)
114-
summary_prompt = PromptBuilder.render(
115-
self.prompts.get("analysis.after_report_text_prompt"),
116-
tasks_list=formatted_tasks,
117-
)
117+
performed_lookup = {name: done for name, done in self.completed_tasks}
118+
operations_text = self._operations_to_text(self.completed_tasks, self.task_results)
118119

119120
try:
120-
summary = self.model_handler.send_request(prompt=summary_prompt)
121-
json_result = self.model_handler.send_and_parse(
122-
prompt=json_prompt,
123-
parser=lambda raw: [
124-
AfterReportBlock(
125-
name=d["name"],
126-
description=d["description"],
127-
tasks=[self.what_has_been_done[i] for i in d["tasks"]],
128-
)
129-
for d in JsonProcessor.parse(raw, expected_type=list)
130-
],
121+
# Summary (structured JSON -> AfterReportSummary)
122+
summary_parser = PydanticOutputParser(pydantic_object=AfterReportSummary)
123+
summary_system = self.prompts.get("system_messages.after_report_summary")
124+
summary_prompt = PromptBuilder.render(
125+
self.prompts.get("analysis.after_report_summary_from_events_prompt"),
126+
operations=operations_text,
127+
)
128+
summary_obj: AfterReportSummary = self.model_handler.run_chain(
129+
prompt=summary_prompt,
130+
parser=summary_parser,
131+
system_message=summary_system,
132+
)
133+
134+
# Blocks (structured JSON -> AfterReportBlocksPlan)
135+
blocks_parser = PydanticOutputParser(pydantic_object=AfterReportBlocksPlan)
136+
blocks_system = self.prompts.get("system_messages.after_report_blocks")
137+
blocks_prompt = PromptBuilder.render(
138+
self.prompts.get("analysis.after_report_blocks_from_events_prompt"),
139+
operations=operations_text,
140+
)
141+
blocks_plan: AfterReportBlocksPlan = self.model_handler.run_chain(
142+
prompt=blocks_prompt,
143+
parser=blocks_parser,
144+
system_message=blocks_system,
131145
)
132-
return AfterReport(summary=summary, blocks=json_result)
146+
147+
blocks: list[AfterReportBlock] = []
148+
for block in blocks_plan.root:
149+
tasks = [(t, bool(performed_lookup.get(t, False))) for t in block.tasks]
150+
blocks.append(AfterReportBlock(name=block.name, description=block.description, tasks=tasks))
151+
152+
return AfterReport(summary=summary_obj.summary, blocks=blocks)
153+
133154
except Exception as e:
134-
logger.error(f"Unexpected error while parsing RepositoryReport: {e}")
135-
raise ValueError(f"Failed to process model response: {e}")
155+
logger.error("Unexpected error while generating AfterReport: %s", e)
156+
raise ValueError(f"Failed to process model response: {e}") from e
157+
158+
@staticmethod
159+
def _events_to_text(events: list[OperationEvent]) -> str:
160+
lines: list[str] = []
161+
for e in events:
162+
kind = getattr(e.kind, "value", str(e.kind))
163+
line = "- %s: %s" % (kind, e.target)
164+
data = getattr(e, "data", None) or {}
165+
if data:
166+
line += " (%s)" % ", ".join("%s=%s" % (k, v) for k, v in data.items())
167+
lines.append(line)
168+
return "\n".join(lines)
169+
170+
def _operations_to_text(self, completed_tasks: list[tuple[str, bool]], task_results: dict[str, dict]) -> str:
171+
parts: list[str] = []
172+
173+
for name, done in completed_tasks:
174+
details = task_results.get(name) or {}
175+
result = details.get("result")
176+
events = details.get("events") or []
177+
178+
# Result (truncate)
179+
result_text = "None"
180+
if result is not None:
181+
result_text = str(result)
182+
if len(result_text) > 600:
183+
result_text = result_text[:600] + "..."
184+
185+
# Events text
186+
try:
187+
events_text = self._events_to_text(events)
188+
except Exception:
189+
events_text = "\n".join("- %s" % str(e) for e in events) if events else ""
190+
191+
parts.append(
192+
"\n".join(
193+
[
194+
f"Operation: {name}",
195+
f"Performed: {'Yes' if done else 'No'}",
196+
f"Result: {result_text}",
197+
"Events:",
198+
events_text or "- (none)",
199+
]
200+
)
201+
)
202+
203+
return "\n\n---\n\n".join(parts)

osa_tool/operations/analysis/repository_report/report_maker.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from osa_tool.core.git.git_agent import GitAgent
2323
from osa_tool.core.models.event import OperationEvent, EventKind
2424
from osa_tool.operations.analysis.repository_report.report_generator import TextGenerator, AfterReportTextGenerator
25+
from osa_tool.scheduler.plan import Plan
2526
from osa_tool.tools.repository_analysis.sourcerank import SourceRank
2627
from osa_tool.utils.logger import logger
2728
from osa_tool.utils.utils import osa_project_root
@@ -404,14 +405,15 @@ class WhatHasBeenDoneReportGenerator(AbstractReportGenerator):
404405
def __init__(
405406
self,
406407
config_manager: ConfigManager,
407-
what_has_been_done: list[tuple[str, bool]],
408+
plan: Plan,
408409
git_agent: GitAgent,
409410
):
410411
super().__init__(config_manager, git_agent)
411412
self.filename = f"{self.metadata.name}_work_summary.pdf"
412413
self.output_path = os.path.join(os.getcwd(), self.filename)
413-
self.what_has_been_done = what_has_been_done
414-
self.text_generator = AfterReportTextGenerator(config_manager, self.what_has_been_done)
414+
self.completed_tasks = plan.list_for_report
415+
self.task_results = plan.results or {}
416+
self.text_generator = AfterReportTextGenerator(config_manager, self.completed_tasks, self.task_results)
415417
self.start_log = f"Starting creating summary for OSA work"
416418
self.report_header = "OSA Work Summary"
417419
self.events: list[OperationEvent] = []

0 commit comments

Comments
 (0)