codrspot-processor-mcp 0.1.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.
- codepreproc_client/__init__.py +14 -0
- codepreproc_client/__main__.py +9 -0
- codepreproc_client/layer1_business/__init__.py +0 -0
- codepreproc_client/layer1_business/allowed_scope_cache.py +62 -0
- codepreproc_client/layer1_business/api_client/__init__.py +0 -0
- codepreproc_client/layer1_business/api_client/lease_client.py +34 -0
- codepreproc_client/layer1_business/api_client/net_guard.py +98 -0
- codepreproc_client/layer1_business/api_client/promote_client.py +59 -0
- codepreproc_client/layer1_business/api_client/reasoning_client.py +163 -0
- codepreproc_client/layer1_business/api_client/registry_client.py +119 -0
- codepreproc_client/layer1_business/api_client/superkg_client.py +327 -0
- codepreproc_client/layer1_business/cli/__init__.py +0 -0
- codepreproc_client/layer1_business/cli/init.py +578 -0
- codepreproc_client/layer1_business/cli/main.py +78 -0
- codepreproc_client/layer1_business/config.py +104 -0
- codepreproc_client/layer1_business/git_utils.py +67 -0
- codepreproc_client/layer1_business/license/__init__.py +0 -0
- codepreproc_client/layer1_business/license/fingerprint.py +61 -0
- codepreproc_client/layer1_business/license/jwt_client.py +60 -0
- codepreproc_client/layer1_business/license/project_lock.py +44 -0
- codepreproc_client/layer1_business/local_registry.py +40 -0
- codepreproc_client/layer1_business/manifest/__init__.py +0 -0
- codepreproc_client/layer1_business/manifest/credentials.py +35 -0
- codepreproc_client/layer1_business/mcp/__init__.py +0 -0
- codepreproc_client/layer1_business/mcp/action_monitor.py +246 -0
- codepreproc_client/layer1_business/mcp/lifecycle.py +649 -0
- codepreproc_client/layer1_business/mcp/server.py +656 -0
- codepreproc_client/layer1_business/mcp/tools.py +1665 -0
- codepreproc_client/layer1_business/project_registry.py +99 -0
- codepreproc_client/layer2_tooling/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/ast_graph.py +426 -0
- codepreproc_client/layer2_tooling/condensation/bm25_store.py +132 -0
- codepreproc_client/layer2_tooling/condensation/chunking/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/chunking/ast_chunker.py +313 -0
- codepreproc_client/layer2_tooling/condensation/chunking/languages.py +238 -0
- codepreproc_client/layer2_tooling/condensation/embed_worker.py +128 -0
- codepreproc_client/layer2_tooling/condensation/embeddings.py +452 -0
- codepreproc_client/layer2_tooling/condensation/extract/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/extract/compactor.py +103 -0
- codepreproc_client/layer2_tooling/condensation/extract/providers/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/extract/providers/base.py +38 -0
- codepreproc_client/layer2_tooling/condensation/indexer.py +945 -0
- codepreproc_client/layer2_tooling/condensation/ppl/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/ppl/emitter.py +162 -0
- codepreproc_client/layer2_tooling/condensation/ppl/parser.py +352 -0
- codepreproc_client/layer2_tooling/condensation/qdrant_store.py +387 -0
- codepreproc_client/layer2_tooling/condensation/scanning/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/scanning/manifest_scanner.py +72 -0
- codepreproc_client/layer2_tooling/condensation/scanning/md_scanner.py +97 -0
- codepreproc_client/layer2_tooling/condensation/scanning/repo_mapper.py +495 -0
- codepreproc_client/layer2_tooling/condensation/session_vector_store.py +69 -0
- codepreproc_client/layer2_tooling/coupling/__init__.py +0 -0
- codepreproc_client/layer2_tooling/coupling/domain.py +263 -0
- codepreproc_client/layer2_tooling/coupling/graph.py +255 -0
- codepreproc_client/layer2_tooling/coupling/layer2_gate.py +189 -0
- codepreproc_client/layer2_tooling/coupling/projector.py +139 -0
- codepreproc_client/layer2_tooling/coupling/resolver.py +150 -0
- codepreproc_client/layer2_tooling/materialization/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/execute_semantic.py +1249 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/filesystem_reorg.py +575 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/patch_generator.py +127 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/patch_validator.py +101 -0
- codepreproc_client/layer2_tooling/materialization/semantic/__init__.py +19 -0
- codepreproc_client/layer2_tooling/materialization/semantic/anchors.py +61 -0
- codepreproc_client/layer2_tooling/materialization/semantic/applicability.py +75 -0
- codepreproc_client/layer2_tooling/materialization/semantic/edit_planner.py +264 -0
- codepreproc_client/layer2_tooling/materialization/semantic/materializer.py +121 -0
- codepreproc_client/layer2_tooling/materialization/semantic/merger.py +73 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/add_import.py +36 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/append_argument.py +37 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/insert_literal.py +24 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_import.py +33 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_statement_unique.py +14 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/rename_symbol_local.py +16 -0
- codepreproc_client/layer2_tooling/materialization/semantic/region_resolver.py +229 -0
- codepreproc_client/layer2_tooling/materialization/semantic/synthesizer.py +186 -0
- codepreproc_client/layer2_tooling/materialization/semantic/target_locator.py +94 -0
- codepreproc_client/layer2_tooling/materialization/semantic/validator.py +322 -0
- codepreproc_client/layer2_tooling/materialization/snippet/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/snippet/assembler.py +503 -0
- codepreproc_client/layer2_tooling/materialization/snippet/loader.py +187 -0
- codepreproc_client/layer2_tooling/retrieval/__init__.py +0 -0
- codepreproc_client/layer2_tooling/retrieval/graph_walk.py +47 -0
- codepreproc_client/layer2_tooling/retrieval/hybrid.py +95 -0
- codepreproc_client/layer2_tooling/retrieval/rerank_worker.py +86 -0
- codepreproc_client/layer2_tooling/retrieval/reranker.py +213 -0
- codepreproc_client/layer2_tooling/watcher/__init__.py +0 -0
- codepreproc_client/layer2_tooling/watcher/fs_watcher.py +3 -0
- codrspot_processor_mcp-0.1.0.dist-info/METADATA +396 -0
- codrspot_processor_mcp-0.1.0.dist-info/RECORD +100 -0
- codrspot_processor_mcp-0.1.0.dist-info/WHEEL +5 -0
- codrspot_processor_mcp-0.1.0.dist-info/entry_points.txt +3 -0
- codrspot_processor_mcp-0.1.0.dist-info/licenses/LICENSE +23 -0
- codrspot_processor_mcp-0.1.0.dist-info/top_level.txt +2 -0
- contracts/__init__.py +0 -0
- contracts/dtos.py +1239 -0
- contracts/ir.py +102 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Client for the SuperKG proxy: POST /v1/superkg/* (contracts/openapi.yaml).
|
|
2
|
+
|
|
3
|
+
Real network calls, not a direct connection to SuperKG - codepreproc-server
|
|
4
|
+
is the only thing that holds the real SuperKG URL/credentials
|
|
5
|
+
(layer3_core/superkg/connector.py on the server). This crosses the same
|
|
6
|
+
JWT-gated boundary as promote/registry; identity (tenant_id/user_id) is
|
|
7
|
+
derived server-side from the JWT, never sent by the client. Every method
|
|
8
|
+
takes `project_id` because authorization is scoped per-project (see
|
|
9
|
+
layer2_infra/api/authz.py::superkg_system_authorized on the server).
|
|
10
|
+
|
|
11
|
+
All public methods are safe to call without awaiting result checks - they
|
|
12
|
+
absorb exceptions and return None/[]/no-op on any failure so the rest of
|
|
13
|
+
codepreproc is unaffected when SuperKG (or the server) is unavailable.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from codepreproc_client.layer1_business.api_client.net_guard import assert_safe_to_send
|
|
25
|
+
from contracts.dtos import (
|
|
26
|
+
SuperkgBootstrapRequest,
|
|
27
|
+
SuperkgChunkMetadata,
|
|
28
|
+
SuperkgDecisionRequest,
|
|
29
|
+
SuperkgEnrichRequest,
|
|
30
|
+
SuperkgIngestRequest,
|
|
31
|
+
SuperkgLogActivityRequest,
|
|
32
|
+
SuperkgQueryRequest,
|
|
33
|
+
SuperkgSessionCloseRequest,
|
|
34
|
+
SuperkgSessionOpenRequest,
|
|
35
|
+
SuperkgTransitionRequest,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from contracts.dtos import CodeChunk
|
|
40
|
+
from contracts.ir import BusinessLogicDiff
|
|
41
|
+
|
|
42
|
+
JWTProvider = Callable[[str], Awaitable[str]] # project_id -> bearer token
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class KGScore:
|
|
47
|
+
kg_score: float = 0.0
|
|
48
|
+
signals: dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class EnrichResult:
|
|
53
|
+
trace_id: str
|
|
54
|
+
kg_scores: dict[str, KGScore] = field(default_factory=dict)
|
|
55
|
+
pattern_suggestions: list[dict[str, Any]] = field(default_factory=list)
|
|
56
|
+
warnings: list[dict[str, Any]] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
_log = logging.getLogger("codepreproc.superkg_client")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SuperKGClient:
|
|
62
|
+
"""Thin async wrapper around codepreproc-server's `/v1/superkg/*` proxy."""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
base_url: str,
|
|
67
|
+
jwt_provider: JWTProvider,
|
|
68
|
+
enabled: bool = True,
|
|
69
|
+
timeout: float = 10.0,
|
|
70
|
+
enrich_timeout: float = 0.8,
|
|
71
|
+
) -> None:
|
|
72
|
+
self.enabled = enabled
|
|
73
|
+
self._base = base_url.rstrip("/")
|
|
74
|
+
self._jwt_provider = jwt_provider
|
|
75
|
+
self._timeout = timeout
|
|
76
|
+
self._enrich_timeout = enrich_timeout
|
|
77
|
+
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
# Internal helpers
|
|
80
|
+
# ------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
async def _headers(self, project_id: str) -> dict[str, str]:
|
|
83
|
+
jwt = await self._jwt_provider(project_id)
|
|
84
|
+
return {"Authorization": f"Bearer {jwt}"}
|
|
85
|
+
|
|
86
|
+
async def _post(
|
|
87
|
+
self, path: str, project_id: str, request, *, kind: str | None = None, timeout: float | None = None
|
|
88
|
+
) -> dict | None:
|
|
89
|
+
if not self.enabled:
|
|
90
|
+
return None
|
|
91
|
+
assert_safe_to_send(request)
|
|
92
|
+
payload = request.model_dump(mode="json")
|
|
93
|
+
if kind is not None:
|
|
94
|
+
payload["kind"] = kind
|
|
95
|
+
try:
|
|
96
|
+
headers = await self._headers(project_id)
|
|
97
|
+
async with httpx.AsyncClient(base_url=self._base, timeout=timeout or self._timeout) as client:
|
|
98
|
+
resp = await client.post(f"/v1/superkg{path}", json=payload, headers=headers)
|
|
99
|
+
resp.raise_for_status()
|
|
100
|
+
return resp.json()
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
_log.debug("SuperKG proxy POST %s failed: %s", path, exc)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
async def _delete(self, path: str, project_id: str, request) -> None:
|
|
106
|
+
if not self.enabled:
|
|
107
|
+
return
|
|
108
|
+
assert_safe_to_send(request)
|
|
109
|
+
try:
|
|
110
|
+
headers = await self._headers(project_id)
|
|
111
|
+
async with httpx.AsyncClient(base_url=self._base, timeout=self._timeout) as client:
|
|
112
|
+
resp = await client.request(
|
|
113
|
+
"DELETE", f"/v1/superkg{path}", json=request.model_dump(mode="json"), headers=headers
|
|
114
|
+
)
|
|
115
|
+
resp.raise_for_status()
|
|
116
|
+
except Exception as exc:
|
|
117
|
+
_log.debug("SuperKG proxy DELETE %s failed: %s", path, exc)
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Project bootstrap
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
async def bootstrap(self, project_id: str, name: str, description: str) -> str | None:
|
|
124
|
+
"""Register a project as a SuperKG system. Returns the system_id or None."""
|
|
125
|
+
data = await self._post(
|
|
126
|
+
"/bootstrap", project_id, SuperkgBootstrapRequest(project_id=project_id, name=name, description=description)
|
|
127
|
+
)
|
|
128
|
+
return data.get("system_id") if data else None
|
|
129
|
+
|
|
130
|
+
# ------------------------------------------------------------------
|
|
131
|
+
# Sessions
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
async def open_session(self, project_id: str, system_id: str, context: str) -> str | None:
|
|
135
|
+
"""Open a work session in SuperKG. Returns session_id or None."""
|
|
136
|
+
data = await self._post(
|
|
137
|
+
"/session", project_id, SuperkgSessionOpenRequest(project_id=project_id, system_id=system_id, context=context)
|
|
138
|
+
)
|
|
139
|
+
return data.get("session_id") if data else None
|
|
140
|
+
|
|
141
|
+
async def close_session(self, project_id: str, session_id: str) -> None:
|
|
142
|
+
"""Close a SuperKG session (fire-and-forget)."""
|
|
143
|
+
await self._delete(
|
|
144
|
+
"/session", project_id, SuperkgSessionCloseRequest(project_id=project_id, session_id=session_id)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# ------------------------------------------------------------------
|
|
148
|
+
# Decisions & activity
|
|
149
|
+
# ------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
async def record_decision(
|
|
152
|
+
self,
|
|
153
|
+
project_id: str,
|
|
154
|
+
system_id: str,
|
|
155
|
+
session_id: str | None,
|
|
156
|
+
intent: str,
|
|
157
|
+
plan_summary: dict,
|
|
158
|
+
bld: "BusinessLogicDiff | None" = None,
|
|
159
|
+
expected_sequence: int | None = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Record a semantic edit decision in SuperKG (fire-and-forget)."""
|
|
162
|
+
req = SuperkgDecisionRequest(
|
|
163
|
+
project_id=project_id,
|
|
164
|
+
system_id=system_id,
|
|
165
|
+
session_id=session_id,
|
|
166
|
+
intent=intent,
|
|
167
|
+
plan_summary=plan_summary,
|
|
168
|
+
bld=bld.model_dump(mode="json") if bld is not None else None,
|
|
169
|
+
expected_sequence=expected_sequence,
|
|
170
|
+
)
|
|
171
|
+
await self._post("/activity", project_id, req, kind="decision")
|
|
172
|
+
|
|
173
|
+
async def log_activity(self, project_id: str, system_id: str, activity_type: str, details: dict) -> None:
|
|
174
|
+
"""Log a work activity event in SuperKG (fire-and-forget)."""
|
|
175
|
+
req = SuperkgLogActivityRequest(
|
|
176
|
+
project_id=project_id, system_id=system_id, activity_type=activity_type, details=details
|
|
177
|
+
)
|
|
178
|
+
await self._post("/activity", project_id, req, kind="activity")
|
|
179
|
+
|
|
180
|
+
async def transition_status(
|
|
181
|
+
self, project_id: str, system_id: str, entity_id: str, entity_type: str, new_status: str
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Apply a state transition to an entity (fire-and-forget)."""
|
|
184
|
+
req = SuperkgTransitionRequest(
|
|
185
|
+
project_id=project_id,
|
|
186
|
+
system_id=system_id,
|
|
187
|
+
entity_id=entity_id,
|
|
188
|
+
entity_type=entity_type,
|
|
189
|
+
new_status=new_status,
|
|
190
|
+
)
|
|
191
|
+
await self._post("/activity", project_id, req, kind="transition")
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------------------
|
|
194
|
+
# Queries
|
|
195
|
+
# ------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
async def get_snapshot(self, project_id: str, system_id: str) -> dict | None:
|
|
198
|
+
"""Return a consistent snapshot of the system state."""
|
|
199
|
+
data = await self._post(
|
|
200
|
+
"/query", project_id, SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="snapshot")
|
|
201
|
+
)
|
|
202
|
+
return data.get("data") if data else None
|
|
203
|
+
|
|
204
|
+
async def get_context(self, project_id: str, system_id: str, mode: str = "narrative") -> dict | None:
|
|
205
|
+
"""Return LLM-ready context for a system in the requested mode.
|
|
206
|
+
|
|
207
|
+
Modes: reflexive, comparative, narrative, consolidation, simulation.
|
|
208
|
+
"""
|
|
209
|
+
data = await self._post(
|
|
210
|
+
"/query",
|
|
211
|
+
project_id,
|
|
212
|
+
SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="context", mode=mode),
|
|
213
|
+
)
|
|
214
|
+
return data.get("data") if data else None
|
|
215
|
+
|
|
216
|
+
async def get_insights(self, project_id: str, system_id: str) -> list[dict]:
|
|
217
|
+
"""List insights for a system."""
|
|
218
|
+
data = await self._post(
|
|
219
|
+
"/query", project_id, SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="insights")
|
|
220
|
+
)
|
|
221
|
+
return data.get("items", []) if data else []
|
|
222
|
+
|
|
223
|
+
async def get_patterns(self, project_id: str, system_id: str) -> list[dict]:
|
|
224
|
+
"""List observed patterns across all systems."""
|
|
225
|
+
data = await self._post(
|
|
226
|
+
"/query", project_id, SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="patterns")
|
|
227
|
+
)
|
|
228
|
+
return data.get("items", []) if data else []
|
|
229
|
+
|
|
230
|
+
# ------------------------------------------------------------------
|
|
231
|
+
# Ingest (granular entity registration)
|
|
232
|
+
# ------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
async def ingest_component(self, project_id: str, system_id: str, file_path: str, language: str) -> str | None:
|
|
235
|
+
"""Register a source file as a SuperKG component. Returns component_id or None."""
|
|
236
|
+
data = await self._post(
|
|
237
|
+
"/ingest",
|
|
238
|
+
project_id,
|
|
239
|
+
SuperkgIngestRequest(
|
|
240
|
+
project_id=project_id, system_id=system_id, kind="component", file_path=file_path, language=language
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
return data.get("id") if data else None
|
|
244
|
+
|
|
245
|
+
async def ingest_object(
|
|
246
|
+
self,
|
|
247
|
+
project_id: str,
|
|
248
|
+
system_id: str,
|
|
249
|
+
component_id: str | None,
|
|
250
|
+
chunk: "CodeChunk",
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Register a CodeChunk's metadata as a SuperKG object (fire-and-forget).
|
|
253
|
+
|
|
254
|
+
Raw source (`chunk.body`) is never sent - net_guard.py forbids a
|
|
255
|
+
`body` field from crossing the client->server boundary.
|
|
256
|
+
"""
|
|
257
|
+
metadata = SuperkgChunkMetadata(
|
|
258
|
+
chunk_id=chunk.chunk_id,
|
|
259
|
+
project_id=chunk.project_id,
|
|
260
|
+
file_path=chunk.file_path,
|
|
261
|
+
language=chunk.language,
|
|
262
|
+
symbol=chunk.symbol,
|
|
263
|
+
kind=chunk.kind,
|
|
264
|
+
start_line=chunk.start_line,
|
|
265
|
+
end_line=chunk.end_line,
|
|
266
|
+
signature=chunk.signature,
|
|
267
|
+
git_sha=chunk.git_sha,
|
|
268
|
+
content_hash=chunk.content_hash,
|
|
269
|
+
)
|
|
270
|
+
await self._post(
|
|
271
|
+
"/ingest",
|
|
272
|
+
project_id,
|
|
273
|
+
SuperkgIngestRequest(
|
|
274
|
+
project_id=project_id, system_id=system_id, kind="object", component_id=component_id, chunk=metadata
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
async def ingest_repo_map(
|
|
279
|
+
self, project_id: str, system_id: str, repo_map: dict[str, Any], output_path: str | None = None
|
|
280
|
+
) -> None:
|
|
281
|
+
"""Register a generated repository map as project-level knowledge."""
|
|
282
|
+
await self._post(
|
|
283
|
+
"/ingest",
|
|
284
|
+
project_id,
|
|
285
|
+
SuperkgIngestRequest(
|
|
286
|
+
project_id=project_id,
|
|
287
|
+
system_id=system_id,
|
|
288
|
+
kind="repo_map",
|
|
289
|
+
repo_map=repo_map,
|
|
290
|
+
output_path=output_path,
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# ------------------------------------------------------------------
|
|
295
|
+
# Enrichment
|
|
296
|
+
# ------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
async def enrich(self, project_id: str, system_id: str, query: str, candidates: list[dict]) -> EnrichResult | None:
|
|
299
|
+
"""Call the enrich proxy with top reranked candidates. Returns None on timeout/failure/disabled."""
|
|
300
|
+
data = await self._post(
|
|
301
|
+
"/enrich",
|
|
302
|
+
project_id,
|
|
303
|
+
SuperkgEnrichRequest(project_id=project_id, system_id=system_id, query=query, candidates=candidates),
|
|
304
|
+
timeout=self._enrich_timeout,
|
|
305
|
+
)
|
|
306
|
+
if not data:
|
|
307
|
+
return None
|
|
308
|
+
raw_scores = data.get("kg_scores", {})
|
|
309
|
+
kg_scores = {
|
|
310
|
+
cid: KGScore(kg_score=float(v.get("kg_score", 0.0)), signals=v.get("signals", {}))
|
|
311
|
+
for cid, v in raw_scores.items()
|
|
312
|
+
}
|
|
313
|
+
return EnrichResult(
|
|
314
|
+
trace_id=data.get("trace_id", ""),
|
|
315
|
+
kg_scores=kg_scores,
|
|
316
|
+
pattern_suggestions=data.get("pattern_suggestions", []),
|
|
317
|
+
warnings=data.get("warnings", []),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# ------------------------------------------------------------------
|
|
321
|
+
# Health
|
|
322
|
+
# ------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
async def health(self) -> dict | None:
|
|
325
|
+
"""SuperKG proxy health isn't exposed as a distinct endpoint - reachability is
|
|
326
|
+
implied by any successful call above; callers should not depend on this."""
|
|
327
|
+
return None
|
|
File without changes
|