Files
llm/scripts/build_1c_agent_intake.py
T

151 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Build the first agent-facing intake packet for a 1C user task."""
from __future__ import annotations
import argparse
import base64
import json
import sys
from pathlib import Path
from typing import Any
sys.path.insert(0, str(Path(__file__).resolve().parent))
from route_1c_question import route_question # noqa: E402
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_INDEX = ROOT / "reports" / "1c-sql" / "upo" / "unified-object-route-index.json"
def decode_arg(value: str | None, encoded: str | None) -> str | None:
if encoded:
return base64.b64decode(encoded).decode("utf-8")
return value
def fact_summary(fact_checks: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
confirmed = []
unresolved = []
for check in fact_checks:
result = check.get("result") or {}
row = {
"path": check.get("path"),
"status": check.get("status"),
"exists": result.get("exists") if result else None,
"confidence": result.get("confidence"),
"reason": result.get("reason"),
"object": result.get("object"),
"match": result.get("match"),
}
if result.get("exists") is True:
confirmed.append(row)
else:
unresolved.append(row)
return confirmed, unresolved
def next_commands(route: dict[str, Any], *, index: Path, view: str) -> list[dict[str, Any]]:
commands = []
decision = route.get("decision") or {}
if decision.get("needs_docs_rag"):
commands.append(
{
"tool": "docs_rag",
"purpose": "official_documentation_context",
"api": "/api/rag/query",
"payload": {
"question": route.get("question"),
"source_type": decision.get("safe_rag_scope") or "official_1c_docs",
"limit": 5,
},
}
)
for path in route.get("fact_paths") or []:
commands.append(
{
"tool": "fact_resolver",
"purpose": "current_configuration_fact",
"command": f"python scripts/resolve_1c_fact.py --index {index} --path <utf8-base64:{path}> --view {view}",
"api": "/api/1c/fact",
"payload": {
"source_kind": "route_index",
"source_path": str(index),
"path": path,
"view": view,
},
}
)
if decision.get("needs_current_config") and not route.get("fact_paths"):
commands.append(
{
"tool": "task_evidence",
"purpose": "discover_objects_and_relevant_metadata",
"command": f"python scripts/build_1c_task_evidence.py --index {index} --text <utf8-base64 task> --view {view}",
}
)
return commands
def build_intake(text: str, *, index: Path, view: str) -> dict[str, Any]:
route = route_question(text, index_path=index, view=view)
confirmed, unresolved = fact_summary(route.get("fact_checks") or [])
decision = route.get("decision") or {}
code_allowed = not unresolved and bool(confirmed or not decision.get("needs_current_config"))
if decision.get("needs_current_config") and not confirmed and not route.get("fact_paths"):
code_allowed = False
return {
"schema": "onec_agent_intake.v1",
"task": {"text": text},
"index": str(index),
"view": view,
"route": route,
"source_policy": {
"allowed_for_current_facts": ["route_index", "metadata_snapshot_explicit_current", "1c_agent_current"],
"allowed_for_documentation": ["official_1c_docs"],
"examples_are_current_facts": False,
"blocked_as_current_fact_sources": ["metadata.example", "synthetic-example", "rag_examples", "old_exports"],
},
"facts": {
"confirmed": confirmed,
"unresolved": unresolved,
"confirmed_count": len(confirmed),
"unresolved_count": len(unresolved),
},
"answer_policy": {
"code_generation_allowed": code_allowed,
"must_check_current_config_before_code": bool(decision.get("current_config_required_before_code")),
"must_not_use_examples_as_facts": True,
"safe_rag_scope": decision.get("safe_rag_scope"),
},
"next_commands": next_commands(route, index=index, view=view),
}
def main() -> int:
parser = argparse.ArgumentParser(description="Build first agent intake packet for a 1C task.")
parser.add_argument("--text")
parser.add_argument("--text-b64")
parser.add_argument("--index", type=Path, default=DEFAULT_INDEX)
parser.add_argument("--view", choices=["effective", "base"], default="effective")
parser.add_argument("--output", type=Path)
args = parser.parse_args()
text = decode_arg(args.text, args.text_b64)
if not text:
raise SystemExit("Use --text or --text-b64.")
result = build_intake(text, index=args.index, view=args.view)
output = json.dumps(result, ensure_ascii=False, indent=2) + "\n"
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(output, encoding="utf-8")
print(json.dumps({"output": str(args.output), "route": result["route"]["decision"]["route"], "code_allowed": result["answer_policy"]["code_generation_allowed"]}, ensure_ascii=False))
else:
print(output)
return 0
if __name__ == "__main__":
raise SystemExit(main())