Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## v4.6.32 - 2026-08-15

- Separated live serving availability from deployment-lock identity so an
unavailable inference endpoint is reported as a configured target rather
than falsely described as currently serving.
- Aligned deterministic runtime answers, `used_model`, Support labels, health
metadata, and operational acceptance gates around the same availability
snapshot, including timeout and missing-runtime cases.

## v4.6.31 - 2026-08-15

- Added a trusted-main-only Ascend host regression workflow backed by a
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "sage-mate"
version = "4.6.31"
version = "4.6.32"
description = "Sage Mate multi-profile local and hosted assistant built on SAGE and vllm-hust"
readme = "README.md"
license = {file = "LICENSE"}
Expand Down
2 changes: 1 addition & 1 deletion src/sage_faculty_twin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Sage Mate application package."""

__version__ = "4.6.31"
__version__ = "4.6.32"

__all__ = ["__version__"]
21 changes: 17 additions & 4 deletions src/sage_faculty_twin/operational_acceptance.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class OperationalExpectedFacts:
speculative_enabled: str
speculative_method: str
speculative_reason: str
runtime_status: str = "live"
runtime_available: bool = True
forbidden_facts: tuple[str, ...] = ()

Expand All @@ -86,7 +87,8 @@ def from_health(cls, health: dict[str, Any]) -> OperationalExpectedFacts:
speculative_enabled=_text(health.get("runtime_speculative_enabled")),
speculative_method=_text(health.get("runtime_speculative_method")),
speculative_reason=_text(health.get("runtime_speculative_reason")),
runtime_available=status in {"live", "receipt"},
runtime_status=status,
runtime_available=status == "live",
forbidden_facts=forbidden,
)

Expand All @@ -102,13 +104,13 @@ def from_dict(cls, payload: dict[str, Any]) -> OperationalExpectedFacts:
return cls(**values)

def required_aliases(self) -> dict[str, tuple[str, ...]]:
if not self.runtime_available:
if self.runtime_status == "unknown":
return {
"uncertainty": ("unavailable", "unknown", "无法", "未知", "不会猜测")
}
ep = self.expert_parallel_enabled.lower()
speculative = self.speculative_enabled.lower()
return {
aliases = {
"model": _aliases(self.model),
"architecture": _aliases(self.architecture),
"accelerator": _aliases(
Expand All @@ -135,6 +137,15 @@ def required_aliases(self) -> dict[str, tuple[str, ...]]:
self.speculative_method,
),
}
if not self.runtime_available:
aliases["availability"] = (
"configured deployment target",
"not evidence that the engine is currently serving",
"部署锁定目标",
"不代表引擎当前正在提供推理",
"不可用或尚未验证",
)
return aliases


def _normalize(value: object) -> str:
Expand Down Expand Up @@ -190,7 +201,9 @@ def evaluate_operational_response(
]
support_ok = bool(runtime_hits and body.get("answer_basis"))
used_model_ok = (
body.get("used_model") == expected.model if expected.runtime_available else True
body.get("used_model") == expected.model
if expected.runtime_available
else body.get("used_model") == "runtime-identity-provider"
)
route_ok = body.get("decision_mode") == "runtime_identity"
timing = body.get("request_timing") or {}
Expand Down
74 changes: 73 additions & 1 deletion src/sage_faculty_twin/runtime_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class RuntimeIdentity:
status: str
source: str
collected_at: str
serving_available: bool = False
served_model: str = "unknown"
checkpoint_family: str = "unknown"
architecture: str = "unknown"
Expand Down Expand Up @@ -107,8 +108,11 @@ def evidence_excerpt(self) -> str:
if self.speculative_enabled
else f"未启用(能力:{self.speculative_capability};原因:{self.speculative_reason})"
)
model_role = "served" if self.serving_available else "configured"
availability = "live" if self.serving_available else "unavailable-or-unverified"
return (
f"served model={self.served_model};checkpoint={self.checkpoint_family} / "
f"{model_role} model={self.served_model};availability={availability};"
f"checkpoint={self.checkpoint_family} / "
f"{self.architecture};engine={self.engine} {self.engine_version};"
f"plugin={self.plugin_version};accelerator={devices};{parallel};"
f"quantization={self.quantization};execution={self.graph_mode};"
Expand All @@ -125,6 +129,7 @@ def to_knowledge_hit(self) -> KnowledgeSearchHit:
source_name=f"runtime:{self.source}",
metadata={
"runtime_status": self.status,
"serving_available": str(self.serving_available).lower(),
"collected_at": self.collected_at,
"source_kind": self.source,
},
Expand Down Expand Up @@ -224,6 +229,7 @@ def snapshot(self) -> RuntimeIdentity:
status=status,
source=source,
collected_at=now,
serving_available=bool(live_model),
served_model=served_model or "unknown",
checkpoint_family=family or "unknown",
architecture=architecture,
Expand Down Expand Up @@ -376,6 +382,72 @@ def render_runtime_identity_answer(identity: RuntimeIdentity, *, english: bool =
if english
else "当前无法读取实时运行时身份。我不会猜测模型、硬件或引擎;请在推理服务恢复后重试。"
)
if not identity.serving_available:
configured_devices = (
f"{identity.device_count}× {identity.accelerator_model}"
if identity.device_count
else f"unknown× {identity.accelerator_model}"
)
configured_tp = identity.tensor_parallel_size or "unknown"
configured_dp = identity.data_parallel_size or "unknown"
configured_ep = (
"on"
if identity.expert_parallel_enabled is True
else "off"
if identity.expert_parallel_enabled is False
else "unknown"
)
configured_speculative = (
f"enabled ({identity.speculative_method})"
if identity.speculative_enabled
else f"not enabled ({identity.speculative_reason})"
)
configured_accelerator_lower = identity.accelerator_model.lower()
configured_is_ascend = any(
marker in configured_accelerator_lower
for marker in ("ascend", "910", "npu")
)
if english:
platform_relation = (
"the Ascend plugin maps engine execution to the NPU runtime"
if configured_is_ascend
else "the selected platform backend maps engine execution to the accelerator runtime"
)
return (
f"The configured deployment target is **{identity.served_model}** with "
f"{identity.engine}, but the live serving endpoint is currently unavailable "
f"or has not been verified. The recorded target describes "
f"{identity.checkpoint_family} / {identity.architecture} on {configured_devices}, "
f"with TP={configured_tp}, DP={configured_dp}, EP={configured_ep}, "
f"quantization={identity.quantization}, execution={identity.graph_mode}, and "
f"speculative decoding {configured_speculative}. In the configured architecture, "
f"SAGE orchestrates the application workflow, vLLM-HUST is the serving engine, "
f"and {platform_relation}. These are deployment configuration facts from "
f"{identity.source}, not evidence that the engine is currently serving. "
f"Collected at {identity.collected_at}."
)
configured_speculative_zh = (
f"已启用({identity.speculative_method})"
if identity.speculative_enabled
else f"未启用({identity.speculative_reason})"
)
configured_platform_relation_zh = (
"Ascend 插件把引擎执行映射到 NPU 运行时"
if configured_is_ascend
else "所选平台后端把引擎执行映射到加速器运行时"
)
return (
f"当前无法从实时 serving endpoint 确认推理引擎在线。部署锁定目标是 "
f"**{identity.served_model}**,引擎类型为 {identity.engine};记录的检查点族/架构为 "
f"{identity.checkpoint_family} / {identity.architecture},目标硬件配置为 {configured_devices},"
f"TP={configured_tp}、DP={configured_dp}、EP={configured_ep},量化方式为 "
f"{identity.quantization},执行模式为 {identity.graph_mode},记录中的 speculative "
f"decoding {configured_speculative_zh}。在该配置架构中,SAGE 组织应用工作流,"
f"vLLM-HUST 是推理服务引擎,{configured_platform_relation_zh}。"
f"这些信息来自 {identity.source} 的部署配置,"
f"不代表引擎当前正在提供推理;当前状态是不可用或尚未验证。"
f"采集时间为 {identity.collected_at}。"
)
devices = f"{identity.device_count}× {identity.accelerator_model}" if identity.device_count else "unknown"
tp = identity.tensor_parallel_size or "unknown"
dp = identity.data_parallel_size or "unknown"
Expand Down
14 changes: 12 additions & 2 deletions src/sage_faculty_twin/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1363,8 +1363,10 @@ def retrieve_knowledge(self, context: ChatWorkflowContext) -> ChatWorkflowContex
receipt_hit = self._deployment_receipt_store.knowledge_hit()
if receipt_hit is not None:
context.knowledge_hits.append(receipt_hit)
if identity.served_model != "unknown":
if identity.serving_available and identity.served_model != "unknown":
context.used_model = identity.served_model
else:
context.used_model = "runtime-identity-provider"
self._append_trace(
context,
key="knowledge_retrieve",
Expand Down Expand Up @@ -6500,8 +6502,13 @@ def _build_attachment_artifact_hits(self, request: ChatRequest) -> list[Conversa
def _build_knowledge_basis_item(self, hit: KnowledgeSearchHit) -> AnswerBasisItem:
if "runtime" in {tag.lower() for tag in hit.tags}:
collected_at = hit.metadata.get("collected_at", "unknown")
serving_available = (
str(hit.metadata.get("serving_available") or "").lower() == "true"
)
return AnswerBasisItem(
basis_label="实时运行状态",
basis_label=(
"实时运行状态" if serving_available else "部署配置与可用性"
),
title=self._clip_basis_text(hit.title, 256),
source_label=self._clip_basis_text(
f"{hit.source_name or 'runtime'} · {collected_at}", 256
Expand Down Expand Up @@ -10229,6 +10236,9 @@ def health(self) -> dict[str, str]:
"runtime_identity_status": runtime_identity.status,
"runtime_identity_source": runtime_identity.source,
"runtime_identity_collected_at": runtime_identity.collected_at,
"runtime_serving_available": str(
runtime_identity.serving_available
).lower(),
"runtime_checkpoint_family": runtime_identity.checkpoint_family,
"runtime_architecture": runtime_identity.architecture,
"runtime_engine": runtime_identity.engine,
Expand Down
4 changes: 3 additions & 1 deletion tests/test_deployment_receipts.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def test_runtime_identity_precedence_live_receipt_lock(tmp_path: Path) -> None:
receipt_identity = provider.snapshot()
assert receipt_identity.source == "deployment-receipt"
assert receipt_identity.served_model == "receipt-model"
assert receipt_identity.serving_available is False
assert receipt_identity.tensor_parallel_size == 4
assert receipt_identity.checkpoint_family == "deepseek_v4"
live["value"] = "live-model"
Expand Down Expand Up @@ -249,7 +250,8 @@ def test_runtime_question_cites_receipt_and_maintenance_reports_sync(
receipt_hits = [
hit for hit in response.knowledge_hits if "deployment-receipt" in hit.tags
]
assert response.used_model == "deepseek/citation-fixture"
assert response.used_model == "runtime-identity-provider"
assert "不代表引擎当前正在提供推理" in response.answer
assert (
receipt_hits and receipt_hits[0].metadata["receipt_id"] == receipt["receipt_id"]
)
Expand Down
15 changes: 13 additions & 2 deletions tests/test_operational_acceptance.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,20 @@ def _body(expected: OperationalExpectedFacts, *, answer: str | None = None) -> d
f"quantization {expected.quantization}; execution {expected.graph_mode}; "
f"speculative decoding {'enabled' if enabled else 'not enabled'} "
f"({expected.speculative_method})."
+ (
" This is the configured deployment target and is not evidence that the engine "
"is currently serving."
if not expected.runtime_available
else ""
)
)
return {
"answer": rendered,
"used_model": expected.model,
"used_model": (
expected.model
if expected.runtime_available
else "runtime-identity-provider"
),
"decision_mode": "runtime_identity",
"knowledge_hits": [
{
Expand Down Expand Up @@ -127,6 +137,7 @@ def test_production_renderer_passes_independent_fixture_gate(health: dict) -> No
source="fixture",
collected_at="2026-08-15T00:00:00+00:00",
served_model=expected.model,
serving_available=expected.runtime_available,
checkpoint_family="fixture-family",
architecture=expected.architecture,
accelerator_model=expected.accelerator,
Expand Down Expand Up @@ -190,7 +201,7 @@ def test_no_runtime_requires_explicit_uncertainty_not_gpu_guess() -> None:
{"runtime_identity_status": "unknown"}
)
body = _body(expected, answer="当前运行时身份无法读取,我不会猜测模型和硬件。")
body["used_model"] = "configured-client-model"
body["used_model"] = "runtime-identity-provider"
result = evaluate_operational_response(
question="当前模型是什么?",
expected=expected,
Expand Down
84 changes: 84 additions & 0 deletions tests/test_runtime_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,35 @@ def test_deployment_receipt_is_used_when_live_probe_fails(tmp_path: Path) -> Non
assert identity.status == "receipt"
assert identity.source == "deployment-lock"
assert identity.served_model == "receipt-model"
assert identity.serving_available is False
assert identity.device_count == 4
answer = render_runtime_identity_answer(identity)
assert "部署锁定目标" in answer
assert "不代表引擎当前正在提供推理" in answer


def test_probe_error_keeps_configuration_but_marks_serving_unavailable(
tmp_path: Path,
) -> None:
(tmp_path / "engine-deployment.lock.env").write_text(
"VLLM_ENGINE_SERVED_MODEL_NAME=locked-model\n"
"VLLM_ENGINE_NPU_DEVICES=0\\,1\n",
encoding="utf-8",
)
provider = RuntimeIdentityProvider(
AppSettings(runtime_dir=tmp_path),
model_probe=lambda: (_ for _ in ()).throw(TimeoutError("probe timed out")),
versions_provider=lambda: {},
hardware_provider=lambda: {},
environ={},
)

identity = provider.snapshot()

assert identity.status == "receipt"
assert identity.serving_available is False
assert identity.served_model == "locked-model"
assert "当前无法从实时 serving endpoint 确认" in render_runtime_identity_answer(identity)


def test_unknown_runtime_never_guesses_cuda_or_gpu(tmp_path: Path) -> None:
Expand All @@ -207,6 +235,7 @@ def test_unknown_runtime_never_guesses_cuda_or_gpu(tmp_path: Path) -> None:
answer = render_runtime_identity_answer(identity)

assert identity.status == "unknown"
assert identity.serving_available is False
assert "不会猜测" in answer
assert "CUDA" not in answer
assert "GPU" not in answer
Expand Down Expand Up @@ -279,4 +308,59 @@ def test_end_to_end_chat_uses_runtime_evidence_without_llm_facts(tmp_path: Path)
health = service.health()
assert health["model_name"] == response.used_model
assert health["runtime_device_count"] == "8"
assert health["runtime_serving_available"] == "true"
assert str(tmp_path) not in json.dumps(response.model_dump(), ensure_ascii=False)


def test_end_to_end_receipt_answer_does_not_claim_live_serving(tmp_path: Path) -> None:
(tmp_path / "engine-deployment.lock.env").write_text(
"VLLM_ENGINE_SERVED_MODEL_NAME=locked-model\n"
"VLLM_ENGINE_NPU_DEVICES=0\\,1\n"
"VLLM_ENGINE_TP_SIZE=2\n",
encoding="utf-8",
)
settings = AppSettings(
runtime_dir=tmp_path,
knowledge_base_dir=tmp_path / "knowledge",
conversation_memory_dir=tmp_path / "memory",
chat_runtime_pipeline_enabled=False,
)
service = DigitalTwinService(settings)
service._runtime_identity_provider = RuntimeIdentityProvider(
settings,
model_probe=lambda: "",
versions_provider=lambda: {},
hardware_provider=lambda: {},
environ={},
)
service._llm_client.classify_interaction_intent_sync = lambda *_args, **_kwargs: (
InteractionIntent(
action="answer",
domain="general",
retrieval_scopes=[],
exclude_scopes=[],
decision_mode="direct_answer",
confidence=1.0,
)
)

response = asyncio.run(
service.answer_in_process(
ChatRequest(
student_name="guest",
question="当前运行的模型和 NPU 信息是什么?",
visitor_profile="general_visitor",
)
)
)

assert response.used_model == "runtime-identity-provider"
assert "部署锁定目标" in response.answer
assert "不代表引擎当前正在提供推理" in response.answer
assert "当前后端实际提供" not in response.answer
assert response.answer_basis[0].basis_label == "部署配置与可用性"
assert response.knowledge_hits[0].metadata["serving_available"] == "false"
health = service.health()
assert health["model_name"] == "locked-model"
assert health["runtime_identity_status"] == "receipt"
assert health["runtime_serving_available"] == "false"
Loading
Loading