Spaces:
Running
Running
| # ruff: noqa: E402 | |
| import logging | |
| from typing import Dict, Any | |
| from langchain_core.messages import SystemMessage, HumanMessage | |
| from schemas import AgentState | |
| logger = logging.getLogger(__name__) | |
| from schemas import MatchOutput | |
| from core.llm_router import get_llm | |
| def matcher_node(state: Any) -> Dict[str, Any]: | |
| """ | |
| W臋ze艂 sprawdzaj膮cy dopasowanie ka偶dego z wyszukanych nabor贸w do profilu firmy. | |
| U偶ywa lokalnego modelu LLM do wyliczenia "relevance_score" oraz poetyckiego podsumowania "poetic_match". | |
| """ | |
| try: | |
| if isinstance(state, dict): | |
| state = AgentState(**state) | |
| except Exception as e: | |
| logger.error(f"B艂膮d inicjalizacji stanu w matcher_node: {e}") | |
| return {"current_agent": "supervisor"} | |
| profile = state.profile | |
| grants = state.eligible_grants | |
| if not profile or not grants: | |
| return { | |
| "current_agent": "supervisor" | |
| } # Je艣li brak danych, przeka偶 do supervisora | |
| # Budujemy prosty opis firmy jako ci膮g tekstowy | |
| company_desc = f"Firma {profile.nip}, bran偶a: {', '.join(profile.pkd_codes)}, wielko艣膰: {profile.size}. " | |
| if profile.innovation_focus: | |
| company_desc += ( | |
| f"Skupienie na innowacjach: {', '.join(profile.innovation_focus)}. " | |
| ) | |
| if profile.investment_plans: | |
| plans = ", ".join([p.description for p in profile.investment_plans]) | |
| company_desc += f"Plany inwestycyjne: {plans}." | |
| logger.info(f"Matcher uruchomiony dla {len(grants)} znalezisk dla {profile.nip}") | |
| from agents.helpers import ANTI_HALLUCINATION_PROMPT | |
| system_prompt = ( | |
| ANTI_HALLUCINATION_PROMPT + "\n\n" | |
| "Jeste艣 analitykiem ds. dotacji o zaci臋ciu wirtualnego poety funduszowego. " | |
| "Twoim zadaniem jest ocena dopasowania dotacji do profilu firmy. " | |
| "Dla ka偶dej podanej dotacji i firmy oce艅 procentowe dopasowanie (relevance_score od 0 do 1), " | |
| "napisz jedno zwi臋z艂e zdanie (poetic_match) po polsku opisuj膮ce to dopasowanie oraz podaj wyja艣nienie (explanation), " | |
| "kt贸re logicznie uzasadnia t臋 ocen臋: kr贸tki pow贸d (reason), kluczowe wspieraj膮ce kryteria (criteria) " | |
| "oraz ewentualne ryzyka/twarde warunki (risks). Odpowiadaj ZAWSZE I WY艁膭CZNIE w j臋zyku polskim." | |
| ) | |
| updated_grants = [] | |
| # Przesiewamy granty, by ocenia膰 tylko te brakuj膮ce | |
| grants_to_eval = [] | |
| for grant in grants: | |
| if grant.poetic_match and grant.relevance_score > 0 and grant.explanation: | |
| updated_grants.append(grant) | |
| else: | |
| grants_to_eval.append(grant) | |
| if not grants_to_eval: | |
| return {"eligible_grants": updated_grants, "current_agent": "supervisor"} | |
| system_msgs = [SystemMessage(content=system_prompt) for _ in grants_to_eval] | |
| human_msgs = [ | |
| HumanMessage( | |
| content=( | |
| f"Profil Firmy: {company_desc}\n" | |
| f"Nab贸r: {grant.title}\n" | |
| f"Instytucja: {grant.institution}\n" | |
| f"Opis Naboru: {grant.description}\n\n" | |
| "Zwr贸膰 wynik dopasowania uwzgl臋dniaj膮c struktur臋 MatchOutput." | |
| ) | |
| ) | |
| for grant in grants_to_eval | |
| ] | |
| messages_batch = [[s, h] for s, h in zip(system_msgs, human_msgs)] | |
| try: | |
| llm = get_llm(task_type="standard", structured_output_schema=MatchOutput) | |
| # R贸wnoleg艂e wywo艂anie wszystkich ewaluacji naraz | |
| results = llm.batch(messages_batch, config={"max_concurrency": 3}) | |
| for grant, parsed in zip(grants_to_eval, results): | |
| if isinstance(parsed, dict): | |
| grant.relevance_score = parsed.get("relevance_score", 0.0) | |
| grant.poetic_match = parsed.get("poetic_match", "") | |
| grant.explanation = parsed.get("explanation") | |
| else: | |
| grant.relevance_score = getattr(parsed, "relevance_score", 0.0) | |
| grant.poetic_match = getattr(parsed, "poetic_match", "") | |
| if getattr(parsed, "explanation", None): | |
| grant.explanation = parsed.explanation.dict() if hasattr(parsed.explanation, "dict") else parsed.explanation | |
| updated_grants.append(grant) | |
| except Exception as e: | |
| logger.error(f"B艂膮d LLM w operacji matcher_node (wieloprocesowej): {e}. Pr贸ba wykonania sekwencyjnego...") | |
| # Awaryjnie wykonaj zapytania sekwencyjnie | |
| for i, grant in enumerate(grants_to_eval): | |
| try: | |
| parsed = llm.invoke(messages_batch[i]) | |
| if isinstance(parsed, dict): | |
| grant.relevance_score = parsed.get("relevance_score", 0.0) | |
| grant.poetic_match = parsed.get("poetic_match", "") | |
| grant.explanation = parsed.get("explanation") | |
| else: | |
| grant.relevance_score = getattr(parsed, "relevance_score", 0.0) | |
| grant.poetic_match = getattr(parsed, "poetic_match", "") | |
| if getattr(parsed, "explanation", None): | |
| grant.explanation = parsed.explanation.dict() if hasattr(parsed.explanation, "dict") else parsed.explanation | |
| updated_grants.append(grant) | |
| except Exception as seq_err: | |
| logger.error(f"B艂膮d sekwencyjny dla grantu {grant.title}: {seq_err}") | |
| grant.relevance_score = 0.0 | |
| grant.poetic_match = "B艂膮d dopasowania." | |
| grant.explanation = { | |
| "reason": "B艂膮d LLM (fallback sekwencyjny)", | |
| "criteria": [], | |
| "risks": "Nie uda艂o si臋 zweryfikowa膰", | |
| } | |
| updated_grants.append(grant) | |
| return {"eligible_grants": updated_grants, "current_agent": "supervisor"} | |