Spaces:
Sleeping
Sleeping
| """ | |
| rag_formatter.py | |
| ββββββββββββββββ | |
| Compact, LLM-optimized formatter for retrieved PyxiScience entities. | |
| Replaces the legacy 4-section verbose catalogue (imports / signatures / | |
| names / detailed) with a single structured block per entity, driven by | |
| the :pxs_trigger: / :pxs_returns: / :pxs_example: / :pxs_antipattern: | |
| meta-tags embedded in PyxiScience docstrings. | |
| Entity dict schema expected by `build_catalogue`: | |
| { | |
| "qualname": "pxs_Poly.pxsl_solution", | |
| "signature": "(mult='\\times', formula=True)", | |
| "import_line": "from pyxiscience.Classes_Extensions import pxs_Poly", | |
| "kind": "function" | "method" | "class", | |
| "docstring": "<raw docstring, :pxs_*: tags intact>", | |
| # for classes, optionally: | |
| "methods": [{"name": str, "signature": str, "summary": str}, ...], | |
| } | |
| Functions and methods go through `_format_function_entity`. | |
| Classes with a non-empty `methods` list go through `_format_class_entity`. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| import textwrap | |
| from typing import Dict, List | |
| # Regex patterns for the :pxs_*: meta-tags. | |
| # Each one matches non-greedily up to the next :pxs_*: tag or end-of-string, | |
| # so multi-line values are preserved. | |
| _META_TAGS = { | |
| "trigger": r":pxs_trigger:\s*(.+?)(?=\n\s*:pxs_|\Z)", | |
| "returns": r":pxs_returns:\s*\|?\s*\n?(.+?)(?=\n\s*:pxs_|\Z)", | |
| "example": r":pxs_example:\s*\|?\s*\n?(.+?)(?=\n\s*:pxs_|\Z)", | |
| "antipattern": r":pxs_antipattern:\s*(.+?)(?=\n\s*:pxs_|\Z)", | |
| } | |
| def _extract_meta_tags(docstring: str) -> Dict[str, str]: | |
| """Parse :pxs_*: tags from an enriched docstring into a flat dict.""" | |
| if not docstring: | |
| return {} | |
| meta: Dict[str, str] = {} | |
| for key, pattern in _META_TAGS.items(): | |
| m = re.search(pattern, docstring, flags=re.DOTALL) | |
| if m: | |
| # Normalise indentation & trim trailing whitespace. | |
| meta[key] = textwrap.dedent(m.group(1)).strip() | |
| return meta | |
| def _first_useful_line(docstring: str) -> str: | |
| """ | |
| Fallback summary when no :pxs_trigger: is present. | |
| Pulls the first non-trivial line BEFORE any :pxs_*: tag, strips | |
| bilingual prefixes ('Fr :', '\\fr{', 'En :', etc.) and truncates. | |
| """ | |
| if not docstring: | |
| return "" | |
| # Only look at the human-readable head (before the first meta-tag). | |
| cut = re.search(r"^\s*:pxs_\w+:", docstring, flags=re.MULTILINE) | |
| head = docstring[: cut.start()] if cut else docstring | |
| for raw in head.splitlines(): | |
| line = raw.strip() | |
| if not line or line.startswith((":", "---", "===", "Args:", "Returns:", "Paramètres")): | |
| continue | |
| # Strip bilingual markers. | |
| line = re.sub(r"^(Fr|En|\\fr|\\en)\s*[:{]?\s*", "", line) | |
| line = line.rstrip("}").strip() | |
| if len(line) > 10: | |
| return line[:140] | |
| return "" | |
| def _indent(text: str, prefix: str = " ") -> str: | |
| """Indent every line of a block with a constant prefix.""" | |
| return "\n".join(prefix + line if line else line for line in text.splitlines()) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Per-entity formatters | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _format_function_entity(entity: Dict) -> str: | |
| """Compact block for a standalone function or (unattached) method.""" | |
| meta = _extract_meta_tags(entity.get("docstring", "")) | |
| lines: List[str] = [] | |
| # Header: qualified name + signature, then the import line. | |
| lines.append(f"βΈ {entity['qualname']}{entity.get('signature', '(...)')}") | |
| lines.append(f" {entity['import_line']}") | |
| lines.append("") | |
| trigger = meta.get("trigger") or _first_useful_line(entity.get("docstring", "")) | |
| if trigger: | |
| lines.append("QUAND UTILISER") | |
| lines.append(_indent(trigger)) | |
| lines.append("") | |
| if meta.get("returns"): | |
| lines.append("CE QUE ΓA RETOURNE") | |
| lines.append(_indent(meta["returns"])) | |
| lines.append("") | |
| if meta.get("example"): | |
| lines.append("EXEMPLE D'APPEL") | |
| lines.append(_indent(meta["example"])) | |
| lines.append("") | |
| if meta.get("antipattern"): | |
| lines.append("NE PAS RΓIMPLΓMENTER MANUELLEMENT") | |
| lines.append(f" β {meta['antipattern']}") | |
| lines.append(f" β Utiliser {entity['qualname']}") | |
| while lines and lines[-1] == "": | |
| lines.pop() | |
| return "\n".join(lines) | |
| def _format_class_entity(entity: Dict) -> str: | |
| """ | |
| Compact block for a class + its methods. Expects entity['methods'] to be | |
| a non-empty list of {name, signature, summary} dicts. | |
| """ | |
| meta = _extract_meta_tags(entity.get("docstring", "")) | |
| lines: List[str] = [] | |
| lines.append(f"βΈ {entity['qualname']} (classe)") | |
| lines.append(f" {entity['import_line']}") | |
| lines.append("") | |
| lines.append("MΓTHODES DISPONIBLES") | |
| for m in entity["methods"]: | |
| sig = m.get("signature", "(...)") | |
| summary = (m.get("summary") or "").strip() | |
| suffix = f" β {summary}" if summary else "" | |
| lines.append(f" .{m['name']}{sig}{suffix}") | |
| lines.append("") | |
| trigger = meta.get("trigger") or _first_useful_line(entity.get("docstring", "")) | |
| if trigger: | |
| lines.append("QUAND UTILISER (la classe)") | |
| lines.append(_indent(trigger)) | |
| lines.append("") | |
| if meta.get("example"): | |
| lines.append("EXEMPLE D'USAGE TYPIQUE") | |
| lines.append(_indent(meta["example"])) | |
| lines.append("") | |
| if meta.get("antipattern"): | |
| lines.append("NE PAS RΓIMPLΓMENTER MANUELLEMENT") | |
| lines.append(f" β {meta['antipattern']}") | |
| lines.append(f" β Utiliser {entity['qualname']}") | |
| while lines and lines[-1] == "": | |
| lines.pop() | |
| return "\n".join(lines) | |
| def _format_entity_for_llm(entity: Dict) -> str: | |
| """Dispatch: class with methods β grouped block; else β function block.""" | |
| if entity.get("kind") == "class" and entity.get("methods"): | |
| return _format_class_entity(entity) | |
| return _format_function_entity(entity) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Public API | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _SEP = "β" * 55 | |
| def build_catalogue(entities: List[Dict]) -> str: | |
| """ | |
| Assemble the full catalogue string from an ordered list of retrieved | |
| entity dicts. Use this as the `{functions}` injection in a downstream | |
| code-generation prompt. | |
| """ | |
| if not entities: | |
| return "Aucune fonction PyxiScience pertinente dΓ©tectΓ©e pour cet exercice." | |
| header = [ | |
| "FONCTIONS PYXISCIENCE DISPONIBLES β USAGE OBLIGATOIRE", | |
| "", | |
| "Les entitΓ©s ci-dessous ont Γ©tΓ© sΓ©lectionnΓ©es comme pertinentes", | |
| "pour cet exercice. Utilise-les plutΓ΄t que de rΓ©implΓ©menter", | |
| "leur logique Γ la main β c'est la diffΓ©rence entre du code", | |
| "PyxiScience et du code gΓ©nΓ©rique qui sera rejetΓ©.", | |
| "", | |
| ] | |
| blocks = [_format_entity_for_llm(e) for e in entities] | |
| body = f"\n{_SEP}\n".join([_SEP] + blocks + [_SEP]) | |
| return "\n".join(header) + body | |
| def build_imports_block(entities: List[Dict]) -> str: | |
| """ | |
| Produce a de-duplicated 'from X import a, b, c' block from the entities, | |
| grouped by module. For methods, we import the PARENT CLASS (not the | |
| method itself β that's unimportable). | |
| """ | |
| by_module: Dict[str, set] = {} | |
| for e in entities: | |
| import_line = e.get("import_line", "") | |
| m = re.match(r"from\s+(\S+)\s+import\s+(.+)", import_line) | |
| if not m: | |
| continue | |
| module, names = m.group(1), m.group(2) | |
| by_module.setdefault(module, set()).update( | |
| n.strip() for n in names.split(",") if n.strip() | |
| ) | |
| if not by_module: | |
| return "# (aucun import)" | |
| return "\n".join( | |
| f"from {mod} import {', '.join(sorted(names))}" | |
| for mod, names in sorted(by_module.items()) | |
| ) |