|
@@ -40,8 +40,7 @@ from script_build_host.domain.artifacts import (
|
|
|
DirectionPreference,
|
|
DirectionPreference,
|
|
|
EvidenceRecordV1,
|
|
EvidenceRecordV1,
|
|
|
)
|
|
)
|
|
|
-from script_build_host.domain.canonical_json import canonical_sha256
|
|
|
|
|
-from script_build_host.domain.errors import ArtifactNotFound, ProtocolViolation
|
|
|
|
|
|
|
+from script_build_host.domain.errors import ArtifactNotFound, ProtocolViolation, ScriptBuildError
|
|
|
from script_build_host.domain.ports import (
|
|
from script_build_host.domain.ports import (
|
|
|
InputSnapshotRepository,
|
|
InputSnapshotRepository,
|
|
|
MissionBindingRepository,
|
|
MissionBindingRepository,
|
|
@@ -86,10 +85,6 @@ class RetrievalAdapter(Protocol):
|
|
|
) -> RetrievalResult: ...
|
|
) -> RetrievalResult: ...
|
|
|
|
|
|
|
|
|
|
|
|
|
-class ImageAdapter(Protocol):
|
|
|
|
|
- async def load(self, *, urls: Sequence[str], snapshot: Any) -> tuple[dict[str, Any], ...]: ...
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
class DispatchGuard(Protocol):
|
|
class DispatchGuard(Protocol):
|
|
|
async def ensure_dispatch_allowed(self, script_build_id: int) -> None: ...
|
|
async def ensure_dispatch_allowed(self, script_build_id: int) -> None: ...
|
|
|
|
|
|
|
@@ -113,7 +108,6 @@ class LegacyScriptToolGateway:
|
|
|
artifacts: ScriptBusinessArtifactRepository,
|
|
artifacts: ScriptBusinessArtifactRepository,
|
|
|
coordinator: Any,
|
|
coordinator: Any,
|
|
|
retrieval_adapters: Mapping[str, RetrievalAdapter] | None = None,
|
|
retrieval_adapters: Mapping[str, RetrievalAdapter] | None = None,
|
|
|
- image_adapter: ImageAdapter | None = None,
|
|
|
|
|
dispatch_guard: DispatchGuard | None = None,
|
|
dispatch_guard: DispatchGuard | None = None,
|
|
|
planner_tools: ScriptPlannerToolPort | None = None,
|
|
planner_tools: ScriptPlannerToolPort | None = None,
|
|
|
candidate_tools: ScriptCandidateToolPort | None = None,
|
|
candidate_tools: ScriptCandidateToolPort | None = None,
|
|
@@ -126,7 +120,6 @@ class LegacyScriptToolGateway:
|
|
|
self.artifacts = artifacts
|
|
self.artifacts = artifacts
|
|
|
self.coordinator = coordinator
|
|
self.coordinator = coordinator
|
|
|
self.retrieval_adapters = dict(retrieval_adapters or {})
|
|
self.retrieval_adapters = dict(retrieval_adapters or {})
|
|
|
- self.image_adapter = image_adapter
|
|
|
|
|
self.dispatch_guard = dispatch_guard
|
|
self.dispatch_guard = dispatch_guard
|
|
|
self.planner_tools = planner_tools
|
|
self.planner_tools = planner_tools
|
|
|
self.candidate_tools = candidate_tools
|
|
self.candidate_tools = candidate_tools
|
|
@@ -199,25 +192,58 @@ class LegacyScriptToolGateway:
|
|
|
async def inspect_script_plan(self, *, context: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
async def inspect_script_plan(self, *, context: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
|
return await self._planner().inspect_script_plan(context=context)
|
|
return await self._planner().inspect_script_plan(context=context)
|
|
|
|
|
|
|
|
- async def read_workbench_detail(
|
|
|
|
|
|
|
+ async def search_mission_context(
|
|
|
self,
|
|
self,
|
|
|
*,
|
|
*,
|
|
|
- view: str,
|
|
|
|
|
- handle: str | None,
|
|
|
|
|
|
|
+ collection: str,
|
|
|
|
|
+ query: str,
|
|
|
|
|
+ goal_ids: Sequence[str],
|
|
|
|
|
+ task_kinds: Sequence[str],
|
|
|
|
|
+ source_types: Sequence[str],
|
|
|
known_revision: str | None,
|
|
known_revision: str | None,
|
|
|
- cursor: int,
|
|
|
|
|
|
|
+ cursor: str | None,
|
|
|
|
|
+ page_size: int,
|
|
|
context: Mapping[str, Any],
|
|
context: Mapping[str, Any],
|
|
|
) -> Mapping[str, Any]:
|
|
) -> Mapping[str, Any]:
|
|
|
if self.workbench is None:
|
|
if self.workbench is None:
|
|
|
- raise ProtocolViolation("Mission Workbench is not configured")
|
|
|
|
|
|
|
+ raise ProtocolViolation("Context Broker is not configured")
|
|
|
return cast(
|
|
return cast(
|
|
|
Mapping[str, Any],
|
|
Mapping[str, Any],
|
|
|
- await self.workbench.detail(
|
|
|
|
|
|
|
+ await self.workbench.search_mission_context(
|
|
|
root_trace_id=_required(context, "root_trace_id"),
|
|
root_trace_id=_required(context, "root_trace_id"),
|
|
|
- view=view,
|
|
|
|
|
- handle=handle,
|
|
|
|
|
|
|
+ role=str(context.get("role") or "agent"),
|
|
|
|
|
+ task_id=cast(str | None, context.get("task_id")),
|
|
|
|
|
+ collection=collection,
|
|
|
|
|
+ query=query,
|
|
|
|
|
+ goal_ids=goal_ids,
|
|
|
|
|
+ task_kinds=task_kinds,
|
|
|
|
|
+ source_types=source_types,
|
|
|
known_revision=known_revision,
|
|
known_revision=known_revision,
|
|
|
cursor=cursor,
|
|
cursor=cursor,
|
|
|
|
|
+ page_size=page_size,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ async def read_mission_context(
|
|
|
|
|
+ self,
|
|
|
|
|
+ *,
|
|
|
|
|
+ handle: str,
|
|
|
|
|
+ section: str,
|
|
|
|
|
+ cursor: str | None,
|
|
|
|
|
+ context: Mapping[str, Any],
|
|
|
|
|
+ ) -> Mapping[str, Any]:
|
|
|
|
|
+ if self.workbench is None:
|
|
|
|
|
+ raise ProtocolViolation("Context Broker is not configured")
|
|
|
|
|
+ return cast(
|
|
|
|
|
+ Mapping[str, Any],
|
|
|
|
|
+ await self.workbench.read_mission_context(
|
|
|
|
|
+ root_trace_id=_required(context, "root_trace_id"),
|
|
|
|
|
+ role=str(context.get("role") or "agent"),
|
|
|
|
|
+ task_id=cast(str | None, context.get("task_id")),
|
|
|
|
|
+ attempt_id=cast(str | None, context.get("attempt_id")),
|
|
|
|
|
+ handle=handle,
|
|
|
|
|
+ section=section,
|
|
|
|
|
+ cursor=cursor,
|
|
|
),
|
|
),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -231,35 +257,6 @@ class LegacyScriptToolGateway:
|
|
|
# immediately before it reserves the Operation.
|
|
# immediately before it reserves the Operation.
|
|
|
return await self._planner().dispatch_script_tasks(task_ids=task_ids, context=context)
|
|
return await self._planner().dispatch_script_tasks(task_ids=task_ids, context=context)
|
|
|
|
|
|
|
|
- async def read_input_snapshot(self, context: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
|
|
- binding, snapshot = await self._scope(context)
|
|
|
|
|
- payload = asdict(snapshot)
|
|
|
|
|
- payload["script_build_id"] = binding.script_build_id
|
|
|
|
|
- payload["strategies"] = [
|
|
|
|
|
- (
|
|
|
|
|
- item
|
|
|
|
|
- if item.get("mode") == "always_on"
|
|
|
|
|
- else {key: value for key, value in item.items() if key != "content"}
|
|
|
|
|
- )
|
|
|
|
|
- for item in payload.get("strategies", [])
|
|
|
|
|
- ]
|
|
|
|
|
- payload["prompt_manifest"] = [
|
|
|
|
|
- {key: value for key, value in item.items() if key != "content"}
|
|
|
|
|
- for item in payload.get("prompt_manifest", [])
|
|
|
|
|
- ]
|
|
|
|
|
- view = await self._snapshot_view(context)
|
|
|
|
|
- return cast(dict[str, Any], _json_values(_project_snapshot(payload, view=view)))
|
|
|
|
|
-
|
|
|
|
|
- async def _snapshot_view(self, context: Mapping[str, Any]) -> str:
|
|
|
|
|
- task_id = context.get("task_id")
|
|
|
|
|
- if not isinstance(task_id, str) or not task_id.strip():
|
|
|
|
|
- return "planner"
|
|
|
|
|
- ledger = await self.coordinator.task_store.load(_required(context, "root_trace_id"))
|
|
|
|
|
- task = ledger.tasks.get(task_id)
|
|
|
|
|
- if task is None:
|
|
|
|
|
- raise ProtocolViolation("protected Task is not in the mission ledger")
|
|
|
|
|
- return task_kind(task.current_spec.context_refs) or "unknown"
|
|
|
|
|
-
|
|
|
|
|
async def read_accepted_portfolio(self, context: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
async def read_accepted_portfolio(self, context: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
|
return await self._root_tools().read_accepted_portfolio(context=context)
|
|
return await self._root_tools().read_accepted_portfolio(context=context)
|
|
|
|
|
|
|
@@ -289,60 +286,6 @@ class LegacyScriptToolGateway:
|
|
|
),
|
|
),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- async def load_frozen_strategy(
|
|
|
|
|
- self,
|
|
|
|
|
- selected_handle: str,
|
|
|
|
|
- context: Mapping[str, Any],
|
|
|
|
|
- *,
|
|
|
|
|
- cursor: int = 0,
|
|
|
|
|
- ) -> dict[str, Any]:
|
|
|
|
|
- """Explicitly load one digest-verified on-demand strategy from the bound snapshot."""
|
|
|
|
|
-
|
|
|
|
|
- binding, snapshot = await self._scope(context)
|
|
|
|
|
- ledger = await self.coordinator.task_store.load(_required(context, "root_trace_id"))
|
|
|
|
|
- task = ledger.tasks.get(_required(context, "task_id"))
|
|
|
|
|
- expected_input_ref = f"script-build://inputs/{binding.input_snapshot_id}"
|
|
|
|
|
- if task is None or {
|
|
|
|
|
- item
|
|
|
|
|
- for item in task.current_spec.context_refs
|
|
|
|
|
- if item.startswith("script-build://inputs/")
|
|
|
|
|
- } != {expected_input_ref}:
|
|
|
|
|
- raise ProtocolViolation("current task is not bound to the frozen InputSnapshot")
|
|
|
|
|
- matches: list[dict[str, Any]] = []
|
|
|
|
|
- for index, item in enumerate(snapshot.strategies):
|
|
|
|
|
- if (
|
|
|
|
|
- strategy_handle(snapshot.snapshot_id, index, item) == selected_handle
|
|
|
|
|
- and item.get("mode") == "on_demand"
|
|
|
|
|
- ):
|
|
|
|
|
- matches.append(item)
|
|
|
|
|
- if len(matches) != 1:
|
|
|
|
|
- raise ValueError("strategy_handle must select one frozen on-demand strategy")
|
|
|
|
|
- item = matches[0]
|
|
|
|
|
- content = item.get("content")
|
|
|
|
|
- digest = item.get("content_sha256")
|
|
|
|
|
- if not isinstance(content, str) or canonical_sha256(content).wire != digest:
|
|
|
|
|
- raise ProtocolViolation("frozen on-demand strategy digest does not match")
|
|
|
|
|
- start = max(cursor, 0)
|
|
|
|
|
- fragment = content[start : start + 7_000]
|
|
|
|
|
- metadata = {
|
|
|
|
|
- key: value
|
|
|
|
|
- for key, value in item.items()
|
|
|
|
|
- if key not in {"content", "content_sha256", "sha256", "source_uri"}
|
|
|
|
|
- }
|
|
|
|
|
- return cast(
|
|
|
|
|
- dict[str, Any],
|
|
|
|
|
- _json_values(
|
|
|
|
|
- {
|
|
|
|
|
- "strategy_handle": selected_handle,
|
|
|
|
|
- "metadata": metadata,
|
|
|
|
|
- "content_fragment": fragment,
|
|
|
|
|
- "next_cursor": (
|
|
|
|
|
- start + len(fragment) if start + len(fragment) < len(content) else None
|
|
|
|
|
- ),
|
|
|
|
|
- }
|
|
|
|
|
- ),
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
async def read_accepted_artifacts(self, context: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
async def read_accepted_artifacts(self, context: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
|
binding, _ = await self._scope(context)
|
|
binding, _ = await self._scope(context)
|
|
|
ledger = await self.coordinator.task_store.load(binding.root_trace_id)
|
|
ledger = await self.coordinator.task_store.load(binding.root_trace_id)
|
|
@@ -496,20 +439,19 @@ class LegacyScriptToolGateway:
|
|
|
artifact=record,
|
|
artifact=record,
|
|
|
)
|
|
)
|
|
|
return {
|
|
return {
|
|
|
- "artifact_ref": _ref_dict(ref),
|
|
|
|
|
- "artifact_version_id": version.artifact_version_id,
|
|
|
|
|
- "evidence": _json_values(asdict(version.artifact)),
|
|
|
|
|
- "metadata": _json_values(redact(dict(result.metadata or {}))),
|
|
|
|
|
|
|
+ "evidence_handle": semantic_handle(
|
|
|
|
|
+ "evidence",
|
|
|
|
|
+ _required(context, "root_trace_id"),
|
|
|
|
|
+ _required(context, "task_id"),
|
|
|
|
|
+ ref.digest or ref.version or "",
|
|
|
|
|
+ ),
|
|
|
|
|
+ "evidence": protect_model_value(
|
|
|
|
|
+ _json_values(asdict(version.artifact)),
|
|
|
|
|
+ namespace=_required(context, "attempt_id"),
|
|
|
|
|
+ ),
|
|
|
|
|
+ "source_count": len(result.source_refs),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async def load_images(
|
|
|
|
|
- self, urls: Sequence[str], context: Mapping[str, Any]
|
|
|
|
|
- ) -> tuple[dict[str, Any], ...]:
|
|
|
|
|
- if self.image_adapter is None:
|
|
|
|
|
- raise ProtocolViolation("image adapter is not configured")
|
|
|
|
|
- _, snapshot = await self._scope(context)
|
|
|
|
|
- return await self.image_adapter.load(urls=urls, snapshot=snapshot)
|
|
|
|
|
-
|
|
|
|
|
async def view_frozen_images(
|
|
async def view_frozen_images(
|
|
|
self, image_handles: Sequence[str], context: Mapping[str, Any]
|
|
self, image_handles: Sequence[str], context: Mapping[str, Any]
|
|
|
) -> Sequence[Mapping[str, Any]]:
|
|
) -> Sequence[Mapping[str, Any]]:
|
|
@@ -518,20 +460,6 @@ class LegacyScriptToolGateway:
|
|
|
context=context,
|
|
context=context,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- async def read_accepted_input_bundle(self, context: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
|
|
|
- return await self._candidates().read_accepted_input_bundle(context=context)
|
|
|
|
|
-
|
|
|
|
|
- async def read_active_frontier(self, context: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
|
|
|
- return await self._candidates().read_active_frontier(context=context)
|
|
|
|
|
-
|
|
|
|
|
- async def read_pinned_candidates(
|
|
|
|
|
- self, context: Mapping[str, Any]
|
|
|
|
|
- ) -> Sequence[Mapping[str, Any]]:
|
|
|
|
|
- return await self._candidates().read_pinned_candidates(context=context)
|
|
|
|
|
-
|
|
|
|
|
- async def read_attempt_workspace(self, context: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
|
|
|
- return await self._candidates().read_attempt_workspace(context=context)
|
|
|
|
|
-
|
|
|
|
|
async def candidate_command(
|
|
async def candidate_command(
|
|
|
self,
|
|
self,
|
|
|
operation: str,
|
|
operation: str,
|
|
@@ -719,6 +647,17 @@ class LegacyScriptToolGateway:
|
|
|
if set(result_by_id) != expected_ids:
|
|
if set(result_by_id) != expected_ids:
|
|
|
raise ProtocolViolation("validation must report every Task criterion exactly once")
|
|
raise ProtocolViolation("validation must report every Task criterion exactly once")
|
|
|
overall = ValidationVerdict(verdict)
|
|
overall = ValidationVerdict(verdict)
|
|
|
|
|
+ if overall is ValidationVerdict.PASSED and self.workbench is not None:
|
|
|
|
|
+ try:
|
|
|
|
|
+ await self.workbench.verify_context_exhausted(
|
|
|
|
|
+ root_trace_id=root_trace_id,
|
|
|
|
|
+ task_id=task_id,
|
|
|
|
|
+ attempt_id=_required(context, "attempt_id"),
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as exc:
|
|
|
|
|
+ if getattr(exc, "code", None) != "CONTEXT_NOT_EXHAUSTED":
|
|
|
|
|
+ raise
|
|
|
|
|
+ raise ScriptBuildError("CONTEXT_NOT_EXHAUSTED", str(exc)) from exc
|
|
|
blocking = [item for item in normalized_defects if item["severity"] in {"hard", "critical"}]
|
|
blocking = [item for item in normalized_defects if item["severity"] in {"hard", "critical"}]
|
|
|
if overall is ValidationVerdict.PASSED and blocking:
|
|
if overall is ValidationVerdict.PASSED and blocking:
|
|
|
raise ProtocolViolation("passed validation cannot contain hard or critical defects")
|
|
raise ProtocolViolation("passed validation cannot contain hard or critical defects")
|
|
@@ -890,9 +829,16 @@ class LegacyScriptToolGateway:
|
|
|
artifact=artifact,
|
|
artifact=artifact,
|
|
|
)
|
|
)
|
|
|
return {
|
|
return {
|
|
|
- "artifact_ref": _ref_dict(ref),
|
|
|
|
|
- "artifact_version_id": version.artifact_version_id,
|
|
|
|
|
- "direction": _json_values(version.artifact.content_payload()),
|
|
|
|
|
|
|
+ "direction_handle": semantic_handle(
|
|
|
|
|
+ "direction",
|
|
|
|
|
+ _required(context, "root_trace_id"),
|
|
|
|
|
+ _required(context, "task_id"),
|
|
|
|
|
+ ref.digest or ref.version or "",
|
|
|
|
|
+ ),
|
|
|
|
|
+ "direction": protect_model_value(
|
|
|
|
|
+ _json_values(version.artifact.content_payload()),
|
|
|
|
|
+ namespace=_required(context, "attempt_id"),
|
|
|
|
|
+ ),
|
|
|
"goal_ids_by_client_key": goal_ids_by_client_key,
|
|
"goal_ids_by_client_key": goal_ids_by_client_key,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -987,150 +933,6 @@ class LegacyScriptToolGateway:
|
|
|
return self.root_tools
|
|
return self.root_tools
|
|
|
|
|
|
|
|
|
|
|
|
|
-_SNAPSHOT_IDENTITY_FIELDS = (
|
|
|
|
|
- "snapshot_id",
|
|
|
|
|
- "script_build_id",
|
|
|
|
|
- "execution_id",
|
|
|
|
|
- "topic_build_id",
|
|
|
|
|
- "topic_id",
|
|
|
|
|
- "canonical_sha256",
|
|
|
|
|
- "business_input_sha256",
|
|
|
|
|
- "parent_snapshot_id",
|
|
|
|
|
- "parent_snapshot_sha256",
|
|
|
|
|
- "created_at",
|
|
|
|
|
-)
|
|
|
|
|
-_TOPIC_ITEM_FIELDS = (
|
|
|
|
|
- "point_type",
|
|
|
|
|
- "dimension",
|
|
|
|
|
- "element_name",
|
|
|
|
|
- "note",
|
|
|
|
|
- "category_path",
|
|
|
|
|
- "item_level",
|
|
|
|
|
- "derivation_type",
|
|
|
|
|
- "content",
|
|
|
|
|
- "result",
|
|
|
|
|
- "name",
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def _project_snapshot(payload: Mapping[str, Any], *, view: str) -> dict[str, Any]:
|
|
|
|
|
- """Project immutable inputs into a role-sized business view."""
|
|
|
|
|
-
|
|
|
|
|
- persona_limits = {
|
|
|
|
|
- "planner": 24,
|
|
|
|
|
- "direction": 32,
|
|
|
|
|
- "pattern-retrieval": 16,
|
|
|
|
|
- "structure": 24,
|
|
|
|
|
- "paragraph": 12,
|
|
|
|
|
- "element-set": 12,
|
|
|
|
|
- "root-delivery": 16,
|
|
|
|
|
- }
|
|
|
|
|
- persona = list(payload.get("persona_points") or ())
|
|
|
|
|
- section_patterns = list(payload.get("section_patterns") or ())
|
|
|
|
|
- output = {key: payload.get(key) for key in _SNAPSHOT_IDENTITY_FIELDS if key in payload}
|
|
|
|
|
- output.update(
|
|
|
|
|
- {
|
|
|
|
|
- "view": view,
|
|
|
|
|
- "account": payload.get("account") or {},
|
|
|
|
|
- "topic": _project_topic(payload.get("topic")),
|
|
|
|
|
- "strategies": list(payload.get("strategies") or ()),
|
|
|
|
|
- "persona_points_total": len(persona),
|
|
|
|
|
- "section_patterns_total": len(section_patterns),
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- limit = persona_limits.get(view, 16)
|
|
|
|
|
- if limit:
|
|
|
|
|
- output["persona_points"] = _select_persona_points(persona, limit=limit)
|
|
|
|
|
- if view in {
|
|
|
|
|
- "planner",
|
|
|
|
|
- "direction",
|
|
|
|
|
- "pattern-retrieval",
|
|
|
|
|
- "structure",
|
|
|
|
|
- "paragraph",
|
|
|
|
|
- "element-set",
|
|
|
|
|
- "root-delivery",
|
|
|
|
|
- }:
|
|
|
|
|
- output["section_patterns"] = section_patterns
|
|
|
|
|
- return output
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def _project_topic(raw: Any) -> dict[str, Any]:
|
|
|
|
|
- if not isinstance(raw, Mapping):
|
|
|
|
|
- return {}
|
|
|
|
|
- topic = raw.get("topic")
|
|
|
|
|
- topic_row = dict(topic) if isinstance(topic, Mapping) else {}
|
|
|
|
|
- projected: dict[str, Any] = {
|
|
|
|
|
- "topic": {
|
|
|
|
|
- key: topic_row[key]
|
|
|
|
|
- for key in (
|
|
|
|
|
- "result",
|
|
|
|
|
- "topic_direction",
|
|
|
|
|
- "topic_understanding",
|
|
|
|
|
- "inspiration_evaluation_config",
|
|
|
|
|
- )
|
|
|
|
|
- if key in topic_row
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- for collection in ("composition_items", "points", "relations", "sources"):
|
|
|
|
|
- values = raw.get(collection)
|
|
|
|
|
- if not isinstance(values, Sequence) or isinstance(values, (str, bytes)):
|
|
|
|
|
- continue
|
|
|
|
|
- rows = []
|
|
|
|
|
- for value in values:
|
|
|
|
|
- if not isinstance(value, Mapping) or value.get("is_active") is False:
|
|
|
|
|
- continue
|
|
|
|
|
- rows.append({key: value[key] for key in _TOPIC_ITEM_FIELDS if key in value})
|
|
|
|
|
- if rows:
|
|
|
|
|
- projected[collection] = rows
|
|
|
|
|
- return projected
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def _select_persona_points(values: Sequence[Any], *, limit: int) -> list[dict[str, Any]]:
|
|
|
|
|
- groups: dict[str, list[Mapping[str, Any]]] = {}
|
|
|
|
|
- for value in values:
|
|
|
|
|
- if isinstance(value, Mapping):
|
|
|
|
|
- groups.setdefault(str(value.get("列") or "其他"), []).append(value)
|
|
|
|
|
- for rows in groups.values():
|
|
|
|
|
- rows.sort(
|
|
|
|
|
- key=lambda item: (
|
|
|
|
|
- -_numeric(item.get("人设权重分")),
|
|
|
|
|
- -_numeric(item.get("帖子覆盖率")),
|
|
|
|
|
- str(item.get("点名称") or ""),
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- selected: list[dict[str, Any]] = []
|
|
|
|
|
- group_names = sorted(groups)
|
|
|
|
|
- while len(selected) < limit and group_names:
|
|
|
|
|
- remaining = []
|
|
|
|
|
- for name in group_names:
|
|
|
|
|
- rows = groups[name]
|
|
|
|
|
- if rows and len(selected) < limit:
|
|
|
|
|
- selected.append(_project_persona_point(rows.pop(0)))
|
|
|
|
|
- if rows:
|
|
|
|
|
- remaining.append(name)
|
|
|
|
|
- group_names = remaining
|
|
|
|
|
- return selected
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def _project_persona_point(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
|
|
- output = {
|
|
|
|
|
- key: value[key]
|
|
|
|
|
- for key in ("列", "点名称", "点类型", "人设权重分", "帖子覆盖率")
|
|
|
|
|
- if key in value
|
|
|
|
|
- }
|
|
|
|
|
- paths = value.get("分类路径")
|
|
|
|
|
- if isinstance(paths, Sequence) and not isinstance(paths, (str, bytes)):
|
|
|
|
|
- output["分类路径"] = [str(item)[:160] for item in paths[:2]]
|
|
|
|
|
- return output
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-def _numeric(value: Any) -> float:
|
|
|
|
|
- try:
|
|
|
|
|
- return float(value)
|
|
|
|
|
- except (TypeError, ValueError):
|
|
|
|
|
- return 0.0
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
def _required(context: Mapping[str, Any], key: str) -> str:
|
|
def _required(context: Mapping[str, Any], key: str) -> str:
|
|
|
value = context.get(key)
|
|
value = context.get(key)
|
|
|
if not isinstance(value, str) or not value.strip():
|
|
if not isinstance(value, str) or not value.strip():
|
|
@@ -1416,7 +1218,6 @@ def _dedupe_refs(values: Any) -> tuple[ArtifactRef, ...]:
|
|
|
|
|
|
|
|
__all__ = [
|
|
__all__ = [
|
|
|
"DispatchGuard",
|
|
"DispatchGuard",
|
|
|
- "ImageAdapter",
|
|
|
|
|
"LegacyScriptToolGateway",
|
|
"LegacyScriptToolGateway",
|
|
|
"RetrievalAdapter",
|
|
"RetrievalAdapter",
|
|
|
"RetrievalResult",
|
|
"RetrievalResult",
|