Spaces:
Running
Running
| from typing import Dict, Any | |
| from langchain_core.messages import HumanMessage, ToolMessage, SystemMessage, AIMessage | |
| from core.llm_router import get_llm | |
| from core.search.regulation_engine import regulation_engine, kruczkowski_trap_agent | |
| from core.trust.trust_scorer import compute_grant_trust_score | |
| from agents.auditor import ( | |
| GlobalAuditOutput, | |
| _PerspectiveResult, | |
| _ROLE_PROMPTS, | |
| _SHARED_INSTRUCTIONS, | |
| ) | |
| from agents.tools.legal_retriever_tool import search_legal_documents | |
| from agents.tools.krs_graph_tool import analyze_company_network | |
| from agents.tools.neo4j_cypher_tool import query_neo4j_graph | |
| from agents.tools.budget_rules_tool import search_budget_rules | |
| from agents.tools.technology_retriever_tool import search_technology_trends | |
| from agents.panel_state import AuditorPanelState | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| # --- PRAWNIK NODE (Dynamic Query Routing) --- | |
| def prawnik_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Agent Prawny z obsługą poszukiwań w RAG oraz RAG Grafowym (KRS).""" | |
| llm_with_tools = get_llm( | |
| task_type="legal_audit", | |
| tools=[search_legal_documents, analyze_company_network, query_neo4j_graph], | |
| ) | |
| # Inicjalizacja wiadomości, jeśli pierwsze wywołanie | |
| messages = state.get("messages", []) | |
| initial_messages_added = [] | |
| if not messages: | |
| ext_prompt = ( | |
| "Zewnętrzny Rewizor: Weryfikujesz cudzy, gotowy wniosek (z biura konsultingowego) przesłany do nas w celu tzw. Reverse-Audit. Nastaw się na bezlitosną weryfikację błędów." | |
| if state.get("is_external_audit", False) | |
| else "" | |
| ) | |
| sys_prompt = f"{_ROLE_PROMPTS['prawnik']}\n{ext_prompt}\n{_SHARED_INSTRUCTIONS}\n\nProgram: {state['program_name']}\nZanim ocenisz, zawsze skorzystaj z narzędzia search_legal_documents, aby sprawdzić wymogi dla perspektywy {state['program_name']}. Jeśli nie znajdziesz nic lub perspektywa nie będzie się zgadzać, PONÓW WYSZUKIWANIE z innym zapytaniem. Jak jesteś gotowy wydać ocenę wywołaj narzędzie submit_evaluation, NIE generuj go jako plain text." | |
| initial_messages_added.append(SystemMessage(content=sys_prompt)) | |
| initial_messages_added.append( | |
| HumanMessage(content=f"TREŚĆ WNIOSKU:\n{state['content'][:150000]}") | |
| ) | |
| messages = initial_messages_added | |
| # Wywołanie modelu | |
| try: | |
| response = llm_with_tools.invoke(messages) | |
| except Exception as e: | |
| logger.error(f"[PRAWNIK] Błąd wywołania modelu: {e}") | |
| response = AIMessage( | |
| content=f"Wystąpił błąd podczas wywołania LLM: {e}. Przechodzę do podsumowania." | |
| ) | |
| return { | |
| "messages": initial_messages_added + [response], | |
| "legal_attempts": state.get("legal_attempts", 0), | |
| } | |
| def prawnik_tools_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Uruchamia narzędzie wyszukiwania dla Prawnika.""" | |
| last_message = state["messages"][-1] | |
| tool_messages = [] | |
| for tool_call in last_message.tool_calls: | |
| if tool_call["name"] in [ | |
| "search_legal_documents", | |
| "analyze_company_network", | |
| "query_neo4j_graph", | |
| ]: | |
| logger.info( | |
| f"[PRAWNIK] Wykorzystanie narzędzia {tool_call['name']}: {tool_call['args']}" | |
| ) | |
| # Bezpieczne wykonanie narzędzia | |
| try: | |
| if tool_call["name"] == "search_legal_documents": | |
| result = search_legal_documents.invoke(tool_call["args"]) | |
| elif tool_call["name"] == "analyze_company_network": | |
| result = analyze_company_network.invoke(tool_call["args"]) | |
| else: | |
| result = query_neo4j_graph.invoke(tool_call["args"]) | |
| except Exception as e: | |
| result = f"Błąd wykonania narzędzia: {e}" | |
| tool_messages.append( | |
| ToolMessage(content=result, tool_call_id=tool_call["id"]) | |
| ) | |
| return { | |
| "messages": tool_messages, | |
| "legal_attempts": state.get("legal_attempts", 0) + 1, | |
| "legal_queries": [str(tc["args"]) for tc in last_message.tool_calls], | |
| } | |
| def prawnik_evaluator_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Generuje ostateczny Pydantic output Prawnika po zebraniu wiedzy z RAG.""" | |
| # Ekstrakcja do schematu | |
| llm = get_llm(task_type="legal_audit", structured_output_schema=_PerspectiveResult) | |
| # Przebieg całej konwersacji prawnika | |
| conversation_text = "\n".join( | |
| [m.content for m in state["messages"] if isinstance(m.content, str)] | |
| ) | |
| # Aktywne użycie silnika w Prawniku (Faza 3) - nie tylko kontekst, ale weryfikacja | |
| engine_rules = "" | |
| engine_compliance_check = "" | |
| trust_context = "" | |
| try: | |
| rules = regulation_engine.get_structured_rules_for_program(state['program_name'] or "") | |
| if rules: | |
| engine_rules = "\n\n--- STRUCTURED RULES FROM REGULATION ENGINE (traktuj jako źródło prawdy) ---\n" | |
| engine_rules += "\n".join(rules.get("key_rules", [])[:7]) | |
| # Aktywna weryfikacja + wpływ na wynik (Faza 3) | |
| compliance = regulation_engine.check_cost_eligibility(state['program_name'] or "", state['content'][:3000]) | |
| if compliance.get("status") == "evaluated": | |
| engine_compliance_check = f"\n\n--- REGULATION ENGINE COMPLIANCE CHECK ---\n{compliance}" | |
| if compliance.get("severity") == "critical": | |
| engine_compliance_check += "\n[CRITICAL ISSUE - RECOMMEND BLOCKING EXPORT OR HUMAN REVIEW]" | |
| # Dodajemy issue bezpośrednio do wyniku (zostanie scalone w ewaluatorze) | |
| # v5.0: Kruczkowski Compliance & Trap + Citation Verifier integration (Faza 2/3) | |
| try: | |
| if kruczkowski_trap_agent: | |
| trap_result = kruczkowski_trap_agent.detect_traps( | |
| document_text=state['content'][:4500], | |
| program=state['program_name'] or "", | |
| msp_context=state.get("msp_analysis") or state.get("external_context", {}).get("msp_analysis") | |
| ) | |
| if trap_result.get("overall_trap_risk") in ("high", "critical"): | |
| engine_compliance_check += f"\n\n--- KRUCZKOWSKI COMPLIANCE & TRAP v5.0 ---\nRisk: {trap_result['overall_trap_risk']} | Traps: {trap_result['num_traps']} | Blocks export: {trap_result.get('blocks_export_recommendation')}\nCitation score: {trap_result.get('citation_verification', {}).get('overall_citation_score')}\n[STRICT MODE: Zalecane dodatkowe sprawdzenie przez człowieka]" | |
| # Zawsze wstrzykujemy citation grounding info | |
| cit = trap_result.get("citation_verification", {}) | |
| if cit.get("overall_citation_score"): | |
| engine_compliance_check += f"\n[CITATION GROUNDING: {cit.get('overall_citation_score')} ({cit.get('citation_quality')}) — {cit.get('recommendation', '')[:120]}]" | |
| except Exception as _trap_e: | |
| logger.debug(f"[Prawnik v5.0] Trap agent skipped: {_trap_e}") | |
| # Trust Score injection (Cycle 10) | |
| trust_score = compute_grant_trust_score({"program": state.get('program_name')}) | |
| trust_context = f"\n\n[Trust Score dla programu: {trust_score}/100 — im niższy, tym ostrożniej podchodzić do wniosków i rekomendacji]" | |
| except Exception: | |
| pass | |
| prompt = f""" | |
| Na podstawie zebranych dotychczas informacji i analizy (patrz historia, upewnij się, że opierasz się na zweryfikowanym prawie z narzędzia): | |
| {conversation_text} | |
| {engine_rules} | |
| {engine_compliance_check} | |
| {trust_context} | |
| Wygeneruj ostateczny wynik audytu prawnego dla wniosku ({state['program_name']}) wg struktury. | |
| Oceń projekt. Role: prawnik. | |
| TREŚĆ: | |
| {state['content'][:150000]} | |
| """ | |
| from tenacity import retry, stop_after_attempt, wait_exponential | |
| def invoke_eval(): | |
| result: _PerspectiveResult = llm.invoke(prompt) | |
| if not result.summary or len(result.summary.strip()) < 10: | |
| raise ValueError("Błąd sanity check: Puste podsumowanie audytu prawnego.") | |
| for issue in result.issues: | |
| issue.perspective = "prawnik" | |
| # Jeśli silnik wykrył critical problem — obniżamy score | |
| if "CRITICAL ISSUE" in engine_compliance_check: | |
| result.partial_score = max(0, result.partial_score - 30) | |
| return { | |
| "issues": result.issues, | |
| "perspectives_summary": {"prawnik": result.summary}, | |
| "perspective_scores": [result.partial_score], | |
| "prawnik_done": True, | |
| } | |
| try: | |
| return invoke_eval() | |
| except Exception as e: | |
| logger.error(f"[PRAWNIK] Ostateczny błąd ewaluatora: {e}") | |
| return { | |
| "prawnik_done": True, | |
| "perspectives_summary": { | |
| "prawnik": f"Błąd audytu prawnego po 5 próbach: {e}" | |
| }, | |
| } | |
| def prawnik_routing(state: AuditorPanelState) -> str: | |
| """Decyduje czy prawnik musi szukać dalej, oceniać czy przekroczył limit.""" | |
| last_message = state["messages"][-1] | |
| if last_message.tool_calls: | |
| if state["legal_attempts"] >= 3: | |
| logger.warning( | |
| "[PRAWNIK] Przekroczono limit wyszukiwań, wymuszam ewaluację." | |
| ) | |
| return "evaluate" | |
| return "tools" | |
| return "evaluate" | |
| # --- FINANSISTA NODE (Dynamic Query Routing) --- | |
| def finansista_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Agent Finansowy z obsługą poszukiwań w RAG (regulaminy finansowe).""" | |
| llm_with_tools = get_llm( | |
| task_type="legal_audit", | |
| tools=[search_budget_rules, analyze_company_network, query_neo4j_graph], | |
| ) | |
| # Inicjalizacja wiadomości, jeśli pierwsze wywołanie | |
| messages = state.get("finansista_messages", []) | |
| initial_messages_added = [] | |
| if not messages: | |
| ext_prompt = ( | |
| "Zewnętrzny Rewizor: Weryfikujesz cudzy, gotowy wniosek (z biura konsultingowego) przesłany do nas w celu tzw. Reverse-Audit." | |
| if state.get("is_external_audit", False) | |
| else "" | |
| ) | |
| sys_prompt = f"{_ROLE_PROMPTS['finansista']}\n{ext_prompt}\n{_SHARED_INSTRUCTIONS}\n\nProgram: {state['program_name']}\nZanim ocenisz wniosek, używaj narzędzia search_budget_rules aby sprawdzić zasady z budżetu programu. Aby zweryfikować MŚP z perspektywy finansowej na podstawie NIP/KRS uzyj analyze_company_network. Gdy będziesz gotowy zwrócić ocenę bez korzystania z narzędzia, powróć i wykonaj finalną ocenę strukturyzowaną." | |
| initial_messages_added.append(SystemMessage(content=sys_prompt)) | |
| initial_messages_added.append( | |
| HumanMessage(content=f"TREŚĆ WNIOSKU:\n{state['content'][:150000]}") | |
| ) | |
| messages = initial_messages_added | |
| try: | |
| response = llm_with_tools.invoke(messages) | |
| except Exception as e: | |
| logger.error(f"[FINANSISTA] Błąd wywołania modelu: {e}") | |
| response = AIMessage( | |
| content=f"Wystąpił błąd podczas wywołania LLM: {e}. Przechodzę do podsumowania." | |
| ) | |
| return { | |
| "finansista_messages": initial_messages_added + [response], | |
| "finansista_attempts": state.get("finansista_attempts", 0), | |
| } | |
| def finansista_tools_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Uruchamia narzędzie wyszukiwania dla Finansisty.""" | |
| last_message = state["finansista_messages"][-1] | |
| tool_messages = [] | |
| for tool_call in last_message.tool_calls: | |
| if tool_call["name"] in [ | |
| "search_budget_rules", | |
| "analyze_company_network", | |
| "query_neo4j_graph", | |
| ]: | |
| logger.info( | |
| f"[FINANSISTA] Wykorzystanie narzędzia {tool_call['name']}: {tool_call['args']}" | |
| ) | |
| try: | |
| if tool_call["name"] == "search_budget_rules": | |
| result = search_budget_rules.invoke(tool_call["args"]) | |
| elif tool_call["name"] == "analyze_company_network": | |
| result = analyze_company_network.invoke(tool_call["args"]) | |
| else: | |
| result = query_neo4j_graph.invoke(tool_call["args"]) | |
| except Exception as e: | |
| result = f"Błąd wykonania narzędzia: {e}" | |
| tool_messages.append( | |
| ToolMessage(content=result, tool_call_id=tool_call["id"]) | |
| ) | |
| return { | |
| "finansista_messages": tool_messages, | |
| "finansista_attempts": state.get("finansista_attempts", 0) + 1, | |
| "finansista_queries": [str(tc["args"]) for tc in last_message.tool_calls], | |
| } | |
| def finansista_evaluator_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Generuje ostateczny Pydantic output Finansisty po zebraniu wiedzy z RAG.""" | |
| llm = get_llm(task_type="legal_audit", structured_output_schema=_PerspectiveResult) | |
| conversation_text = "\n".join( | |
| [ | |
| m.content | |
| for m in state.get("finansista_messages", []) | |
| if isinstance(m.content, str) | |
| ] | |
| ) | |
| # Aktywne użycie RegulationEngine przy analizie budżetowej (Faza 3) | |
| engine_context = "" | |
| trust_context = "" | |
| try: | |
| eligibility = regulation_engine.check_cost_eligibility(state.get('program_name') or "", state.get('content', '')[:2000]) | |
| if eligibility.get("status") == "evaluated": | |
| engine_context = f"\n\nREGULATION ENGINE BUDGET CHECK (źródło prawdy):\n eligible={eligibility.get('eligible')} severity={eligibility.get('severity')}\n justification: {eligibility.get('justification','')}\n reference: {eligibility.get('regulation_reference','')}\n rec: {eligibility.get('recommendation','')}\n" | |
| # v5.0 Citation + Kruczkowski for finansista (budget traps) | |
| try: | |
| if kruczkowski_trap_agent: | |
| t = kruczkowski_trap_agent.detect_traps(state.get('content', '')[:3000], state.get('program_name') or "") | |
| if t.get("citation_verification"): | |
| c = t["citation_verification"] | |
| engine_context += f"\n[CITATION GROUNDING (fin): {c.get('overall_citation_score')} / {c.get('citation_quality')}]" | |
| if t.get("overall_trap_risk") in ("high", "critical"): | |
| engine_context += f"\n[KRUCZKOWSKI BUDGET TRAPS: {t.get('num_traps')} — {t.get('overall_trap_risk')}]" | |
| except Exception: | |
| pass | |
| # Trust Score injection (Cycle 10) — teraz z citation boost z v5.0 | |
| trust_score = compute_grant_trust_score({ | |
| "program": state.get('program_name'), | |
| "citation_verification_score": (t.get("citation_verification", {}).get("overall_citation_score") if 't' in locals() else None) | |
| }) | |
| trust_context = f"\n\n[Trust Score dla programu: {trust_score}/100 — niski score = wyższe ryzyko w rekomendacjach finansowych]" | |
| except Exception: | |
| pass | |
| prompt = f""" | |
| Na podstawie zebranych dotychczas informacji i analizy finansowej/budżetowej (patrz historia): | |
| {conversation_text} | |
| {engine_context} | |
| {trust_context} | |
| Wygeneruj ostateczny wynik audytu finansowego dla wniosku ({state['program_name']}) wg struktury. | |
| Oceń projekt. Role: finansista. | |
| TREŚĆ: | |
| {state['content'][:150000]} | |
| """ | |
| from tenacity import retry, stop_after_attempt, wait_exponential | |
| def invoke_eval(): | |
| result: _PerspectiveResult = llm.invoke(prompt) | |
| if not result.summary or len(result.summary.strip()) < 10: | |
| raise ValueError( | |
| "Błąd sanity check: Puste podsumowanie audytu finansowego." | |
| ) | |
| for issue in result.issues: | |
| issue.perspective = "finansista" | |
| return { | |
| "issues": result.issues, | |
| "perspectives_summary": {"finansista": result.summary}, | |
| "perspective_scores": [result.partial_score], | |
| "finansista_done": True, | |
| } | |
| try: | |
| return invoke_eval() | |
| except Exception as e: | |
| logger.error(f"[FINANSISTA] Ostateczny błąd ewaluatora: {e}") | |
| return { | |
| "finansista_done": True, | |
| "perspectives_summary": { | |
| "finansista": f"Błąd audytu finansowego po 5 próbach: {e}" | |
| }, | |
| } | |
| def finansista_routing(state: AuditorPanelState) -> str: | |
| """Decyduje czy finansista musi szukać dalej, czy oceniać.""" | |
| last_message = state["finansista_messages"][-1] | |
| if last_message.tool_calls: | |
| if state.get("finansista_attempts", 0) >= 3: | |
| logger.warning( | |
| "[FINANSISTA] Przekroczono limit wyszukiwań, wymuszam ewaluację." | |
| ) | |
| return "evaluate" | |
| return "tools" | |
| return "evaluate" | |
| # --- INNOWATOR NODE (Dynamic Query Routing) --- | |
| def innowator_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Agent Technologiczny (Innowator) z obsługą poszukiwań w RAG (trendy, KIS, B+R).""" | |
| llm_with_tools = get_llm(task_type="legal_audit", tools=[search_technology_trends]) | |
| messages = state.get("innowator_messages", []) | |
| initial_messages_added = [] | |
| if not messages: | |
| ext_prompt = ( | |
| "Zewnętrzny Rewizor: Weryfikujesz cudzy, gotowy wniosek (z biura konsultingowego) przesłany do nas w celu tzw. Reverse-Audit." | |
| if state.get("is_external_audit", False) | |
| else "" | |
| ) | |
| sys_prompt = f"{_ROLE_PROMPTS['innowator']}\n{ext_prompt}\n{_SHARED_INSTRUCTIONS}\n\nProgram: {state['program_name']}\nZanim dokonasz oceny innowacyjności, użyj narzędzia search_technology_trends, aby zweryfikować czy technologia, poziom TRL lub KIS są poprawne dla tego programu. Kiedy będziesz gotowy zwrócić ocenę, powróć i wykonaj finalną ocenę strukturyzowaną." | |
| initial_messages_added.append(SystemMessage(content=sys_prompt)) | |
| initial_messages_added.append( | |
| HumanMessage(content=f"TREŚĆ WNIOSKU:\n{state['content'][:150000]}") | |
| ) | |
| messages = initial_messages_added | |
| try: | |
| response = llm_with_tools.invoke(messages) | |
| except Exception as e: | |
| logger.error(f"[INNOWATOR] Błąd wywołania modelu: {e}") | |
| response = AIMessage( | |
| content=f"Wystąpił błąd podczas wywołania LLM: {e}. Przechodzę do podsumowania." | |
| ) | |
| return { | |
| "innowator_messages": initial_messages_added + [response], | |
| "innowator_attempts": state.get("innowator_attempts", 0), | |
| } | |
| def innowator_tools_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Uruchamia narzędzie wyszukiwania dla Innowatora.""" | |
| last_message = state["innowator_messages"][-1] | |
| tool_messages = [] | |
| for tool_call in last_message.tool_calls: | |
| if tool_call["name"] == "search_technology_trends": | |
| logger.info( | |
| f"[INNOWATOR] Wykorzystanie narzędzia {tool_call['name']}: {tool_call['args']}" | |
| ) | |
| try: | |
| result = search_technology_trends.invoke(tool_call["args"]) | |
| except Exception as e: | |
| result = f"Błąd wykonania narzędzia: {e}" | |
| tool_messages.append( | |
| ToolMessage(content=result, tool_call_id=tool_call["id"]) | |
| ) | |
| return { | |
| "innowator_messages": tool_messages, | |
| "innowator_attempts": state.get("innowator_attempts", 0) + 1, | |
| "innowator_queries": [str(tc["args"]) for tc in last_message.tool_calls], | |
| } | |
| def innowator_evaluator_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Generuje ostateczny Pydantic output Innowatora po zebraniu wiedzy z RAG.""" | |
| llm = get_llm(task_type="legal_audit", structured_output_schema=_PerspectiveResult) | |
| conversation_text = "\n".join( | |
| [ | |
| m.content | |
| for m in state.get("innowator_messages", []) | |
| if isinstance(m.content, str) | |
| ] | |
| ) | |
| # Aktywne użycie RegulationEngine również w perspektywie innowacyjnej (Faza 3) | |
| engine_context = "" | |
| trust_context = "" | |
| try: | |
| rules = regulation_engine.get_structured_rules_for_program(state.get('program_name') or "") | |
| if rules and (rules.get("key_rules") or rules.get("scoring_criteria")): | |
| engine_context = "\n\n--- STRUCTURED PROGRAM RULES (Regulation Engine) — upewnij się, że innowacyjność jest zgodna z celami i kryteriami programu:\n" | |
| engine_context += "KEY RULES: " + "; ".join((rules.get("key_rules") or [])[:4]) | |
| engine_context += "\nSCORING: " + "; ".join((rules.get("scoring_criteria") or [])[:3]) | |
| # Trust Score injection (Cycle 10) | |
| trust_score = compute_grant_trust_score({"program": state.get('program_name')}) | |
| trust_context = f"\n\n[Trust Score dla programu: {trust_score}/100 — niski score sugeruje większą ostrożność przy ocenie innowacyjności]" | |
| except Exception: | |
| pass | |
| prompt = f""" | |
| Na podstawie zebranych dotychczas informacji i analizy innowacyjnej/technologicznej (patrz historia): | |
| {conversation_text} | |
| {engine_context} | |
| {trust_context} | |
| Wygeneruj ostateczny wynik audytu innowacyjnego dla wniosku ({state['program_name']}) wg struktury. | |
| Oceń projekt. Role: innowator. | |
| TREŚĆ: | |
| {state['content'][:150000]} | |
| """ | |
| from tenacity import retry, stop_after_attempt, wait_exponential | |
| def invoke_eval(): | |
| result: _PerspectiveResult = llm.invoke(prompt) | |
| if not result.summary or len(result.summary.strip()) < 10: | |
| raise ValueError( | |
| "Błąd sanity check: Puste podsumowanie audytu innowacyjnego." | |
| ) | |
| for issue in result.issues: | |
| issue.perspective = "innowator" | |
| return { | |
| "issues": result.issues, | |
| "perspectives_summary": {"innowator": result.summary}, | |
| "perspective_scores": [result.partial_score], | |
| "innowator_done": True, | |
| } | |
| try: | |
| return invoke_eval() | |
| except Exception as e: | |
| logger.error(f"[INNOWATOR] Ostateczny błąd ewaluatora: {e}") | |
| return { | |
| "innowator_done": True, | |
| "perspectives_summary": { | |
| "innowator": f"Błąd audytu innowacyjnego po 5 próbach: {e}" | |
| }, | |
| } | |
| def innowator_routing(state: AuditorPanelState) -> str: | |
| """Decyduje czy Innowator musi szukać dalej, czy oceniać.""" | |
| last_message = state["innowator_messages"][-1] | |
| if last_message.tool_calls: | |
| if state.get("innowator_attempts", 0) >= 3: | |
| logger.warning( | |
| "[INNOWATOR] Przekroczono limit wyszukiwań, wymuszam ewaluację." | |
| ) | |
| return "evaluate" | |
| return "tools" | |
| return "evaluate" | |
| # --- ZARZĄDZAJĄCY NODE --- | |
| def zarzadzajacy_node(state: AuditorPanelState) -> Dict[str, Any]: | |
| """Reduktor zbierający wszystkie dane i tworzący GlobalAuditOutput.""" | |
| scores = state.get("perspective_scores", []) | |
| issues = state.get("issues", []) | |
| has_critical = any(i.severity == "critical" for i in issues) | |
| if not scores: | |
| overall_score = 0 | |
| else: | |
| base = int(sum(scores) / len(scores)) | |
| overall_score = max(0, base - 20) if has_critical else base | |
| export_status = "ok" | |
| if has_critical: | |
| export_status = "blocked" | |
| elif any(i.severity == "high" for i in issues): | |
| export_status = "warning" | |
| final = GlobalAuditOutput( | |
| is_approved=not has_critical, | |
| export_status=export_status, | |
| overall_score=overall_score, | |
| confidence_score=0.9, # LangGraph gives high confidence theoretically | |
| human_review_required=has_critical or overall_score < 60, | |
| issues=issues, | |
| perspectives_summary=state.get("perspectives_summary", {}), | |
| ) | |
| return {"final_output": final} | |