Настройка процесса генерации документации

This commit is contained in:
2026-04-14 16:40:30 +03:00
parent 02eaab6bce
commit 88987abaa6
180 changed files with 9397 additions and 33226 deletions
@@ -250,6 +250,16 @@ def _build_v2_llm() -> AgentLlmService:
/ "src/app/core/agent/processes/v2/workflows/doc_explain_summary/steps/prompts/prompts.yml",
Path(__file__).resolve().parents[3]
/ "src/app/core/agent/processes/v2/workflows/general_qa_summary/steps/prompts/prompts.yml",
Path(__file__).resolve().parents[3]
/ "src/app/core/agent/processes/v2/workflows/doc_update_from_feature_v2/steps/step5_execute_subprocesses/prompts/prompts.yml",
Path(__file__).resolve().parents[3]
/ "src/app/core/agent/processes/v2/workflows/doc_update_from_feature_v2/subprocesses/create_doc/steps/step1_generate_frontmatter/prompts/prompts.yml",
Path(__file__).resolve().parents[3]
/ "src/app/core/agent/processes/v2/workflows/doc_update_from_feature_v2/subprocesses/edit_doc/steps/step1_generate_frontmatter/prompts/prompts.yml",
Path(__file__).resolve().parents[3]
/ "src/app/core/agent/processes/v2/workflows/doc_update_from_feature_v2/subprocesses/create_doc/steps/step2_generate_sections/prompts/prompts.yml",
Path(__file__).resolve().parents[3]
/ "src/app/core/agent/processes/v2/workflows/doc_update_from_feature_v2/subprocesses/edit_doc/steps/step2_generate_sections/prompts/prompts.yml",
Path(__file__).resolve().parents[3] / "src/app/core/agent/processes/v2/intent_router/routers/prompts.yml",
]
return AgentLlmService(client=_build_client(), prompts=PromptLoader(prompt_paths))
@@ -0,0 +1,236 @@
from __future__ import annotations
from types import SimpleNamespace
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step3_parse_requirements.parser import (
FunctionalRequirementsParser,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step4_prepare_tasks.services import (
RequirementTaskBuilder,
RequirementTaskOrderer,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step5_execute_subprocesses.classifier import (
DeleteIntentHeuristic,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.source_sections import (
RequirementSourceSections,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_parser import (
TemplateParser,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
DocUpdateFromFeatureV2Context,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import (
AnalyticsMeta,
RequirementUnit,
)
class _UnexpectedLlmCall:
def generate(self, *args, **kwargs): # noqa: ANN002, ANN003
raise AssertionError("LLM should not be called for explicit happy-path metadata.")
def test_parser_reads_section_6_metadata_and_units() -> None:
parser = FunctionalRequirementsParser()
content = """
analysis_id: AN-42
application: loyalty
platform: web
# 6. Функциональные требования
domain: billing
sub_domain: payments
## 6.1 Экран статуса платежа
id: payment-status
doc_type: ui_page
application: loyalty
platform: pprb
### Функциональные требования
- Показываем статус и ошибку.
## 6.2 Метод получения статуса
id: get-payment-status
doc_type: api_method
application: loyalty
platform: ufs
### Контракт метода
- GET /payments/status
""".strip()
meta, units = parser.parse(content)
assert meta.analysis_id == "AN-42"
assert meta.application == "loyalty"
assert meta.platform == "web"
assert meta.domain == "billing"
assert meta.subdomain == "payments"
assert [unit.section_key for unit in units] == ["6.1", "6.2"]
assert [unit.heading for unit in units] == ["Экран статуса платежа", "Метод получения статуса"]
assert units[0].metadata["doc_type"] == "ui_page"
assert "Показываем статус" in units[0].body
def test_parser_reads_backticked_root_metadata_format() -> None:
parser = FunctionalRequirementsParser()
content = """
# 6. Описание изменений
- `domain`: `orders`
- `sub_domain`: `list_read`
## 6.1 Страница списка заказов
id: orders.ui.list
doc_type: ui_page
application: orders_web
platform: web
### Требования к UI
- Таблица заказов.
""".strip()
meta, units = parser.parse(content)
assert meta.domain == "orders"
assert meta.subdomain == "list_read"
assert len(units) == 1
assert units[0].metadata["doc_type"] == "ui_page"
def test_task_builder_orders_platforms_and_inherits_root_context() -> None:
context = DocUpdateFromFeatureV2Context(
runtime=SimpleNamespace(),
route=SimpleNamespace(),
rag_session_id="",
analytics_meta=AnalyticsMeta(
analysis_id="AN-42",
application="loyalty",
domain="billing",
subdomain="payments",
),
requirements=[
RequirementUnit(
section_key="6.3",
heading="WEB страница",
body="Описание",
metadata={"id": "web-page", "doc_type": "ui_page", "platform": "web", "op": "create"},
),
RequirementUnit(
section_key="6.1",
heading="PPRB страница",
body="Описание",
metadata={"id": "pprb-page", "doc_type": "ui_page", "platform": "pprb", "op": "create"},
),
RequirementUnit(
section_key="6.2",
heading="UFS метод",
body="Описание",
metadata={"id": "ufs-method", "doc_type": "api_method", "platform": "ufs", "op": "create"},
),
],
)
builder = RequirementTaskBuilder(_UnexpectedLlmCall())
orderer = RequirementTaskOrderer()
tasks = orderer.order(builder.build(context))
assert [task.platform for task in tasks] == ["pprb", "ufs", "web"]
assert [task.section_key for task in tasks] == ["6.1", "6.2", "6.3"]
assert all(task.application == "loyalty" for task in tasks)
assert all(task.domain == "billing" for task in tasks)
assert all(task.subdomain == "payments" for task in tasks)
assert tasks[0].path == "docs/billing/pprb/ui_page/pprb-page.md"
def test_task_builder_uses_create_when_path_is_new_and_no_delete_markers() -> None:
context = DocUpdateFromFeatureV2Context(
runtime=SimpleNamespace(),
route=SimpleNamespace(),
rag_session_id="",
analytics_meta=AnalyticsMeta(
application="orders_web",
domain="orders",
subdomain="list_read",
),
requirements=[
RequirementUnit(
section_key="6.1",
heading="Страница списка заказов",
body="### Требования к UI\n- Показывать таблицу заказов.",
metadata={
"id": "orders.ui.list",
"doc_type": "ui_page",
"application": "orders_web",
"platform": "web",
},
)
],
)
tasks = RequirementTaskBuilder(_UnexpectedLlmCall()).build(context)
assert len(tasks) == 1
assert tasks[0].action.value == "create"
assert tasks[0].path == "docs/orders/web/ui_page/orders.ui.list.md"
def test_delete_heuristic_does_not_match_phrase_ne_udalos() -> None:
heuristic = DeleteIntentHeuristic()
assert heuristic.is_delete("Не удалось загрузить список заказов.") is False
assert heuristic.is_delete("Нужно удалить существующую страницу документации.") is True
def test_template_parser_extracts_ordered_sections_from_ui_template() -> None:
parser = TemplateParser()
template = """
---
doc_type: ui_page
---
# <title>
## Summary
Правила оформления: `../common-elements/summary.md`
## Details
Правила оформления: `../common-elements/details.md`
### Требования к UI
Правила оформления: `../common-elements/ui-requirements.md`
""".strip()
spec = parser.parse("templates/ui_page.template.md", template)
assert spec.doc_type == "ui_page"
assert spec.title_level == 1
assert [(item.level, item.title, item.rule_path) for item in spec.sections] == [
(2, "Summary", "common-elements/summary.md"),
(2, "Details", "common-elements/details.md"),
(3, "Требования к UI", "common-elements/ui-requirements.md"),
]
assert spec.sections[1].has_children is True
def test_requirement_source_sections_match_template_titles() -> None:
locator = RequirementSourceSections()
body = """
### Технический use case (тезисно)
1. Открыть экран.
### Контракт метода
| Поле | Тип |
### Нефункциональные требования
- Метрики.
""".strip()
sections = locator.extract(body)
assert "открыть экран" not in sections
assert locator.find_for_title(sections, "Технический use case") == "1. Открыть экран."
assert "| Поле | Тип |" in locator.find_for_title(sections, "Контракт")