tactus 0.39.0__py3-none-any.whl → 0.40.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tactus/__init__.py +1 -1
- tactus/cli/app.py +24 -0
- tactus/core/dsl_stubs.py +7 -1
- tactus/core/runtime.py +314 -57
- {tactus-0.39.0.dist-info → tactus-0.40.0.dist-info}/METADATA +1 -1
- {tactus-0.39.0.dist-info → tactus-0.40.0.dist-info}/RECORD +9 -9
- {tactus-0.39.0.dist-info → tactus-0.40.0.dist-info}/WHEEL +0 -0
- {tactus-0.39.0.dist-info → tactus-0.40.0.dist-info}/entry_points.txt +0 -0
- {tactus-0.39.0.dist-info → tactus-0.40.0.dist-info}/licenses/LICENSE +0 -0
tactus/__init__.py
CHANGED
tactus/cli/app.py
CHANGED
|
@@ -505,6 +505,12 @@ def run(
|
|
|
505
505
|
real: Optional[list[str]] = typer.Option(
|
|
506
506
|
None, "--real", help="Use real implementation for specific tool(s)"
|
|
507
507
|
),
|
|
508
|
+
auto_deps: bool = typer.Option(
|
|
509
|
+
False, "--auto-deps", help="Automatically run dependency tasks without prompting"
|
|
510
|
+
),
|
|
511
|
+
no_deps: bool = typer.Option(
|
|
512
|
+
False, "--no-deps", help="Fail fast if dependency tasks are required"
|
|
513
|
+
),
|
|
508
514
|
sandbox: Optional[bool] = typer.Option(
|
|
509
515
|
None,
|
|
510
516
|
"--sandbox/--no-sandbox",
|
|
@@ -568,6 +574,15 @@ def run(
|
|
|
568
574
|
console.print(f"[red]Error:[/red] Workflow file not found: {workflow_file}")
|
|
569
575
|
raise typer.Exit(1)
|
|
570
576
|
|
|
577
|
+
if not isinstance(auto_deps, bool):
|
|
578
|
+
auto_deps = False
|
|
579
|
+
if not isinstance(no_deps, bool):
|
|
580
|
+
no_deps = False
|
|
581
|
+
|
|
582
|
+
if auto_deps and no_deps:
|
|
583
|
+
console.print("[red]Error:[/red] --auto-deps and --no-deps cannot be combined")
|
|
584
|
+
raise typer.Exit(1)
|
|
585
|
+
|
|
571
586
|
# Determine format based on extension
|
|
572
587
|
file_format = "lua" if workflow_file.suffix in [".tac", ".lua"] else "yaml"
|
|
573
588
|
|
|
@@ -808,6 +823,15 @@ def run(
|
|
|
808
823
|
tool_paths=tool_paths,
|
|
809
824
|
source_file_path=str(workflow_file),
|
|
810
825
|
)
|
|
826
|
+
runtime.dependency_mode = "auto" if auto_deps else "none" if no_deps else "prompt"
|
|
827
|
+
|
|
828
|
+
def _dependency_prompt_handler(plan, label: str) -> bool:
|
|
829
|
+
pending = [task.kind for task in plan.tasks if task.status != "complete"]
|
|
830
|
+
pending_summary = ", ".join(pending) if pending else "none"
|
|
831
|
+
console.print(f"[yellow]Dependencies required for {label}: {pending_summary}[/yellow]")
|
|
832
|
+
return typer.confirm("Run dependencies now?", default=False)
|
|
833
|
+
|
|
834
|
+
runtime.dependency_prompt_handler = _dependency_prompt_handler
|
|
811
835
|
|
|
812
836
|
# Always create a mock manager so Mocks {} blocks can register tool mocks.
|
|
813
837
|
from tactus.core.mocking import MockManager, set_current_mock_manager
|
tactus/core/dsl_stubs.py
CHANGED
|
@@ -461,7 +461,13 @@ def create_dsl_stubs(
|
|
|
461
461
|
if hasattr(task_config, "items"):
|
|
462
462
|
child_tasks = {}
|
|
463
463
|
for key, value in task_config.items():
|
|
464
|
-
if isinstance(key, str) and hasattr(value, "items"):
|
|
464
|
+
if not (isinstance(key, str) and hasattr(value, "items")):
|
|
465
|
+
continue
|
|
466
|
+
try:
|
|
467
|
+
marker = value["__tactus_task_config"]
|
|
468
|
+
except Exception:
|
|
469
|
+
marker = False
|
|
470
|
+
if marker:
|
|
465
471
|
child_tasks[key] = value
|
|
466
472
|
if child_tasks:
|
|
467
473
|
try:
|
tactus/core/runtime.py
CHANGED
|
@@ -14,7 +14,7 @@ import logging
|
|
|
14
14
|
import time
|
|
15
15
|
import uuid
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Any, Dict, List, Optional
|
|
17
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
18
18
|
|
|
19
19
|
from tactus.core.registry import ProcedureRegistry, RegistryBuilder, TaskDeclaration
|
|
20
20
|
from tactus.core.dsl_stubs import create_dsl_stubs, lua_table_to_dict
|
|
@@ -28,6 +28,9 @@ from tactus.protocols.storage import StorageBackend
|
|
|
28
28
|
from tactus.protocols.hitl import HITLHandler
|
|
29
29
|
from tactus.protocols.chat_recorder import ChatRecorder
|
|
30
30
|
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from biblicus.corpus import Corpus
|
|
33
|
+
|
|
31
34
|
# For backwards compatibility with YAML
|
|
32
35
|
try:
|
|
33
36
|
from tactus.core.yaml_parser import ProcedureYAMLParser, ProcedureConfigError
|
|
@@ -142,6 +145,8 @@ class TactusRuntime:
|
|
|
142
145
|
self.tool_paths = tool_paths or []
|
|
143
146
|
self.recursion_depth = recursion_depth
|
|
144
147
|
self.external_config = external_config or {}
|
|
148
|
+
self.dependency_mode = self.external_config.get("dependency_mode", "prompt")
|
|
149
|
+
self.dependency_prompt_handler = None
|
|
145
150
|
self.run_id = run_id
|
|
146
151
|
self.source_file_path = source_file_path
|
|
147
152
|
|
|
@@ -2552,6 +2557,7 @@ class TactusRuntime:
|
|
|
2552
2557
|
return self._execute_task(self.task_name)
|
|
2553
2558
|
|
|
2554
2559
|
if self.registry:
|
|
2560
|
+
self._execute_run_dependencies()
|
|
2555
2561
|
# Check for named 'main' procedure first
|
|
2556
2562
|
if "main" in self.registry.named_procedures:
|
|
2557
2563
|
logger.info("Executing named 'main' procedure")
|
|
@@ -2639,10 +2645,169 @@ class TactusRuntime:
|
|
|
2639
2645
|
logger.error(f"Legacy procedure execution failed: {e}")
|
|
2640
2646
|
raise
|
|
2641
2647
|
|
|
2642
|
-
def
|
|
2648
|
+
def _parse_task_dependencies(self, task_payload: dict[str, Any]) -> list[str]:
|
|
2649
|
+
depends_on = task_payload.get("depends_on")
|
|
2650
|
+
if isinstance(depends_on, str):
|
|
2651
|
+
return [depends_on]
|
|
2652
|
+
if isinstance(depends_on, list):
|
|
2653
|
+
return [item for item in depends_on if isinstance(item, str) and item.strip()]
|
|
2654
|
+
return []
|
|
2655
|
+
|
|
2656
|
+
def _parse_task_provides(self, task_payload: dict[str, Any]) -> list[dict[str, Any]]:
|
|
2657
|
+
provides = task_payload.get("provides")
|
|
2658
|
+
if isinstance(provides, dict):
|
|
2659
|
+
return [provides]
|
|
2660
|
+
if isinstance(provides, list):
|
|
2661
|
+
return [item for item in provides if isinstance(item, dict)]
|
|
2662
|
+
return []
|
|
2663
|
+
|
|
2664
|
+
def _iter_task_declarations(self) -> list[tuple[str, TaskDeclaration]]:
|
|
2665
|
+
if not self.registry:
|
|
2666
|
+
return []
|
|
2667
|
+
tasks = getattr(self.registry, "tasks", None) or {}
|
|
2668
|
+
if not tasks:
|
|
2669
|
+
return []
|
|
2670
|
+
|
|
2671
|
+
def walk(task: TaskDeclaration, prefix: str) -> list[tuple[str, TaskDeclaration]]:
|
|
2672
|
+
full_name = f"{prefix}:{task.name}" if prefix else task.name
|
|
2673
|
+
pairs = [(full_name, task)]
|
|
2674
|
+
for child in task.children.values():
|
|
2675
|
+
pairs.extend(walk(child, full_name))
|
|
2676
|
+
return pairs
|
|
2677
|
+
|
|
2678
|
+
pairs: list[tuple[str, TaskDeclaration]] = []
|
|
2679
|
+
for task in tasks.values():
|
|
2680
|
+
pairs.extend(walk(task, ""))
|
|
2681
|
+
return pairs
|
|
2682
|
+
|
|
2683
|
+
def _find_task_providing(self, *, kind: str, corpus_name: Optional[str]) -> Optional[str]:
|
|
2684
|
+
if not self.registry:
|
|
2685
|
+
return None
|
|
2686
|
+
matches: list[str] = []
|
|
2687
|
+
normalize_task_kind = self._normalize_task_kind
|
|
2688
|
+
|
|
2689
|
+
for full_name, task in self._iter_task_declarations():
|
|
2690
|
+
payload = task.model_dump()
|
|
2691
|
+
for provides in self._parse_task_provides(payload):
|
|
2692
|
+
provided_kind = provides.get("kind")
|
|
2693
|
+
provided_corpus = provides.get("corpus")
|
|
2694
|
+
if not isinstance(provided_kind, str):
|
|
2695
|
+
continue
|
|
2696
|
+
if normalize_task_kind(provided_kind) != kind:
|
|
2697
|
+
continue
|
|
2698
|
+
if provided_corpus is None or provided_corpus == corpus_name:
|
|
2699
|
+
matches.append(full_name)
|
|
2700
|
+
if matches:
|
|
2701
|
+
return matches[0]
|
|
2702
|
+
if kind == "load" and corpus_name and len(self.registry.corpora) == 1:
|
|
2703
|
+
tasks = getattr(self.registry, "tasks", None) or {}
|
|
2704
|
+
if "load" in tasks:
|
|
2705
|
+
return "load"
|
|
2706
|
+
return None
|
|
2707
|
+
|
|
2708
|
+
@staticmethod
|
|
2709
|
+
def _normalize_task_kind(value: str) -> str:
|
|
2710
|
+
if not isinstance(value, str):
|
|
2711
|
+
return ""
|
|
2712
|
+
cleaned = value.strip().lower()
|
|
2713
|
+
aliases = {
|
|
2714
|
+
"fetch": "load",
|
|
2715
|
+
"sync": "load",
|
|
2716
|
+
"build": "index",
|
|
2717
|
+
"run": "query",
|
|
2718
|
+
}
|
|
2719
|
+
return aliases.get(cleaned, cleaned)
|
|
2720
|
+
|
|
2721
|
+
def _open_or_init_corpus(self, corpus_root: Path) -> "Corpus":
|
|
2722
|
+
from biblicus.corpus import Corpus
|
|
2723
|
+
|
|
2724
|
+
corpus = Corpus(corpus_root.resolve())
|
|
2725
|
+
try:
|
|
2726
|
+
corpus.load_catalog()
|
|
2727
|
+
except FileNotFoundError:
|
|
2728
|
+
corpus = Corpus.init(corpus_root, force=False)
|
|
2729
|
+
return corpus
|
|
2730
|
+
|
|
2731
|
+
def _corpus_pipeline_config(self, corpus_decl: Any) -> Optional[dict]:
|
|
2732
|
+
config = corpus_decl.config if isinstance(corpus_decl.config, dict) else {}
|
|
2733
|
+
corpus_configuration = config.get("configuration", {}) if isinstance(config, dict) else {}
|
|
2734
|
+
pipeline = (
|
|
2735
|
+
corpus_configuration.get("pipeline", {})
|
|
2736
|
+
if isinstance(corpus_configuration, dict)
|
|
2737
|
+
else {}
|
|
2738
|
+
)
|
|
2739
|
+
if not isinstance(pipeline, dict):
|
|
2740
|
+
return None
|
|
2741
|
+
if "extract" in pipeline and isinstance(pipeline.get("extract"), dict):
|
|
2742
|
+
return pipeline.get("extract")
|
|
2743
|
+
if "steps" in pipeline:
|
|
2744
|
+
return pipeline
|
|
2745
|
+
return None
|
|
2746
|
+
|
|
2747
|
+
def _retriever_index_config(self, retriever_decl: Any) -> dict:
|
|
2748
|
+
config = retriever_decl.config if isinstance(retriever_decl.config, dict) else {}
|
|
2749
|
+
configuration = config.get("configuration", {}) if isinstance(config, dict) else {}
|
|
2750
|
+
pipeline = configuration.get("pipeline", {}) if isinstance(configuration, dict) else {}
|
|
2751
|
+
if not pipeline and isinstance(config.get("pipeline"), dict):
|
|
2752
|
+
pipeline = config.get("pipeline") or {}
|
|
2753
|
+
index_config = pipeline.get("index", {}) if isinstance(pipeline, dict) else {}
|
|
2754
|
+
if isinstance(index_config, dict):
|
|
2755
|
+
return index_config
|
|
2756
|
+
return {}
|
|
2757
|
+
|
|
2758
|
+
def _prompt_dependency_plan(self, *, plan: Any, label: str) -> bool:
|
|
2759
|
+
prompt_handler = getattr(self, "dependency_prompt_handler", None)
|
|
2760
|
+
if callable(prompt_handler):
|
|
2761
|
+
return bool(prompt_handler(plan, label))
|
|
2762
|
+
pending = [task.kind for task in plan.tasks if task.status != "complete"]
|
|
2763
|
+
prompt = f"Run dependencies for {label}? ({', '.join(pending)}) [y/N]: "
|
|
2764
|
+
response = input(prompt).strip().lower()
|
|
2765
|
+
return response in {"y", "yes"}
|
|
2766
|
+
|
|
2767
|
+
def _execute_dependency_plan(
|
|
2768
|
+
self,
|
|
2769
|
+
*,
|
|
2770
|
+
plan: Any,
|
|
2771
|
+
corpus: Any,
|
|
2772
|
+
label: str,
|
|
2773
|
+
load_task_name: Optional[str] = None,
|
|
2774
|
+
extract_task_name: Optional[str] = None,
|
|
2775
|
+
index_task_name: Optional[str] = None,
|
|
2776
|
+
) -> list[Any]:
|
|
2777
|
+
mode = self.dependency_mode
|
|
2778
|
+
if plan.status == "complete":
|
|
2779
|
+
return []
|
|
2780
|
+
if plan.status == "blocked":
|
|
2781
|
+
raise RuntimeError(plan.root.reason or f"Dependencies blocked for {label}")
|
|
2782
|
+
if mode == "none":
|
|
2783
|
+
raise RuntimeError(f"Dependencies missing for {label}")
|
|
2784
|
+
if mode not in {"prompt", "auto"}:
|
|
2785
|
+
raise RuntimeError(f"Unsupported dependency mode: {mode}")
|
|
2786
|
+
if mode == "prompt" and not self._prompt_dependency_plan(plan=plan, label=label):
|
|
2787
|
+
raise RuntimeError(f"Dependencies declined for {label}")
|
|
2788
|
+
|
|
2789
|
+
from biblicus.workflow import build_default_handler_registry
|
|
2790
|
+
|
|
2791
|
+
handler_registry = build_default_handler_registry(corpus)
|
|
2792
|
+
if load_task_name:
|
|
2793
|
+
handler_registry["load"] = lambda _: self._execute_task(load_task_name)
|
|
2794
|
+
if extract_task_name:
|
|
2795
|
+
handler_registry["extract"] = lambda _: self._execute_task(extract_task_name)
|
|
2796
|
+
if index_task_name:
|
|
2797
|
+
handler_registry["index"] = lambda _: self._execute_task(index_task_name)
|
|
2798
|
+
return plan.execute(mode="auto", handler_registry=handler_registry)
|
|
2799
|
+
|
|
2800
|
+
def _execute_task(self, task_name: str, *, _stack: Optional[list[str]] = None) -> Any:
|
|
2643
2801
|
if not self.registry:
|
|
2644
2802
|
raise RuntimeError("No registry available for task execution")
|
|
2645
2803
|
|
|
2804
|
+
stack = list(_stack or [])
|
|
2805
|
+
if task_name in stack:
|
|
2806
|
+
raise RuntimeError(
|
|
2807
|
+
f"Task dependency cycle detected: {' -> '.join(stack + [task_name])}"
|
|
2808
|
+
)
|
|
2809
|
+
stack.append(task_name)
|
|
2810
|
+
|
|
2646
2811
|
task = self._resolve_task(task_name)
|
|
2647
2812
|
if task is None:
|
|
2648
2813
|
# Allow run fallback to main procedure
|
|
@@ -2650,6 +2815,10 @@ class TactusRuntime:
|
|
|
2650
2815
|
self.task_name = None
|
|
2651
2816
|
return self._execute_workflow()
|
|
2652
2817
|
|
|
2818
|
+
corpus_tasks = self._resolve_corpus_task_targets(task_name)
|
|
2819
|
+
if corpus_tasks:
|
|
2820
|
+
return self._execute_corpus_tasks(task_name, corpus_tasks)
|
|
2821
|
+
|
|
2653
2822
|
retriever_tasks = self._resolve_retriever_task_targets(task_name)
|
|
2654
2823
|
if retriever_tasks:
|
|
2655
2824
|
return self._execute_retriever_tasks(task_name, retriever_tasks)
|
|
@@ -2657,6 +2826,10 @@ class TactusRuntime:
|
|
|
2657
2826
|
raise RuntimeError(f"Task '{task_name}' not found")
|
|
2658
2827
|
|
|
2659
2828
|
task_payload = task.model_dump()
|
|
2829
|
+
if task_name == "run":
|
|
2830
|
+
self._execute_run_dependencies()
|
|
2831
|
+
for dependency in self._parse_task_dependencies(task_payload):
|
|
2832
|
+
self._execute_task(dependency, _stack=stack)
|
|
2660
2833
|
entry = task_payload.get("entry")
|
|
2661
2834
|
if entry is None:
|
|
2662
2835
|
if task.children:
|
|
@@ -2670,20 +2843,74 @@ class TactusRuntime:
|
|
|
2670
2843
|
raise RuntimeError(f"Task '{task_name}' entry must be a function")
|
|
2671
2844
|
return entry()
|
|
2672
2845
|
|
|
2673
|
-
def
|
|
2674
|
-
if not
|
|
2675
|
-
|
|
2846
|
+
def _execute_run_dependencies(self) -> None:
|
|
2847
|
+
if not self.registry:
|
|
2848
|
+
return
|
|
2849
|
+
retrievers = getattr(self.registry, "retrievers", None) or {}
|
|
2850
|
+
if not retrievers:
|
|
2851
|
+
return
|
|
2852
|
+
for retriever_name in retrievers:
|
|
2853
|
+
self._ensure_retriever_dependencies(retriever_name)
|
|
2676
2854
|
|
|
2855
|
+
def _execute_corpus_tasks(self, task_name: str, corpus_names: list[str]) -> Any:
|
|
2856
|
+
if not corpus_names:
|
|
2857
|
+
raise RuntimeError(f"No corpora available for task '{task_name}'")
|
|
2677
2858
|
base_task = task_name.split(":")[0]
|
|
2678
|
-
if base_task == "
|
|
2859
|
+
if base_task == "extract":
|
|
2679
2860
|
results = []
|
|
2680
|
-
for
|
|
2681
|
-
results.append(self.
|
|
2861
|
+
for corpus_name in corpus_names:
|
|
2862
|
+
results.append(self._execute_corpus_extract(corpus_name))
|
|
2682
2863
|
return results[0] if len(results) == 1 else results
|
|
2864
|
+
raise RuntimeError(f"Corpus task '{base_task}' is not supported")
|
|
2683
2865
|
|
|
2684
|
-
|
|
2866
|
+
def _execute_corpus_extract(self, corpus_name: str) -> Any:
|
|
2867
|
+
if not self.registry:
|
|
2868
|
+
raise RuntimeError("No registry available for corpus execution")
|
|
2869
|
+
corpus_decl = self.registry.corpora.get(corpus_name)
|
|
2870
|
+
if corpus_decl is None:
|
|
2871
|
+
raise RuntimeError(f"Corpus '{corpus_name}' not found")
|
|
2872
|
+
corpus_root = corpus_decl.config.get("corpus_root") or corpus_decl.config.get("root")
|
|
2873
|
+
if not corpus_root:
|
|
2874
|
+
raise RuntimeError(f"Corpus '{corpus_name}' is missing a root path")
|
|
2685
2875
|
|
|
2686
|
-
|
|
2876
|
+
pipeline_config = self._corpus_pipeline_config(corpus_decl)
|
|
2877
|
+
load_task_name = self._find_task_providing(kind="load", corpus_name=corpus_name)
|
|
2878
|
+
extract_task_name = self._find_task_providing(kind="extract", corpus_name=corpus_name)
|
|
2879
|
+
|
|
2880
|
+
try:
|
|
2881
|
+
from biblicus.workflow import build_plan_for_extract
|
|
2882
|
+
except Exception as exc:
|
|
2883
|
+
raise RuntimeError(f"Biblicus workflow unavailable: {exc}") from exc
|
|
2884
|
+
|
|
2885
|
+
corpus = self._open_or_init_corpus(Path(corpus_root))
|
|
2886
|
+
plan = build_plan_for_extract(
|
|
2887
|
+
corpus,
|
|
2888
|
+
pipeline_config=pipeline_config,
|
|
2889
|
+
load_handler_available=bool(load_task_name),
|
|
2890
|
+
)
|
|
2891
|
+
results = self._execute_dependency_plan(
|
|
2892
|
+
plan=plan,
|
|
2893
|
+
corpus=corpus,
|
|
2894
|
+
label=f"corpus '{corpus_name}'",
|
|
2895
|
+
load_task_name=load_task_name,
|
|
2896
|
+
extract_task_name=extract_task_name,
|
|
2897
|
+
)
|
|
2898
|
+
return results[-1] if results else {"status": "up-to-date"}
|
|
2899
|
+
|
|
2900
|
+
def _ensure_retriever_dependencies(self, retriever_name: str) -> None:
|
|
2901
|
+
plan, corpus, handlers = self._build_retriever_index_plan(retriever_name)
|
|
2902
|
+
self._execute_dependency_plan(
|
|
2903
|
+
plan=plan,
|
|
2904
|
+
corpus=corpus,
|
|
2905
|
+
label=f"retriever '{retriever_name}'",
|
|
2906
|
+
load_task_name=handlers.get("load"),
|
|
2907
|
+
extract_task_name=handlers.get("extract"),
|
|
2908
|
+
index_task_name=handlers.get("index"),
|
|
2909
|
+
)
|
|
2910
|
+
|
|
2911
|
+
def _build_retriever_index_plan(
|
|
2912
|
+
self, retriever_name: str
|
|
2913
|
+
) -> tuple[Any, Any, dict[str, Optional[str]]]:
|
|
2687
2914
|
if not self.registry:
|
|
2688
2915
|
raise RuntimeError("No registry available for retriever execution")
|
|
2689
2916
|
|
|
@@ -2702,63 +2929,78 @@ class TactusRuntime:
|
|
|
2702
2929
|
if not corpus_root:
|
|
2703
2930
|
raise RuntimeError(f"Corpus '{retriever.corpus}' is missing a root path")
|
|
2704
2931
|
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
if isinstance(
|
|
2709
|
-
else {}
|
|
2710
|
-
)
|
|
2711
|
-
if isinstance(corpus_configuration, dict):
|
|
2712
|
-
pipeline = corpus_configuration.get("pipeline", {}) or {}
|
|
2713
|
-
if isinstance(pipeline, dict):
|
|
2714
|
-
extraction_pipeline = pipeline.get("extract", {}) or {}
|
|
2715
|
-
if isinstance(extraction_pipeline, list):
|
|
2716
|
-
extraction_pipeline = {}
|
|
2717
|
-
|
|
2718
|
-
retriever_id = retriever.config.get("retriever_id") or retriever.config.get(
|
|
2719
|
-
"retriever_type"
|
|
2932
|
+
from tactus.core.retriever_tasks import resolve_retriever_id
|
|
2933
|
+
|
|
2934
|
+
retriever_id = resolve_retriever_id(
|
|
2935
|
+
retriever.config if isinstance(retriever.config, dict) else {}
|
|
2720
2936
|
)
|
|
2721
2937
|
if not retriever_id:
|
|
2722
2938
|
raise RuntimeError(
|
|
2723
|
-
f"Retriever '{retriever_name}' is missing retriever_id; cannot build
|
|
2939
|
+
f"Retriever '{retriever_name}' is missing retriever_id; cannot build plan"
|
|
2724
2940
|
)
|
|
2725
2941
|
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2942
|
+
pipeline_config = self._corpus_pipeline_config(corpus_decl)
|
|
2943
|
+
index_config = self._retriever_index_config(retriever)
|
|
2944
|
+
|
|
2945
|
+
load_task_name = self._find_task_providing(kind="load", corpus_name=retriever.corpus)
|
|
2946
|
+
extract_task_name = self._find_task_providing(kind="extract", corpus_name=retriever.corpus)
|
|
2947
|
+
index_task_name = None
|
|
2948
|
+
for task_name, task in self._iter_task_declarations():
|
|
2949
|
+
for provides in self._parse_task_provides(task.model_dump()):
|
|
2950
|
+
if provides.get("kind") != "index":
|
|
2951
|
+
continue
|
|
2952
|
+
provided_retriever = provides.get("retriever")
|
|
2953
|
+
if provided_retriever in {retriever_name, retriever_id}:
|
|
2954
|
+
index_task_name = task_name
|
|
2955
|
+
break
|
|
2956
|
+
if index_task_name:
|
|
2957
|
+
break
|
|
2733
2958
|
|
|
2734
2959
|
try:
|
|
2735
|
-
from biblicus.
|
|
2736
|
-
from biblicus.extraction import build_extraction_snapshot
|
|
2737
|
-
from biblicus.retrievers import get_retriever
|
|
2960
|
+
from biblicus.workflow import build_plan_for_index
|
|
2738
2961
|
except Exception as exc:
|
|
2739
|
-
raise RuntimeError(f"Biblicus
|
|
2740
|
-
|
|
2741
|
-
corpus =
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
extractor_id="pipeline",
|
|
2749
|
-
configuration_name=f"{retriever.corpus or retriever_name}-extract",
|
|
2750
|
-
configuration=extraction_pipeline,
|
|
2751
|
-
)
|
|
2752
|
-
if isinstance(index_config, dict) and "extraction_snapshot" not in index_config:
|
|
2753
|
-
index_config["extraction_snapshot"] = (
|
|
2754
|
-
f"{extraction_manifest.configuration.extractor_id}:"
|
|
2755
|
-
f"{extraction_manifest.snapshot_id}"
|
|
2756
|
-
)
|
|
2757
|
-
snapshot = retriever_impl.build_snapshot(
|
|
2758
|
-
corpus, configuration_name=configuration_name, configuration=index_config
|
|
2962
|
+
raise RuntimeError(f"Biblicus workflow unavailable: {exc}") from exc
|
|
2963
|
+
|
|
2964
|
+
corpus = self._open_or_init_corpus(Path(corpus_root))
|
|
2965
|
+
plan = build_plan_for_index(
|
|
2966
|
+
corpus,
|
|
2967
|
+
retriever_id,
|
|
2968
|
+
pipeline_config=pipeline_config,
|
|
2969
|
+
index_config=index_config,
|
|
2970
|
+
load_handler_available=bool(load_task_name),
|
|
2759
2971
|
)
|
|
2760
2972
|
|
|
2761
|
-
|
|
2973
|
+
handlers = {
|
|
2974
|
+
"load": load_task_name,
|
|
2975
|
+
"extract": extract_task_name,
|
|
2976
|
+
"index": index_task_name,
|
|
2977
|
+
}
|
|
2978
|
+
return plan, corpus, handlers
|
|
2979
|
+
|
|
2980
|
+
def _execute_retriever_tasks(self, task_name: str, retriever_names: list[str]) -> Any:
|
|
2981
|
+
if not retriever_names:
|
|
2982
|
+
raise RuntimeError(f"No retrievers available for task '{task_name}'")
|
|
2983
|
+
|
|
2984
|
+
base_task = task_name.split(":")[0]
|
|
2985
|
+
if base_task == "index":
|
|
2986
|
+
results = []
|
|
2987
|
+
for retriever_name in retriever_names:
|
|
2988
|
+
results.append(self._execute_retriever_index(retriever_name))
|
|
2989
|
+
return results[0] if len(results) == 1 else results
|
|
2990
|
+
|
|
2991
|
+
raise RuntimeError(f"Retriever task '{base_task}' is not supported")
|
|
2992
|
+
|
|
2993
|
+
def _execute_retriever_index(self, retriever_name: str) -> Any:
|
|
2994
|
+
plan, corpus, handlers = self._build_retriever_index_plan(retriever_name)
|
|
2995
|
+
results = self._execute_dependency_plan(
|
|
2996
|
+
plan=plan,
|
|
2997
|
+
corpus=corpus,
|
|
2998
|
+
label=f"retriever '{retriever_name}'",
|
|
2999
|
+
load_task_name=handlers.get("load"),
|
|
3000
|
+
extract_task_name=handlers.get("extract"),
|
|
3001
|
+
index_task_name=handlers.get("index"),
|
|
3002
|
+
)
|
|
3003
|
+
return results[-1] if results else {"status": "up-to-date"}
|
|
2762
3004
|
|
|
2763
3005
|
def _resolve_task(self, task_name: str) -> Optional[TaskDeclaration]:
|
|
2764
3006
|
if not self.registry:
|
|
@@ -2806,6 +3048,21 @@ class TactusRuntime:
|
|
|
2806
3048
|
return [name for name in targets if name == target]
|
|
2807
3049
|
return targets
|
|
2808
3050
|
|
|
3051
|
+
def _resolve_corpus_task_targets(self, task_name: str) -> list[str]:
|
|
3052
|
+
if not self.registry or not self.registry.corpora:
|
|
3053
|
+
return []
|
|
3054
|
+
segments = [segment for segment in task_name.split(":") if segment]
|
|
3055
|
+
if not segments:
|
|
3056
|
+
return []
|
|
3057
|
+
task = segments[0]
|
|
3058
|
+
target = segments[1] if len(segments) > 1 else None
|
|
3059
|
+
if task != "extract":
|
|
3060
|
+
return []
|
|
3061
|
+
targets = list(self.registry.corpora.keys())
|
|
3062
|
+
if target:
|
|
3063
|
+
return [name for name in targets if name == target]
|
|
3064
|
+
return targets
|
|
3065
|
+
|
|
2809
3066
|
def _expand_inline_task_children(self, registry: ProcedureRegistry) -> None:
|
|
2810
3067
|
if not registry or not registry.tasks:
|
|
2811
3068
|
return
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
tactus/__init__.py,sha256=
|
|
1
|
+
tactus/__init__.py,sha256=kZIgMAqEdibTGcY5NARvHI3BnbR8fsjBkiGdbDZ2JBg,1245
|
|
2
2
|
tactus/adapters/__init__.py,sha256=47Y8kGBR4QGxqEGvjA1mneOSACb2L7oELnj6P2uI7uk,759
|
|
3
3
|
tactus/adapters/broker_log.py,sha256=9ZR-rJdyW6bMNZx3OfXoQEnDxcAzNsiJ8aPxZGqJYrM,6019
|
|
4
4
|
tactus/adapters/cli_hitl.py,sha256=Nrfoi35Ei9fTMReLG2QxKkhKyIvl3pYcAUdQCAUOZDk,17361
|
|
@@ -29,7 +29,7 @@ tactus/broker/protocol.py,sha256=v4DFSVoecerqxbqK-vbRfYEAD10tk-QXNH_d9PFgkWg,534
|
|
|
29
29
|
tactus/broker/server.py,sha256=s0_Uokovf5s-IR8Ieb3r1h9dnt4eO_PT0aycwuHwhks,56236
|
|
30
30
|
tactus/broker/stdio.py,sha256=JXkEz-PCU3IQXNkt16YJtYmwkR43eS6CfjxAHc-YCfQ,439
|
|
31
31
|
tactus/cli/__init__.py,sha256=kVhdCkwWEPdt3vn9si-iKvh6M9817aOH6rLSsNzRuyg,80
|
|
32
|
-
tactus/cli/app.py,sha256
|
|
32
|
+
tactus/cli/app.py,sha256=I_89_mis8JFnfJ6yLNFA_PmQrmd79Ci3XZ0Pc0-6MDU,103242
|
|
33
33
|
tactus/cli/control.py,sha256=jCKKy8f6x8wV5im-MwxOtgz85oYLTHhPKXx-3FtRwoU,13364
|
|
34
34
|
tactus/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
tactus/core/__init__.py,sha256=TK5rWr3HmOO_igFa5ESGp6teWwS58vnvQhIWqkcgqwk,880
|
|
@@ -37,7 +37,7 @@ tactus/core/compaction.py,sha256=mVwFsEb9FEc-bMPFcKgXyAO-pVnAXeQGZka7RWtjVsY,397
|
|
|
37
37
|
tactus/core/config_manager.py,sha256=kxz853j4Nx97SBh8-fAar_OfmfWZvvcfafLyxjTQG1A,35131
|
|
38
38
|
tactus/core/context_assembler.py,sha256=tbd-XACBvAFkBlPrzAAZ_L-2JGnO4meS-GL4ilE7XHw,2406
|
|
39
39
|
tactus/core/context_models.py,sha256=YHKjHKEzLk3fgpkmXYQ9RWDPKRNStt92ve40xcMv3p0,975
|
|
40
|
-
tactus/core/dsl_stubs.py,sha256
|
|
40
|
+
tactus/core/dsl_stubs.py,sha256=-bTopHqUq6B_CSsnVkeNK7uy-WbJD8n3B4LOCpXGcXM,110492
|
|
41
41
|
tactus/core/exceptions.py,sha256=r-4IrZw_WrioBkpMR42Q3LNwEqimJ6jxXgfOo2wANTM,1962
|
|
42
42
|
tactus/core/execution_context.py,sha256=OgTe9E0xc3nTQbCTEaaBfI11dVA1-J0eFz0eQR4XMZY,29402
|
|
43
43
|
tactus/core/lua_sandbox.py,sha256=Ln2P1gdxVl396HLvEw7FmDKV3eVdVdbDzYHMbDSEciY,19106
|
|
@@ -47,7 +47,7 @@ tactus/core/output_validator.py,sha256=LcSjgAiDRvzsj2uWasQihengQRt7R-ZYaPiLQPbZy
|
|
|
47
47
|
tactus/core/registry.py,sha256=z5HFoi1zb81-TJ_6qeTWSte4J0Zw-NYmN19VjjBjJE8,26577
|
|
48
48
|
tactus/core/retrieval.py,sha256=AMDa4X4YWaSDNdf3T3hRUzgM4W1l_sGqxAX5Jki5PH0,12083
|
|
49
49
|
tactus/core/retriever_tasks.py,sha256=i2wsoZN9cZOypkdPAJKqlrRP_jwtqpHVCFoxPL0Iirk,931
|
|
50
|
-
tactus/core/runtime.py,sha256=
|
|
50
|
+
tactus/core/runtime.py,sha256=_egKpvp1qSB0q1zTdcjXOeaLZIEB2oQWoiFxih9K_yw,168471
|
|
51
51
|
tactus/core/template_resolver.py,sha256=r97KzFNaK4nFSoWtIFZeSKyuUWgbp-ay1_BGrb-BgUY,4179
|
|
52
52
|
tactus/core/yaml_parser.py,sha256=JD7Nehaxw3uP1KV_uTU_xiXTbEWqoKOceU5tAJ4lcH8,13985
|
|
53
53
|
tactus/core/dependencies/__init__.py,sha256=28-TM7_i-JqTD3hvkq1kMzr__A8VjfIKXymdW9mn5NM,362
|
|
@@ -226,8 +226,8 @@ tactus/validation/generated/LuaParserVisitor.py,sha256=ageKSmHPxnO3jBS2fBtkmYBOd
|
|
|
226
226
|
tactus/validation/generated/__init__.py,sha256=5gWlwRI0UvmHw2fnBpj_IG6N8oZeabr5tbj1AODDvjc,196
|
|
227
227
|
tactus/validation/grammar/LuaLexer.g4,sha256=t2MXiTCr127RWAyQGvamkcU_m4veqPzSuHUtAKwalw4,2771
|
|
228
228
|
tactus/validation/grammar/LuaParser.g4,sha256=ceZenb90BdiZmVdOxMGj9qJk3QbbWVZe5HUqPgoePfY,3202
|
|
229
|
-
tactus-0.
|
|
230
|
-
tactus-0.
|
|
231
|
-
tactus-0.
|
|
232
|
-
tactus-0.
|
|
233
|
-
tactus-0.
|
|
229
|
+
tactus-0.40.0.dist-info/METADATA,sha256=ZMyLh83LARAXlKw67who3Yfbgkyw5_4ZCJ0PAO3gSTI,60383
|
|
230
|
+
tactus-0.40.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
231
|
+
tactus-0.40.0.dist-info/entry_points.txt,sha256=vWseqty8m3z-Worje0IYxlioMjPDCoSsm0AtY4GghBY,47
|
|
232
|
+
tactus-0.40.0.dist-info/licenses/LICENSE,sha256=ivohBcAIYnaLPQ-lKEeCXSMvQUVISpQfKyxHBHoa4GA,1066
|
|
233
|
+
tactus-0.40.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|