Spaces:
Sleeping
Sleeping
Download agents/main_agent.py from helloguddn/Suwon_multiculturalism: direct link, hf CLI and curl.
- Browser
- Download file 7.35 kB
-
https://huggingface.co/spaces/helloguddn/Suwon_multiculturalism/resolve/main/agents/main_agent.py
- Command line
-
hf download hf://spaces/helloguddn/Suwon_multiculturalism/agents/main_agent.py
-
curl -L -o main_agent.py https://huggingface.co/spaces/helloguddn/Suwon_multiculturalism/resolve/main/agents/main_agent.py
7.35 kB
| """Single-agent orchestrator using Gemini function calling.""" | |
| from __future__ import annotations | |
| import asyncio | |
| import google.generativeai as genai | |
| from schemas.chat import ChatRequest, ChatResponse, MapPin, WarningCard | |
| from tools import contract_scanner, dlp_tool, labor_law_rag, maps_tool, public_data_tool | |
| from services import gemini_service, storage_service | |
| _LANG_NAMES = {"ko": "한국어", "vi": "베트남어", "en": "English"} | |
| _DISCLAIMERS = { | |
| "ko": "⚠️ 본 서비스는 법적 조언이 아닌 참고용 가이드입니다. 최종 판단은 전문가와 상담하세요.", | |
| "vi": "⚠️ Dịch vụ này chỉ mang tính tham khảo, không phải tư vấn pháp lý. Hãy tham khảo ý kiến chuyên gia.", | |
| "en": "⚠️ This service is for reference only and not legal advice. Please consult a professional.", | |
| } | |
| _SYSTEM_PROMPT = """당신은 Co-Local입니다. 수원시에 거주하는 이주 근로자를 위한 노동권 전문 AI 어시스턴트입니다. | |
| 주요 역할: | |
| - 근로계약서의 법 위반 조항 분석 및 설명 | |
| - 한국 노동법(임금·근로시간·해고·연차·4대보험 등) 질문 답변 | |
| - 주변 노동청·복지센터·법률상담 기관 안내 | |
| - 임금체불 등 노동권 침해 상황 대처 방법 안내 | |
| 반드시 {LANGUAGE}로 응답하세요. | |
| 답변은 명확하고 실용적이어야 하며, 구체적인 연락처와 다음 행동 지침을 포함하세요. | |
| 법적 책임 면책 문구를 항상 답변 말미에 포함하세요.""" | |
| async def run_agent(request: ChatRequest) -> ChatResponse: | |
| gemini_service.configure() | |
| language = request.language | |
| session_id = request.session_id | |
| # 1. Contract image pre-processing (before main agent loop) | |
| warning_cards: list[WarningCard] = [] | |
| contract_context = "" | |
| if request.image_base64: | |
| scan_result = await contract_scanner.scan_contract_image( | |
| request.image_base64, language | |
| ) | |
| for v in scan_result.get("violations", []): | |
| warning_cards.append( | |
| WarningCard( | |
| title=v.get("title", "위반 항목"), | |
| content=v.get("content", ""), | |
| severity=v.get("severity", "medium"), | |
| ) | |
| ) | |
| summary = scan_result.get("summary", "") | |
| if summary: | |
| contract_context = f"\n\n[계약서 분석 완료]\n{summary}" | |
| if warning_cards: | |
| issues = "\n".join(f"- {w.title}: {w.content}" for w in warning_cards) | |
| contract_context += f"\n\n발견된 문제:\n{issues}" | |
| # 2. Per-turn mutable state (closures below write into these) | |
| map_pins_buffer: list[dict] = [] | |
| # 3. Tool functions — closures that capture map_pins_buffer | |
| def search_labor_law(query: str) -> str: | |
| """한국 노동법 관련 법조항을 검색합니다. 임금·근로시간·해고·연차·4대보험·임금체불 등 노동 관련 질문에 사용하세요. | |
| Args: | |
| query: 검색할 키워드 또는 질문 (예: '최저임금', '연장근무 수당', '부당해고 신고') | |
| """ | |
| return labor_law_rag.search_labor_law(query) | |
| def find_nearby_centers(latitude: float, longitude: float, center_type: str = "all") -> str: | |
| """사용자 위치 근처의 노동청·복지센터·법률상담 기관을 찾아 지도에 핀을 꽂습니다. | |
| Args: | |
| latitude: 위도 (사용자 GPS 또는 메시지에서 추출) | |
| longitude: 경도 (사용자 GPS 또는 메시지에서 추출) | |
| center_type: 'labor'(노동청/고용센터), 'welfare'(복지센터/다문화센터), 'legal'(법률상담), 'all'(전체) | |
| """ | |
| desc, pins = maps_tool.find_nearby_centers(latitude, longitude, center_type) | |
| map_pins_buffer.extend(pins) | |
| return desc | |
| def get_center_info(center_type: str = "all") -> str: | |
| """수원시 외국인 지원 기관의 전체 목록과 연락처를 조회합니다. | |
| Args: | |
| center_type: 'labor', 'welfare', 'legal', 'all' | |
| """ | |
| return public_data_tool.get_center_info(center_type) | |
| # 4. Build model with tools | |
| lang = _LANG_NAMES.get(language, "한국어") | |
| system = _SYSTEM_PROMPT.replace("{LANGUAGE}", lang) | |
| model = genai.GenerativeModel( | |
| model_name="gemini-3.1-pro", | |
| tools=[search_labor_law, find_nearby_centers, get_center_info], | |
| system_instruction=system, | |
| ) | |
| history = storage_service.get_history(session_id) | |
| chat = model.start_chat(history=history) | |
| # 5. Build message | |
| user_text = dlp_tool.mask_pii(request.message) | |
| if request.location: | |
| user_text += f"\n[현재 위치: 위도 {request.location.lat}, 경도 {request.location.lng}]" | |
| user_text += contract_context | |
| msg_parts: list = [] | |
| if request.image_base64: | |
| import base64, io | |
| from PIL import Image | |
| img_bytes = base64.b64decode(request.image_base64) | |
| img = Image.open(io.BytesIO(img_bytes)) | |
| msg_parts.append(img) | |
| msg_parts.append(user_text) | |
| # 6. Agent loop — manual function calling for full control | |
| response = await asyncio.to_thread(chat.send_message, msg_parts) | |
| for _ in range(8): | |
| fn_calls = [ | |
| p.function_call | |
| for p in response.parts | |
| if hasattr(p, "function_call") and p.function_call.name | |
| ] | |
| if not fn_calls: | |
| break | |
| tool_map = { | |
| "search_labor_law": search_labor_law, | |
| "find_nearby_centers": find_nearby_centers, | |
| "get_center_info": get_center_info, | |
| } | |
| tool_responses = [] | |
| for fc in fn_calls: | |
| fn = tool_map.get(fc.name) | |
| try: | |
| result = fn(**dict(fc.args)) if fn else f"알 수 없는 도구: {fc.name}" | |
| except Exception as exc: | |
| result = f"도구 오류: {exc}" | |
| tool_responses.append( | |
| genai.protos.Part( | |
| function_response=genai.protos.FunctionResponse( | |
| name=fc.name, | |
| response={"result": result}, | |
| ) | |
| ) | |
| ) | |
| response = await asyncio.to_thread(chat.send_message, tool_responses) | |
| # 7. Collect final reply text | |
| reply_parts = [p.text for p in response.parts if hasattr(p, "text") and p.text] | |
| reply = dlp_tool.mask_pii("".join(reply_parts)) | |
| # Append disclaimer if labor / contract content was involved | |
| if warning_cards or map_pins_buffer: | |
| reply += f"\n\n{_DISCLAIMERS.get(language, _DISCLAIMERS['ko'])}" | |
| # 8. Persist session history | |
| storage_service.save_history(session_id, list(chat.history)) | |
| # 9. Build next actions | |
| next_actions: list[str] = [] | |
| if warning_cards: | |
| next_actions += [ | |
| "고용노동부 신고: ☎1350 (무료, 외국어 지원)", | |
| "수원시 마을변호사 무료상담: ☎1899-3300", | |
| ] | |
| if map_pins_buffer: | |
| next_actions.append("지도에서 가까운 기관 위치 확인") | |
| return ChatResponse( | |
| session_id=session_id, | |
| reply=reply, | |
| warning_cards=warning_cards, | |
| map_pins=[MapPin(**p) for p in map_pins_buffer], | |
| next_actions=next_actions, | |
| ) | |