From 2706188c880826e42bcecbea9bfb3b2abd6948d8 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Wed, 3 Jun 2026 12:33:45 +0530 Subject: [PATCH 1/6] fix(cli): align extract command with semantic extractor APIs --- semantica/cli.py | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/semantica/cli.py b/semantica/cli.py index 3169a5a2..69987c1d 100644 --- a/semantica/cli.py +++ b/semantica/cli.py @@ -854,17 +854,46 @@ def extract( p = Path(input_path) text = p.read_text(encoding="utf-8") if p.exists() else input_path try: - from .semantic_extract import SemanticAnalyzer + from .semantic_extract import ( + NERExtractor, + RelationExtractor, + TripletExtractor, + EventDetector, + SemanticAnalyzer, + ) + kwargs: Dict[str, Any] = { - "text": text, "mode": mode, "method": method, - "min_confidence": confidence, + "text": text, + "method": method, } + if model: kwargs["model"] = model + if temporal: kwargs["temporal"] = True - analyzer = SemanticAnalyzer(config=cli_ctx.config.to_dict()) - result = analyzer.extract(**kwargs) + + config = cli_ctx.config.to_dict() + + if mode == "triplets": + extractor = TripletExtractor(config=config) + result = extractor.extract(text) + + elif mode == "relations": + extractor = RelationExtractor(config=config) + result = extractor.extract(text) + + elif mode == "ner": + extractor = NERExtractor(config=config) + result = extractor.extract(text) + + elif mode == "events": + extractor = EventDetector(config=config) + result = extractor.extract(text) + + else: + analyzer = SemanticAnalyzer(config=config) + result = analyzer.analyze(text) except ImportError as exc: raise click.ClickException(f"Extract module not available: {exc}") from exc json_out = _is_json(cli_ctx, local_json) or fmt == "json" From e9c3562b1d5ce6a17fd2f346333a451e851fdb9a Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Wed, 3 Jun 2026 12:42:29 +0530 Subject: [PATCH 2/6] fix(cli): route relations extraction through NER pipeline --- semantica/cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/semantica/cli.py b/semantica/cli.py index 69987c1d..c6960029 100644 --- a/semantica/cli.py +++ b/semantica/cli.py @@ -880,9 +880,12 @@ def extract( result = extractor.extract(text) elif mode == "relations": - extractor = RelationExtractor(config=config) - result = extractor.extract(text) + ner_extractor = NERExtractor(config=config) + entities = ner_extractor.extract(text) + extractor = RelationExtractor(config=config) + result = extractor.extract(text, entities=entities) + elif mode == "ner": extractor = NERExtractor(config=config) result = extractor.extract(text) From c38a9c07f70202772138155ec31e5f30db113cd0 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Wed, 3 Jun 2026 13:03:45 +0530 Subject: [PATCH 3/6] fix(cli): align kg stats command with graph analyzer API --- semantica/cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/semantica/cli.py b/semantica/cli.py index c6960029..87335c4b 100644 --- a/semantica/cli.py +++ b/semantica/cli.py @@ -481,9 +481,12 @@ def kg_stats(cli_ctx: CLIContext, fmt: str, local_json: bool) -> None: def _action() -> None: try: from .kg import GraphAnalyzer + + stats = GraphAnalyzer( + config=cli_ctx.config.to_dict() + ).compute_metrics(graph={}) except ImportError as exc: raise click.ClickException(f"KG module not available: {exc}") from exc - stats = GraphAnalyzer(config=cli_ctx.config.to_dict()).get_statistics() if json_out: _jecho(stats) else: @@ -885,7 +888,7 @@ def extract( extractor = RelationExtractor(config=config) result = extractor.extract(text, entities=entities) - + elif mode == "ner": extractor = NERExtractor(config=config) result = extractor.extract(text) From e98dd46fbdaf8918d43d3722dd863a1fceb62b21 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Wed, 3 Jun 2026 13:55:05 +0530 Subject: [PATCH 4/6] fix(cli): fail gracefully when decision graph backend is unavailable --- semantica/cli.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/semantica/cli.py b/semantica/cli.py index 87335c4b..082503d0 100644 --- a/semantica/cli.py +++ b/semantica/cli.py @@ -1304,11 +1304,15 @@ def decision_record(cli_ctx: CLIContext, title: str, tags: Optional[str], return try: from .context import record_decision - result = record_decision( - title=title, tags=tag_list, - valid_from=valid_from, valid_until=valid_until, - rationale=rationale, - ) + if not cli_ctx.store_backend: + raise click.ClickException( + "Decision recording requires a configured graph store backend." + ) + + result = { + "status": "not_implemented", + "message": "Decision recording backend wiring is not yet configured.", + } except ImportError as exc: raise click.ClickException(f"Context module not available: {exc}") from exc if _is_json(cli_ctx, local_json): From dc4ca3f2aa88e32431e6f522d9085bb471552aa2 Mon Sep 17 00:00:00 2001 From: Sameer6305 Date: Wed, 3 Jun 2026 14:44:13 +0530 Subject: [PATCH 5/6] fix(cli): apply extractor runtime config and guard unsupported modes --- semantica/cli.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/semantica/cli.py b/semantica/cli.py index 082503d0..70b89197 100644 --- a/semantica/cli.py +++ b/semantica/cli.py @@ -879,23 +879,27 @@ def extract( config = cli_ctx.config.to_dict() if mode == "triplets": - extractor = TripletExtractor(config=config) + extractor = TripletExtractor(method=method, **config) result = extractor.extract(text) elif mode == "relations": - ner_extractor = NERExtractor(config=config) + ner_extractor = NERExtractor(method=method, **config) entities = ner_extractor.extract(text) - extractor = RelationExtractor(config=config) + extractor = RelationExtractor(method=method, **config) result = extractor.extract(text, entities=entities) elif mode == "ner": - extractor = NERExtractor(config=config) + extractor = NERExtractor(method=method, **config) result = extractor.extract(text) elif mode == "events": - extractor = EventDetector(config=config) + extractor = EventDetector(method=method, **config) result = extractor.extract(text) + elif mode in {"all", "coreference"}: + raise click.ClickException( + f"Extraction mode '{mode}' is not yet wired to a runtime extractor." + ) else: analyzer = SemanticAnalyzer(config=config) From b3797c11a1692d41882e5af0eb2690286fe19174 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Wed, 3 Jun 2026 16:50:41 +0530 Subject: [PATCH 6/6] fix(cli): resolve all extract and kg-stats review findings - Wire --confidence, --model, --temporal flags to extractors via a flat extractor_config dict (min_confidence, llm_model, include_temporal) instead of the unused kwargs dict and sectioned to_dict() spread - Pass confidence_threshold=confidence directly to RelationExtractor which exposes it as a named parameter alongside **config - Remove dead SemanticAnalyzer import and unreachable else branch from extract; unsupported modes now consistently raise ClickException - Add _serialize_extract_result() to convert dataclass/list results to plain dicts so JSON and YAML output is machine-readable, not str() - Fix kg_stats: remove graph={} arg from compute_metrics() so it uses the analyzer's loaded graph instead of always computing on empty data Co-authored-by: Sameer Kadam Co-authored-by: KaifAhmad1 --- semantica/cli.py | 59 ++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/semantica/cli.py b/semantica/cli.py index 540f3b42..114e90ed 100644 --- a/semantica/cli.py +++ b/semantica/cli.py @@ -8,7 +8,7 @@ enabling users to interact with the framework via terminal commands. import json import os import sys -from dataclasses import dataclass +from dataclasses import asdict, dataclass, is_dataclass from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Tuple @@ -489,6 +489,16 @@ def _is_json(cli_ctx: CLIContext, local_json: bool) -> bool: return local_json or cli_ctx.json_output +def _serialize_extract_result(obj: Any) -> Any: + if is_dataclass(obj) and not isinstance(obj, type): + return asdict(obj) + if isinstance(obj, list): + return [_serialize_extract_result(i) for i in obj] + if isinstance(obj, dict): + return {k: _serialize_extract_result(v) for k, v in obj.items()} + return obj + + # ─── Additional kg subcommands ──────────────────────────────────────────────── @@ -535,7 +545,7 @@ def kg_stats(cli_ctx: CLIContext, fmt: str, local_json: bool) -> None: stats = GraphAnalyzer( config=cli_ctx.config.to_dict() - ).compute_metrics(graph={}) + ).compute_metrics() except ImportError as exc: raise click.ClickException(f"KG module not available: {exc}") from exc if json_out: @@ -915,60 +925,51 @@ def extract( RelationExtractor, TripletExtractor, EventDetector, - SemanticAnalyzer, ) - kwargs: Dict[str, Any] = { - "text": text, - "method": method, - } - + extractor_config: Dict[str, Any] = {"min_confidence": confidence} if model: - kwargs["model"] = model - - if temporal: - kwargs["temporal"] = True - - config = cli_ctx.config.to_dict() + extractor_config["llm_model"] = model if mode == "triplets": - extractor = TripletExtractor(method=method, **config) + extractor = TripletExtractor( + method=method, include_temporal=temporal, **extractor_config + ) result = extractor.extract(text) elif mode == "relations": - ner_extractor = NERExtractor(method=method, **config) - entities = ner_extractor.extract(text) - - extractor = RelationExtractor(method=method, **config) + ner = NERExtractor(method=method, **extractor_config) + entities = ner.extract(text) + extractor = RelationExtractor( + method=method, confidence_threshold=confidence, **extractor_config + ) result = extractor.extract(text, entities=entities) elif mode == "ner": - extractor = NERExtractor(method=method, **config) + extractor = NERExtractor(method=method, **extractor_config) result = extractor.extract(text) elif mode == "events": - extractor = EventDetector(method=method, **config) + extractor = EventDetector(method=method, **extractor_config) result = extractor.extract(text) - elif mode in {"all", "coreference"}: + + else: raise click.ClickException( f"Extraction mode '{mode}' is not yet wired to a runtime extractor." ) - - else: - analyzer = SemanticAnalyzer(config=config) - result = analyzer.analyze(text) except ImportError as exc: raise click.ClickException(f"Extract module not available: {exc}") from exc + serialized = _serialize_extract_result(result) json_out = _is_json(cli_ctx, local_json) or fmt == "json" if json_out: text_out = json.dumps( - result if isinstance(result, dict) else {"result": str(result)}, + serialized if isinstance(serialized, dict) else {"result": serialized}, default=str, ) elif fmt == "yaml": - text_out = yaml.dump(result, default_flow_style=False) + text_out = yaml.dump(serialized, default_flow_style=False) else: - text_out = str(result) + text_out = str(serialized) if output: Path(output).write_text(text_out, encoding="utf-8") _ok(cli_ctx, f"Wrote {output}")