Download src/analysis_agent.py from FlyingNunchucks/08-centralized-multi-agent-orchestrator: direct link, hf CLI and curl.
- Browser
- Download file 6.81 kB
-
https://huggingface.co/spaces/FlyingNunchucks/08-centralized-multi-agent-orchestrator/resolve/main/src/analysis_agent.py
- Command line
-
hf download hf://spaces/FlyingNunchucks/08-centralized-multi-agent-orchestrator/src/analysis_agent.py
-
curl -L -o analysis_agent.py https://huggingface.co/spaces/FlyingNunchucks/08-centralized-multi-agent-orchestrator/resolve/main/src/analysis_agent.py
6.81 kB
| """Bounded specialist agent for evidence-linked analysis.""" | |
| from collections.abc import Callable | |
| import json | |
| from .schemas import AnalysisResult, ResearchResult | |
| AnalysisModel = Callable[[str], str] | |
| class AnalysisAgent: | |
| """Interpret validated research findings without introducing new facts.""" | |
| role = "analysis" | |
| def __init__(self, model: AnalysisModel) -> None: | |
| self.model = model | |
| def build_prompt( | |
| self, | |
| mission: str, | |
| research_result: ResearchResult, | |
| ) -> str: | |
| """Build the bounded prompt given to the analysis model.""" | |
| research_packet = research_result.model_dump_json(indent=2) | |
| return f"""You are the Analysis Agent in a centralized multi-agent system. | |
| Your job is narrow: interpret the validated research handoff and identify implications relevant to the mission. | |
| MISSION: | |
| {mission} | |
| VALIDATED RESEARCH HANDOFF: | |
| {research_packet} | |
| Rules: | |
| - Use only information contained in the validated research handoff. | |
| - Do not invent new facts, evidence, source IDs, entity relationships, or comparative claims. | |
| - Do not perform new research. | |
| - Every analysis point must cite one or more source_id values already present in the research findings. | |
| - If you cannot cite at least one approved source_id for an idea, do not put it in points; place it in questions_for_verification instead. | |
| - Preserve the epistemic strength of the research. Do not upgrade related evidence into a stronger claim. | |
| - If the research says competitors compete on speed and service, you may say those are competitive dimensions; do not say the competitors dominate, outperform, or are superior unless the research explicitly establishes that. | |
| - If the research says customers show interest in automation, you may say demand exists for automation; do not say that demand aligns with this company's offering unless the research explicitly describes that offering and establishes the fit. | |
| - If a capability is unresolved, describe it as unresolved; do not call it inadequate, insufficient, or a proven weakness. | |
| - When an implication requires interpretation rather than direct evidence, use bounded language such as may, could, or suggests and lower confidence appropriately. | |
| - Distinguish opportunities, risks, constraints, and uncertainties. | |
| - Explain the reasoning that connects the research evidence to each analysis point. | |
| - Record any assumptions explicitly rather than presenting them as facts. | |
| - Put claims that deserve independent checking in questions_for_verification. | |
| - Questions must be neutral and must not embed an unsupported premise. Ask what must be learned, not what must be done to fix a problem that has not been established. | |
| - Do not create point IDs. Application code assigns stable IDs after validating your response. | |
| - Do not make the final recommendation; the Synthesis Agent will own the final answer. | |
| - Return valid JSON only. | |
| Return exactly this shape: | |
| {{ | |
| "assessment": "overall interpretation of the research", | |
| "points": [ | |
| {{ | |
| "kind": "opportunity", | |
| "statement": "analysis point", | |
| "reasoning": "why the research supports this interpretation", | |
| "source_ids": ["source-1"], | |
| "confidence": "high" | |
| }} | |
| ], | |
| "assumptions": [], | |
| "questions_for_verification": [] | |
| }} | |
| """ | |
| def _quarantine_unlinked_points( | |
| payload: object, | |
| approved_ids: set[str], | |
| ) -> object: | |
| """Remove non-evidence-linked points while preserving them for audit.""" | |
| if not isinstance(payload, dict): | |
| return payload | |
| raw_points = payload.get("points") | |
| if not isinstance(raw_points, list): | |
| return payload | |
| kept_points = [] | |
| omitted_points = [] | |
| for raw_point in raw_points: | |
| if not isinstance(raw_point, dict): | |
| kept_points.append(raw_point) | |
| continue | |
| raw_source_ids = raw_point.get("source_ids") | |
| cleaned_ids = [] | |
| if isinstance(raw_source_ids, list): | |
| for source_id in raw_source_ids: | |
| if isinstance(source_id, str): | |
| cleaned = source_id.strip() | |
| if cleaned and cleaned not in cleaned_ids: | |
| cleaned_ids.append(cleaned) | |
| has_only_approved_ids = bool(cleaned_ids) and all( | |
| source_id in approved_ids | |
| for source_id in cleaned_ids | |
| ) | |
| if has_only_approved_ids: | |
| normalized_point = dict(raw_point) | |
| normalized_point["source_ids"] = cleaned_ids | |
| kept_points.append(normalized_point) | |
| continue | |
| statement = raw_point.get("statement") | |
| if isinstance(statement, str) and statement.strip(): | |
| omitted_points.append(statement.strip()) | |
| else: | |
| omitted_points.append( | |
| "Analysis point omitted because it lacked approved source links." | |
| ) | |
| normalized_payload = dict(payload) | |
| normalized_payload["points"] = kept_points | |
| normalized_payload["omitted_points"] = omitted_points | |
| if raw_points and not kept_points: | |
| raise ValueError( | |
| "analysis model returned no evidence-linked points after validation" | |
| ) | |
| return normalized_payload | |
| def run( | |
| self, | |
| mission: str, | |
| research_result: ResearchResult, | |
| ) -> AnalysisResult: | |
| """Run the analysis model and validate its handoff contract.""" | |
| if not mission.strip(): | |
| raise ValueError("mission must not be empty") | |
| prompt = self.build_prompt(mission, research_result) | |
| raw_response = self.model(prompt) | |
| try: | |
| payload = json.loads(raw_response) | |
| except json.JSONDecodeError as exc: | |
| raise ValueError("analysis model returned invalid JSON") from exc | |
| approved_ids = { | |
| source_id | |
| for finding in research_result.findings | |
| for source_id in finding.source_ids | |
| } | |
| payload = self._quarantine_unlinked_points( | |
| payload=payload, | |
| approved_ids=approved_ids, | |
| ) | |
| result = AnalysisResult.model_validate(payload) | |
| cited_ids = { | |
| source_id | |
| for point in result.points | |
| for source_id in point.source_ids | |
| } | |
| unknown_ids = cited_ids - approved_ids | |
| if unknown_ids: | |
| raise ValueError( | |
| "analysis model cited unknown source IDs: " | |
| + ", ".join(sorted(unknown_ids)) | |
| ) | |
| for index, point in enumerate(result.points, start=1): | |
| point.point_id = f"analysis-{index}" | |
| return result | |