151 lines
5.6 KiB
Python
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())
|