feat(api): expose agent log intelligence readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
This commit is contained in:
@@ -97,6 +97,9 @@ from src.services.ai_agent_interaction_learning_proof import (
|
||||
from src.services.ai_agent_learning_writeback_approval_package import (
|
||||
load_latest_ai_agent_learning_writeback_approval_package,
|
||||
)
|
||||
from src.services.ai_agent_log_intelligence_integration_readback import (
|
||||
load_latest_ai_agent_log_intelligence_integration_readback,
|
||||
)
|
||||
from src.services.ai_agent_live_read_model_gate import (
|
||||
load_latest_ai_agent_live_read_model_gate,
|
||||
)
|
||||
@@ -1800,6 +1803,37 @@ async def get_agent_learning_writeback_approval_package() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-log-intelligence-integration-readback",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 AI Agent LOG / KM / RAG / MCP / PlayBook 整合 readback",
|
||||
description=(
|
||||
"讀取目前 API image 內已提交的 LOG intelligence source refs,彙總服務日誌、"
|
||||
"分類摘要、KM/RAG、MCP audit、PlayBook learning 與 AI Agent 決策 runtime 的串接面。"
|
||||
"此端點不查 live log、不寫 KM、不寫 RAG index、不更新 PlayBook trust、不呼叫 MCP tool、"
|
||||
"不執行修復、不觸發 workflow、不讀 secret、不讀 raw session / SQLite、不呼叫 GitHub。"
|
||||
),
|
||||
)
|
||||
async def get_agent_log_intelligence_integration_readback() -> dict[str, Any]:
|
||||
"""Return the read-only LOG intelligence integration matrix."""
|
||||
try:
|
||||
payload = await asyncio.to_thread(
|
||||
load_latest_ai_agent_log_intelligence_integration_readback
|
||||
)
|
||||
return redact_public_lan_topology(payload)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error("ai_agent_log_intelligence_integration_readback_invalid", error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="AI Agent LOG / KM / RAG / MCP / PlayBook 整合 readback 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-telegram-receipt-approval-package",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
"""AI Agent log intelligence integration readback.
|
||||
|
||||
This service exposes a source-of-truth map for LOG -> KM/RAG/MCP/PlayBook
|
||||
automation. It only inspects committed source files packaged with the API image;
|
||||
it does not query live logs, read secrets, write KM, update PlayBook trust,
|
||||
call MCP tools, trigger workflows, or execute runtime repairs.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from src.services.snapshot_paths import resolve_repo_root
|
||||
|
||||
_SCHEMA_VERSION = "ai_agent_log_intelligence_integration_readback_v1"
|
||||
_DEFAULT_REPO_ROOT = resolve_repo_root(Path(__file__))
|
||||
|
||||
_LANES: tuple[dict[str, Any], ...] = (
|
||||
{
|
||||
"lane_id": "structured_service_log_collection",
|
||||
"title": "服務日誌與可觀測性來源",
|
||||
"source_kind": "logs_metrics_traces",
|
||||
"integration_target": "diagnosis_context",
|
||||
"required_refs": (
|
||||
"apps/api/src/core/logging.py",
|
||||
"apps/api/src/core/deep_linking.py",
|
||||
"apps/api/src/services/diagnosis_aggregator.py",
|
||||
),
|
||||
"required_labels": ("project_id", "service", "environment", "trace_id", "severity"),
|
||||
},
|
||||
{
|
||||
"lane_id": "log_classification_summary",
|
||||
"title": "Log 分類、摘要與異常訊號",
|
||||
"source_kind": "log_classification",
|
||||
"integration_target": "agent_evidence_packet",
|
||||
"required_refs": (
|
||||
"apps/api/src/services/log_anomaly_detector.py",
|
||||
"apps/api/src/services/log_summary_service.py",
|
||||
"apps/api/src/services/diagnosis_aggregator.py",
|
||||
),
|
||||
"required_labels": ("signal_kind", "severity", "service", "package", "tool"),
|
||||
},
|
||||
{
|
||||
"lane_id": "km_rag_consumption",
|
||||
"title": "KM / RAG 證據消費",
|
||||
"source_kind": "knowledge_memory",
|
||||
"integration_target": "rag_context",
|
||||
"required_refs": (
|
||||
"apps/api/src/services/knowledge_extractor_service.py",
|
||||
"apps/api/src/services/knowledge_service.py",
|
||||
"apps/api/src/services/knowledge_rag_service.py",
|
||||
"apps/api/src/services/rag_service.py",
|
||||
"apps/api/src/services/graph_rag.py",
|
||||
),
|
||||
"required_labels": ("project_id", "product", "service", "incident_id", "evidence_ref"),
|
||||
},
|
||||
{
|
||||
"lane_id": "playbook_learning_loop",
|
||||
"title": "PlayBook 匹配、RAG 與 trust 學習",
|
||||
"source_kind": "playbook_learning",
|
||||
"integration_target": "playbook_candidate_and_trust_gate",
|
||||
"required_refs": (
|
||||
"apps/api/src/services/playbook_rag.py",
|
||||
"apps/api/src/services/playbook_match_resolver.py",
|
||||
"apps/api/src/services/playbook_embedding_service.py",
|
||||
"apps/api/src/services/playbook_evolver.py",
|
||||
"apps/api/src/services/ai_agent_matched_playbook_learning_gap.py",
|
||||
),
|
||||
"required_labels": ("playbook_id", "incident_id", "service", "risk_tier", "verifier_id"),
|
||||
},
|
||||
{
|
||||
"lane_id": "mcp_tool_audit_context",
|
||||
"title": "MCP 工具、稽核上下文與工具註冊",
|
||||
"source_kind": "mcp_tools",
|
||||
"integration_target": "controlled_tool_context",
|
||||
"required_refs": (
|
||||
"apps/api/src/plugins/mcp/mcp_bridge.py",
|
||||
"apps/api/src/services/mcp_audit_context.py",
|
||||
"apps/api/src/services/mcp_audit_service.py",
|
||||
"apps/api/src/services/mcp_tool_registry.py",
|
||||
),
|
||||
"required_labels": ("mcp_server", "tool", "agent_run_id", "gateway_path", "redaction_state"),
|
||||
},
|
||||
{
|
||||
"lane_id": "agent_decision_runtime_context",
|
||||
"title": "AI Agent 決策與結果捕獲",
|
||||
"source_kind": "agent_runtime",
|
||||
"integration_target": "controlled_decision_runtime",
|
||||
"required_refs": (
|
||||
"apps/api/src/services/ai_agent_autonomous_runtime_control.py",
|
||||
"apps/api/src/services/ai_agent_task_result_audit_trail.py",
|
||||
"apps/api/src/services/ai_agent_result_capture_writer_dry_run_readback.py",
|
||||
"apps/api/src/services/ai_agent_result_capture_no_write_readback.py",
|
||||
),
|
||||
"required_labels": ("agent_run_id", "decision_id", "risk_tier", "source_system", "verifier_id"),
|
||||
},
|
||||
{
|
||||
"lane_id": "post_apply_verifier_feedback",
|
||||
"title": "Verifier 與 post-apply feedback",
|
||||
"source_kind": "verification_feedback",
|
||||
"integration_target": "learning_writeback_candidate",
|
||||
"required_refs": (
|
||||
"apps/api/src/services/ai_agent_runtime_verifier_evidence_review.py",
|
||||
"apps/api/src/services/ai_agent_post_write_verifier_package.py",
|
||||
"apps/api/src/services/ai_agent_result_capture_post_release_verifier_rollback_gate.py",
|
||||
),
|
||||
"required_labels": ("verifier_id", "rollback_ref", "service", "environment", "decision_id"),
|
||||
},
|
||||
)
|
||||
|
||||
_LABEL_TAXONOMY: tuple[dict[str, Any], ...] = (
|
||||
{
|
||||
"label_group": "ownership",
|
||||
"required_fields": ("project_id", "product", "service", "owner_lane"),
|
||||
"purpose": "把所有 log / event 綁回專案、產品、服務與 owner lane。",
|
||||
},
|
||||
{
|
||||
"label_group": "runtime_surface",
|
||||
"required_fields": ("environment", "host", "package", "tool", "source_system"),
|
||||
"purpose": "把網站、服務、套件、工具與主機來源分群。",
|
||||
},
|
||||
{
|
||||
"label_group": "correlation",
|
||||
"required_fields": ("trace_id", "incident_id", "agent_run_id", "decision_id"),
|
||||
"purpose": "把 log、告警、AI run、AwoooP work item 與 verifier 串成同一條證據鏈。",
|
||||
},
|
||||
{
|
||||
"label_group": "learning",
|
||||
"required_fields": ("playbook_id", "verifier_id", "risk_tier", "redaction_state"),
|
||||
"purpose": "讓 KM/RAG/PlayBook trust writeback 能判斷是否可學習。",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def load_latest_ai_agent_log_intelligence_integration_readback(
|
||||
repo_root: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the latest LOG -> AI automation integration readback."""
|
||||
root = repo_root or _DEFAULT_REPO_ROOT
|
||||
lanes = [_build_lane(root, lane) for lane in _LANES]
|
||||
ready_lane_count = sum(1 for lane in lanes if lane["status"] == "source_refs_present")
|
||||
missing_lane_count = len(lanes) - ready_lane_count
|
||||
label_field_count = len(
|
||||
{field for group in _LABEL_TAXONOMY for field in group["required_fields"]}
|
||||
)
|
||||
active_blockers = []
|
||||
if missing_lane_count:
|
||||
active_blockers.append("committed_source_refs_missing")
|
||||
active_blockers.append("runtime_e2e_log_sample_readback_missing")
|
||||
|
||||
return {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"priority": "P1-LOG-KM-RAG-MCP-PLAYBOOK",
|
||||
"scope": "ai_agent_log_intelligence_integration",
|
||||
"status": (
|
||||
"controlled_apply_ready_missing_runtime_e2e_log_sample"
|
||||
if not missing_lane_count
|
||||
else "blocked_missing_committed_source_refs"
|
||||
),
|
||||
"readback": {
|
||||
"workplan_id": "P1-LOG-INTELLIGENCE",
|
||||
"workplan_title": "所有服務日誌貼標並串接 KM / RAG / MCP / PlayBook / AI Agent",
|
||||
"repo_root_ref": str(root),
|
||||
"safe_next_step": (
|
||||
"add_runtime_log_sample_verifier_then_write_trusted_KM_RAG_PlayBook_feedback_receipt"
|
||||
),
|
||||
},
|
||||
"integration_lanes": lanes,
|
||||
"label_taxonomy": list(_LABEL_TAXONOMY),
|
||||
"rollups": {
|
||||
"lane_count": len(lanes),
|
||||
"ready_lane_count": ready_lane_count,
|
||||
"missing_lane_count": missing_lane_count,
|
||||
"source_ref_count": sum(len(lane["evidence_refs"]) for lane in lanes),
|
||||
"missing_source_ref_count": sum(len(lane["missing_refs"]) for lane in lanes),
|
||||
"readiness_percent": _percent(ready_lane_count / max(len(lanes), 1) * 100),
|
||||
"label_group_count": len(_LABEL_TAXONOMY),
|
||||
"required_label_field_count": label_field_count,
|
||||
"runtime_e2e_log_sample_readback_present": False,
|
||||
"km_rag_playbook_trust_writeback_authorized": False,
|
||||
"mcp_tool_execution_authorized": False,
|
||||
},
|
||||
"active_blockers": active_blockers,
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": True,
|
||||
"live_log_query_performed": False,
|
||||
"km_write_performed": False,
|
||||
"rag_index_write_performed": False,
|
||||
"playbook_trust_write_performed": False,
|
||||
"mcp_tool_call_performed": False,
|
||||
"runtime_repair_performed": False,
|
||||
"workflow_trigger_performed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"raw_session_or_sqlite_read_allowed": False,
|
||||
"github_api_used": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _build_lane(root: Path, lane: dict[str, Any]) -> dict[str, Any]:
|
||||
evidence_refs = [str(ref) for ref in lane["required_refs"]]
|
||||
missing_refs = [ref for ref in evidence_refs if not _ref_exists(root, ref)]
|
||||
return {
|
||||
"lane_id": lane["lane_id"],
|
||||
"title": lane["title"],
|
||||
"source_kind": lane["source_kind"],
|
||||
"integration_target": lane["integration_target"],
|
||||
"status": "source_refs_present" if not missing_refs else "missing_source_refs",
|
||||
"required_labels": list(lane["required_labels"]),
|
||||
"evidence_refs": evidence_refs,
|
||||
"missing_refs": missing_refs,
|
||||
"controlled_apply_next_step": "attach_runtime_sample_and_post_apply_verifier",
|
||||
"runtime_write_enabled": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def _ref_exists(root: Path, ref: str) -> bool:
|
||||
if (root / ref).exists():
|
||||
return True
|
||||
if ref.startswith("apps/api/"):
|
||||
return (root / ref.removeprefix("apps/api/")).exists()
|
||||
return False
|
||||
|
||||
|
||||
def _percent(value: Any) -> int:
|
||||
return max(0, min(100, round(float(value or 0))))
|
||||
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.agents import router
|
||||
from src.services.ai_agent_log_intelligence_integration_readback import (
|
||||
_ref_exists,
|
||||
load_latest_ai_agent_log_intelligence_integration_readback,
|
||||
)
|
||||
|
||||
|
||||
def test_ai_agent_log_intelligence_integration_loader_maps_all_lanes():
|
||||
payload = load_latest_ai_agent_log_intelligence_integration_readback()
|
||||
|
||||
_assert_log_intelligence_payload(payload)
|
||||
|
||||
|
||||
def test_ai_agent_log_intelligence_integration_endpoint_returns_readback():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/agents/agent-log-intelligence-integration-readback"
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
_assert_log_intelligence_payload(response.json())
|
||||
|
||||
|
||||
def test_log_intelligence_ref_exists_supports_api_container_layout(tmp_path):
|
||||
container_ref = tmp_path / "src" / "services" / "diagnosis_aggregator.py"
|
||||
container_ref.parent.mkdir(parents=True)
|
||||
container_ref.write_text("# packaged API source\n", encoding="utf-8")
|
||||
|
||||
assert _ref_exists(
|
||||
tmp_path,
|
||||
"apps/api/src/services/diagnosis_aggregator.py",
|
||||
)
|
||||
|
||||
|
||||
def _assert_log_intelligence_payload(payload: dict):
|
||||
assert (
|
||||
payload["schema_version"]
|
||||
== "ai_agent_log_intelligence_integration_readback_v1"
|
||||
)
|
||||
assert payload["priority"] == "P1-LOG-KM-RAG-MCP-PLAYBOOK"
|
||||
assert payload["status"] == "controlled_apply_ready_missing_runtime_e2e_log_sample"
|
||||
assert payload["rollups"]["lane_count"] == 7
|
||||
assert payload["rollups"]["ready_lane_count"] == 7
|
||||
assert payload["rollups"]["missing_lane_count"] == 0
|
||||
assert payload["rollups"]["readiness_percent"] == 100
|
||||
assert payload["rollups"]["runtime_e2e_log_sample_readback_present"] is False
|
||||
assert payload["rollups"]["km_rag_playbook_trust_writeback_authorized"] is False
|
||||
assert payload["rollups"]["mcp_tool_execution_authorized"] is False
|
||||
assert "runtime_e2e_log_sample_readback_missing" in payload["active_blockers"]
|
||||
|
||||
lanes = {lane["lane_id"]: lane for lane in payload["integration_lanes"]}
|
||||
assert set(lanes) == {
|
||||
"structured_service_log_collection",
|
||||
"log_classification_summary",
|
||||
"km_rag_consumption",
|
||||
"playbook_learning_loop",
|
||||
"mcp_tool_audit_context",
|
||||
"agent_decision_runtime_context",
|
||||
"post_apply_verifier_feedback",
|
||||
}
|
||||
assert "apps/api/src/services/diagnosis_aggregator.py" in lanes[
|
||||
"structured_service_log_collection"
|
||||
]["evidence_refs"]
|
||||
assert "apps/api/src/services/knowledge_rag_service.py" in lanes[
|
||||
"km_rag_consumption"
|
||||
]["evidence_refs"]
|
||||
assert "apps/api/src/services/playbook_rag.py" in lanes[
|
||||
"playbook_learning_loop"
|
||||
]["evidence_refs"]
|
||||
assert "apps/api/src/plugins/mcp/mcp_bridge.py" in lanes[
|
||||
"mcp_tool_audit_context"
|
||||
]["evidence_refs"]
|
||||
assert all(lane["runtime_write_enabled"] is False for lane in lanes.values())
|
||||
assert all(not lane["missing_refs"] for lane in lanes.values())
|
||||
|
||||
label_groups = {
|
||||
group["label_group"]: set(group["required_fields"])
|
||||
for group in payload["label_taxonomy"]
|
||||
}
|
||||
assert {"project_id", "product", "service"}.issubset(
|
||||
label_groups["ownership"]
|
||||
)
|
||||
assert {"package", "tool", "source_system"}.issubset(
|
||||
label_groups["runtime_surface"]
|
||||
)
|
||||
assert {"trace_id", "incident_id", "agent_run_id"}.issubset(
|
||||
label_groups["correlation"]
|
||||
)
|
||||
assert {"playbook_id", "verifier_id", "risk_tier"}.issubset(
|
||||
label_groups["learning"]
|
||||
)
|
||||
|
||||
boundaries = payload["operation_boundaries"]
|
||||
assert boundaries["read_only_api_allowed"] is True
|
||||
assert boundaries["live_log_query_performed"] is False
|
||||
assert boundaries["km_write_performed"] is False
|
||||
assert boundaries["rag_index_write_performed"] is False
|
||||
assert boundaries["playbook_trust_write_performed"] is False
|
||||
assert boundaries["mcp_tool_call_performed"] is False
|
||||
assert boundaries["runtime_repair_performed"] is False
|
||||
assert boundaries["secret_value_collection_allowed"] is False
|
||||
assert boundaries["raw_session_or_sqlite_read_allowed"] is False
|
||||
assert boundaries["github_api_used"] is False
|
||||
Reference in New Issue
Block a user