131 lines
5.0 KiB
Python
131 lines
5.0 KiB
Python
"""Hypothesis Agent — analyzes phenomena and generates investigative hypotheses."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
|
|
from base_agent import BaseAgent
|
|
from evidence_graph import EvidenceGraph, HYPOTHESIS_EDGE_WEIGHTS
|
|
from llm_client import LLMClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class HypothesisAgent(BaseAgent):
|
|
name = "hypothesis"
|
|
role = (
|
|
"Hypothesis analyst. You review all phenomena discovered so far "
|
|
"and formulate investigative hypotheses about what happened on this system. "
|
|
"Your ultimate goal: build the most complete picture of events that occurred. "
|
|
"For each hypothesis, identify which existing phenomena support or contradict it."
|
|
)
|
|
|
|
def __init__(self, llm: LLMClient, graph: EvidenceGraph) -> None:
|
|
super().__init__(llm, graph)
|
|
self._register_hypothesis_tools()
|
|
|
|
def _register_hypothesis_tools(self) -> None:
|
|
"""Register hypothesis-specific tools."""
|
|
|
|
valid_edge_types = list(HYPOTHESIS_EDGE_WEIGHTS.keys())
|
|
|
|
self.register_tool(
|
|
name="add_hypothesis",
|
|
description=(
|
|
"Create a new investigative hypothesis about what happened on the system. "
|
|
"Each hypothesis should be a specific, testable claim."
|
|
),
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Short title for the hypothesis.",
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Detailed description of what this hypothesis claims.",
|
|
},
|
|
},
|
|
"required": ["title", "description"],
|
|
},
|
|
executor=self._add_hypothesis,
|
|
)
|
|
|
|
self.register_tool(
|
|
name="link_phenomenon_to_hypothesis",
|
|
description=(
|
|
"Link an existing phenomenon to a hypothesis with a relationship type. "
|
|
f"Valid relationship types: {', '.join(valid_edge_types)}. "
|
|
"direct_evidence = the phenomenon IS the hypothesis. "
|
|
"supports = consistent with the hypothesis. "
|
|
"prerequisite_met = a necessary condition is satisfied. "
|
|
"consequence_observed = an expected result of the hypothesis is found. "
|
|
"contradicts = directly contradicts the hypothesis. "
|
|
"weakens = makes the hypothesis less likely."
|
|
),
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"phenomenon_id": {
|
|
"type": "string",
|
|
"description": "ID of the phenomenon (e.g. 'ph-a1b2c3d4').",
|
|
},
|
|
"hypothesis_id": {
|
|
"type": "string",
|
|
"description": "ID of the hypothesis (e.g. 'hyp-e5f6g7h8').",
|
|
},
|
|
"edge_type": {
|
|
"type": "string",
|
|
"enum": valid_edge_types,
|
|
"description": "The edge_type of the relationship.",
|
|
},
|
|
"reason": {
|
|
"type": "string",
|
|
"description": "The reason this relationship holds (1-2 sentences).",
|
|
},
|
|
},
|
|
"required": ["phenomenon_id", "hypothesis_id", "edge_type", "reason"],
|
|
},
|
|
executor=self._link_phenomenon_to_hypothesis,
|
|
)
|
|
|
|
async def _add_hypothesis(self, title: str, description: str) -> str:
|
|
hid = await self.graph.add_hypothesis(
|
|
title=title,
|
|
description=description,
|
|
created_by=self.name,
|
|
)
|
|
return f"Hypothesis created: {hid} — {title} (confidence: 0.50)"
|
|
|
|
async def _link_phenomenon_to_hypothesis(
|
|
self,
|
|
phenomenon_id: str,
|
|
hypothesis_id: str,
|
|
edge_type: str = "",
|
|
reason: str = "",
|
|
# Common LLM misnaming — accept as fallbacks
|
|
relationship: str = "",
|
|
note: str = "",
|
|
) -> str:
|
|
edge_type = edge_type or relationship
|
|
reason = reason or note
|
|
if not edge_type:
|
|
return "Error: edge_type is required."
|
|
try:
|
|
new_conf = await self.graph.update_hypothesis_confidence(
|
|
hyp_id=hypothesis_id,
|
|
phenomenon_id=phenomenon_id,
|
|
edge_type=edge_type,
|
|
reason=reason,
|
|
)
|
|
weight = HYPOTHESIS_EDGE_WEIGHTS[edge_type]
|
|
direction = "+" if weight > 0 else ""
|
|
return (
|
|
f"Linked: {phenomenon_id} —[{edge_type}]→ {hypothesis_id} "
|
|
f"(weight: {direction}{weight}, new confidence: {new_conf:.3f})"
|
|
)
|
|
except ValueError as e:
|
|
return f"Error linking: {e}"
|