Spaces:
Sleeping
Sleeping
| """ | |
| Model Context Protocol(MCP) ์ฒ๋ฆฌ ๋ชจ๋ | |
| Claude์์ ํต์ ์ ์ํ MCP ํ๋กํ ์ฝ ์ฒ๋ฆฌ ํด๋์ค | |
| """ | |
| import os | |
| import json | |
| import logging | |
| import uuid | |
| from datetime import datetime | |
| from typing import Dict, List, Any, Optional, Union | |
| import mcp | |
| from src import config | |
| from src.utils import ( | |
| MCPError, | |
| DataProcessingError, | |
| generate_unique_id, | |
| save_json_data, | |
| merge_metadata | |
| ) | |
| logger = config.setup_logger("mcp_processor") | |
| class MCPProcessor: | |
| """ | |
| Claude์์ ํต์ ์ ์ํ MCP ํ๋กํ ์ฝ ์ฒ๋ฆฌ๊ธฐ | |
| MCP ์คํ์ ๋ง๊ฒ ์ปจํ ์คํธ๋ฅผ ์ค๋นํ๊ณ Claude์ ํต์ ํ๋ ๊ธฐ๋ฅ ์ ๊ณต | |
| """ | |
| _instance = None | |
| def __new__(cls, *args, **kwargs): | |
| """์ฑ๊ธํด ํจํด ๊ตฌํ""" | |
| if cls._instance is None: | |
| cls._instance = super(MCPProcessor, cls).__new__(cls) | |
| return cls._instance | |
| def __init__(self, model_name: str = None, api_key: str = None): | |
| """ | |
| MCP ํ๋ก์ธ์ ์ด๊ธฐํ | |
| Args: | |
| model_name: ์ฌ์ฉํ Claude ๋ชจ๋ธ๋ช | |
| api_key: API ํค | |
| """ | |
| # ์ด๋ฏธ ์ด๊ธฐํ๋ ๊ฒฝ์ฐ ์ค๋ณต ์ด๊ธฐํ ๋ฐฉ์ง | |
| if hasattr(self, 'initialized') and self.initialized: | |
| return | |
| # MCP SDK ํ๊ฒฝ ์ค์ | |
| self.model_name = model_name or config.MCP_MODEL_NAME | |
| self.api_key = api_key or config.MCP_API_KEY | |
| try: | |
| # MCP ํด๋ผ์ด์ธํธ ์ด๊ธฐํ | |
| self.client = mcp.Client( | |
| model=self.model_name, | |
| api_key=self.api_key if self.api_key else None # API ํค๊ฐ ์์ผ๋ฉด None ์ ๋ฌ | |
| ) | |
| self.conversations = {} # ๋ํ ๋ด์ฉ ์บ์ | |
| self.data_dir = config.DATA_DIR | |
| self.initialized = True | |
| logger.info(f"MCP ํ๋ก์ธ์ ์ด๊ธฐํ๋จ (๋ชจ๋ธ: {self.model_name})") | |
| except Exception as e: | |
| logger.error(f"MCP ํ๋ก์ธ์ ์ด๊ธฐํ ์คํจ: {str(e)}") | |
| raise MCPError(f"MCP ํ๋ก์ธ์ ์ด๊ธฐํ ์คํจ: {str(e)}", "init") | |
| def process_query( | |
| self, | |
| query: str, | |
| context_data: Optional[Union[List[Dict[str, Any]], Dict[str, Any]]] = None, | |
| conversation_id: Optional[str] = None, | |
| system_prompt: Optional[str] = None, | |
| temperature: float = 0.2, | |
| max_tokens: int = 2000 | |
| ) -> Dict[str, Any]: | |
| """ | |
| MCP๋ฅผ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ์ ๋ํ ์๋ต ์์ฑ | |
| Args: | |
| query: ์ฌ์ฉ์ ์ฟผ๋ฆฌ | |
| context_data: ์ปจํ ์คํธ ๋ฐ์ดํฐ | |
| conversation_id: ๋ํ ID | |
| system_prompt: ์์คํ ํ๋กฌํํธ | |
| temperature: ์์ฑ ์จ๋(์ฐฝ์์ฑ) | |
| max_tokens: ์ต๋ ์์ฑ ํ ํฐ ์ | |
| Returns: | |
| ์๋ต ๋ฐ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ํฌํจํ ๋์ ๋๋ฆฌ | |
| Raises: | |
| MCPError: MCP ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์ ์ | |
| """ | |
| if not query: | |
| raise MCPError("์ฟผ๋ฆฌ๊ฐ ๋น์ด ์์ต๋๋ค.", "empty_query") | |
| logger.info(f"MCP ์ฟผ๋ฆฌ ์ฒ๋ฆฌ ์ค: {query[:50]}...") | |
| # ์ปจํ ์คํธ ์ฒ๋ฆฌ | |
| context = None | |
| if context_data: | |
| context = self._prepare_context(context_data) | |
| logger.debug(f"์ปจํ ์คํธ ์ค๋น ์๋ฃ: {len(context)}๊ฐ ํญ๋ชฉ") | |
| # ๋ํ ID ์ฒ๋ฆฌ | |
| if not conversation_id: | |
| conversation_id = generate_unique_id("conv") | |
| logger.debug(f"์ ๋ํ ID ์์ฑ: {conversation_id}") | |
| # ์์คํ ํ๋กฌํํธ ์ค๋น | |
| if system_prompt: | |
| # ์ฌ์ฉ์ ์ ๊ณต ์์คํ ํ๋กฌํํธ ์ฌ์ฉ | |
| pass | |
| else: | |
| # ๊ธฐ๋ณธ ์์คํ ํ๋กฌํํธ ์ค์ | |
| system_prompt = self._get_default_system_prompt() | |
| try: | |
| # MCP๋ฅผ ํตํ ์๋ต ์์ฑ | |
| response = self.client.query( | |
| message=query, | |
| context=context, | |
| conversation_id=conversation_id, | |
| system_prompt=system_prompt, | |
| parameters={ | |
| "temperature": temperature, | |
| "max_tokens": max_tokens | |
| } | |
| ) | |
| logger.info(f"MCP ์๋ต ์์ฑ ์๋ฃ: {len(response.content)} ์") | |
| # ๋ํ ์ด๋ ฅ ์บ์ฑ | |
| self._update_conversation_cache(conversation_id, query, response.content, context) | |
| # ํ์ํ ๊ฒฝ์ฐ ๋ํ ์ ์ฅ | |
| # self.save_conversation(conversation_id) | |
| # ์๋ต ๋ฐ ์ฌ์ฉ๋ ์ปจํ ์คํธ ๋ฐํ | |
| result = { | |
| "content": response.content, | |
| "conversation_id": response.conversation_id, | |
| "used_context": getattr(response, 'used_context', None), | |
| "query": query, | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| return result | |
| except Exception as e: | |
| logger.error(f"MCP ์ฟผ๋ฆฌ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") | |
| raise MCPError(f"MCP ์ฟผ๋ฆฌ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}", "query_processing") | |
| def _prepare_context( | |
| self, | |
| context_data: Union[List[Dict[str, Any]], Dict[str, Any]] | |
| ) -> List[Dict[str, Any]]: | |
| """ | |
| MCP์ ์ ๋ฌํ ์ปจํ ์คํธ ์ค๋น | |
| Args: | |
| context_data: ์ปจํ ์คํธ ๋ฐ์ดํฐ | |
| Returns: | |
| MCP ํ์์ ์ปจํ ์คํธ ๋ฆฌ์คํธ | |
| """ | |
| if isinstance(context_data, list): | |
| # ์ด๋ฏธ ๋ฆฌ์คํธ ํํ๋ก ์ ๋ฌ๋ ๊ฒฝ์ฐ | |
| return [self._format_context_item(item) for item in context_data] | |
| elif isinstance(context_data, dict): | |
| # ๋จ์ผ ์์ดํ ์ด ๋์ ๋๋ฆฌ๋ก ์ ๋ฌ๋ ๊ฒฝ์ฐ | |
| return [self._format_context_item(context_data)] | |
| else: | |
| logger.error(f"์ง์๋์ง ์๋ ์ปจํ ์คํธ ๋ฐ์ดํฐ ํ์: {type(context_data)}") | |
| raise MCPError( | |
| f"์ง์๋์ง ์๋ ์ปจํ ์คํธ ๋ฐ์ดํฐ ํ์: {type(context_data)}", | |
| "invalid_context_format" | |
| ) | |
| def _format_context_item(self, item: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| ์ปจํ ์คํธ ์์ดํ ์ MCP ํ์์ผ๋ก ๋ณํ | |
| Args: | |
| item: ์๋ณธ ์ปจํ ์คํธ ์์ดํ | |
| Returns: | |
| MCP ํ์์ ์ปจํ ์คํธ ์์ดํ | |
| """ | |
| # ํ์ ํ๋ ๊ฒ์ฆ ('text' ๋๋ 'content') | |
| raw_text = item.get("text") or item.get("content") | |
| if not raw_text: | |
| raise MCPError("์ปจํ ์คํธ ์์ดํ ์ 'text' ๋๋ 'content' ํ๋๊ฐ ์์ต๋๋ค.", "missing_context_content") | |
| # ๊ณ ์ ID ์์ฑ ๋๋ ์ฌ์ฉ | |
| ctx_id = item.get("id", generate_unique_id("ctx")) | |
| # ๋ฉํ๋ฐ์ดํฐ ์ถ์ถ | |
| metadata = item.get("metadata", {}) | |
| source_code = metadata.get("code", "์ ์ ์์") | |
| relevance = item.get("relevance", metadata.get("relevance", 0)) | |
| # XML ํ๊ทธ๋ก ์ปจํ ์คํธ ๋ํ | |
| xml_content = f"""<evidence source="{source_code}" relevance="{relevance:.4f}"> | |
| {raw_text} | |
| </evidence>""" | |
| # ๊ธฐ๋ณธ ํ๋ ์ค์ (MCP ํ์ค ํ์) | |
| formatted = { | |
| "id": ctx_id, | |
| "content": xml_content | |
| } | |
| # ๋ฉํ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ์ถ๊ฐ | |
| if "metadata" in item: | |
| formatted["metadata"] = item["metadata"] | |
| # ์์ฑ ์๊ฐ ์ถ๊ฐ (์๋ ๊ฒฝ์ฐ) | |
| if "created_at" not in formatted["metadata"]: | |
| formatted["metadata"]["created_at"] = datetime.now().isoformat() | |
| else: | |
| # ๊ธฐ๋ณธ ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ | |
| formatted["metadata"] = { | |
| "source": "KCSC", | |
| "created_at": datetime.now().isoformat() | |
| } | |
| return formatted | |
| def _get_default_system_prompt(self) -> str: | |
| """๊ธฐ๋ณธ ์์คํ ํ๋กฌํํธ ์์ฑ""" | |
| return """ํ๊ตญ๊ฑด์ค๊ธฐ์ค์ผํฐ(KCSC)์ ๊ฑด์ค๊ธฐ์ค ์ ๋ณด์ ๋ํ ์ง์์๋ต์ ์ํํฉ๋๋ค. | |
| ์ฃผ์ด์ง ์ปจํ ์คํธ๋ฅผ ๋ฐํ์ผ๋ก ์ ํํ๊ณ ๊ฐ๊ด์ ์ธ ๋ต๋ณ์ ์ ๊ณตํ์ธ์. | |
| ๊ฑด์ค๊ธฐ์ค์ ๋ช ์๋ ๋ด์ฉ์ ์ฐ์ ์ ์ผ๋ก ์ฐธ๊ณ ํ๋, ๋ช ํํ ์ ๋ณด๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ์ด๋ฅผ ์์งํ๊ฒ ๋ฐํ๊ณ | |
| ์ผ๋ฐ์ ์ธ ๊ฑด์ถ๊ตฌ์กฐ ์ง์์ ๋ฐํ์ผ๋ก ๋ต๋ณํ ์ ์์ต๋๋ค. | |
| ๋ต๋ณ ์ ์ฐธ๊ณ ํ ์ฌํญ: | |
| 1. ์ธ์ฉ๋ ๊ฑด์ค๊ธฐ์ค ์ฝ๋์ ์ถ์ฒ๋ฅผ ๋ช ํํ ๊ธฐ์ฌํ์ธ์. | |
| 2. ๊ด๋ จ ์์์ด๋ ๋ํ๊ฐ ํ์ํ ๊ฒฝ์ฐ ์ ์ ํ ํฌ๋งทํ ํ์ฌ ์ ๊ณตํ์ธ์. | |
| 3. ๋ถ์์ ํ๊ฑฐ๋ ๋ชจํธํ ์ ๋ณด์ ๋ํด์๋ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์์ฒญํ๊ฑฐ๋ ์ ํ์ฌํญ์ ๋ช ์ํ์ธ์. | |
| 4. ๊ฐ๋ฅํ ํ ๊ด๋ จ ๋ฒ๊ท, ๊ท์ , ํ์ค๊ณผ ๊ฐ์ ๊ณต์ ์ ๋ณด๋ฅผ ์ฐธ์กฐํ์ธ์. | |
| 5. ํน์ ์ค๊ณ ๋ฌธ์ ์ ๋ํ ์ต์ข ์ฑ ์์ ํญ์ ๊ณต์ธ๋ ์ ๋ฌธ๊ฐ์๊ฒ ์์์ ๋ช ์ํ์ธ์. | |
| ํ๊ตญ์ด๋ก ๋ต๋ณํ๋, ํ์ํ ๊ฒฝ์ฐ ์ ๋ฌธ ์ฉ์ด๋ ํ๊ธ๊ณผ ์๋ฌธ์ ๋ณ๊ธฐํ ์ ์์ต๋๋ค.""" | |
| def _update_conversation_cache( | |
| self, | |
| conversation_id: str, | |
| query: str, | |
| response: str, | |
| context: Optional[List[Dict[str, Any]]] = None | |
| ): | |
| """ | |
| ๋ํ ์ด๋ ฅ ์บ์ ์ ๋ฐ์ดํธ | |
| Args: | |
| conversation_id: ๋ํ ID | |
| query: ์ฌ์ฉ์ ์ฟผ๋ฆฌ | |
| response: AI ์๋ต | |
| context: ์ฌ์ฉ๋ ์ปจํ ์คํธ | |
| """ | |
| if conversation_id not in self.conversations: | |
| self.conversations[conversation_id] = { | |
| "id": conversation_id, | |
| "messages": [], | |
| "contexts": [], | |
| "created_at": datetime.now().isoformat(), | |
| "updated_at": datetime.now().isoformat() | |
| } | |
| # ๋ฉ์์ง ์ถ๊ฐ | |
| self.conversations[conversation_id]["messages"].append({ | |
| "role": "user", | |
| "content": query, | |
| "timestamp": datetime.now().isoformat() | |
| }) | |
| self.conversations[conversation_id]["messages"].append({ | |
| "role": "assistant", | |
| "content": response, | |
| "timestamp": datetime.now().isoformat() | |
| }) | |
| # ์ปจํ ์คํธ ์ถ๊ฐ (์ค๋ณต ๋ฐฉ์ง) | |
| if context: | |
| for ctx in context: | |
| ctx_id = ctx.get("id") | |
| if ctx_id: | |
| # ์ด๋ฏธ ์๋ ์ปจํ ์คํธ์ธ์ง ํ์ธ | |
| existing_ctx = next( | |
| (c for c in self.conversations[conversation_id]["contexts"] if c.get("id") == ctx_id), | |
| None | |
| ) | |
| if not existing_ctx: | |
| self.conversations[conversation_id]["contexts"].append(ctx) | |
| # ์ ๋ฐ์ดํธ ์๊ฐ ๊ฐฑ์ | |
| self.conversations[conversation_id]["updated_at"] = datetime.now().isoformat() | |
| def get_conversation_history(self, conversation_id: str) -> Optional[Dict[str, Any]]: | |
| """ | |
| ๋ํ ์ด๋ ฅ ์กฐํ | |
| Args: | |
| conversation_id: ๋ํ ID | |
| Returns: | |
| ๋ํ ์ด๋ ฅ ๋๋ None (์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ) | |
| """ | |
| # ์บ์์์ ๋จผ์ ์กฐํ | |
| if conversation_id in self.conversations: | |
| return self.conversations[conversation_id] | |
| # MCP SDK์ ๋ํ ์ด๋ ฅ ์กฐํ ๊ธฐ๋ฅ ์ฌ์ฉ | |
| try: | |
| logger.info(f"MCP์์ ๋ํ ์ด๋ ฅ ์กฐํ ์ค: {conversation_id}") | |
| history = self.client.get_conversation(conversation_id) | |
| # ์บ์ ์ ๋ฐ์ดํธ | |
| self.conversations[conversation_id] = history | |
| return history | |
| except Exception as e: | |
| logger.error(f"๋ํ ์ด๋ ฅ ์กฐํ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") | |
| return None | |
| def save_conversation( | |
| self, | |
| conversation_id: str, | |
| file_prefix: str = "conversation", | |
| include_context: bool = True | |
| ) -> Optional[str]: | |
| """ | |
| ๋ํ ๋ด์ฉ ์ ์ฅ | |
| Args: | |
| conversation_id: ์ ์ฅํ ๋ํ ID | |
| file_prefix: ํ์ผ๋ช ์ ๋์ฌ | |
| include_context: ์ปจํ ์คํธ ํฌํจ ์ฌ๋ถ | |
| Returns: | |
| ์ ์ฅ๋ ํ์ผ ๊ฒฝ๋ก ๋๋ None (์คํจ ์) | |
| """ | |
| if not conversation_id: | |
| logger.warning("์ ์ฅํ ๋ํ ID๊ฐ ์์ต๋๋ค.") | |
| return None | |
| try: | |
| # ๋ํ ๋ด์ฉ ๊ฐ์ ธ์ค๊ธฐ | |
| conversation = self.get_conversation_history(conversation_id) | |
| if not conversation: | |
| logger.warning(f"๋ํ ID {conversation_id}์ ํด๋นํ๋ ๋ํ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.") | |
| return None | |
| # ์ปจํ ์คํธ ํํฐ๋ง (์ ํ์ ) | |
| if not include_context and "contexts" in conversation: | |
| conversation = conversation.copy() | |
| conversation.pop("contexts", None) | |
| # ํ์ผ์ ์ ์ฅ | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filename = f"{file_prefix}_{timestamp}.json" | |
| filepath = save_json_data(conversation, filename, self.data_dir) | |
| logger.info(f"๋ํ ๋ด์ฉ ์ ์ฅ ์๋ฃ: {filepath}") | |
| return filepath | |
| except Exception as e: | |
| logger.error(f"๋ํ ์ ์ฅ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") | |
| return None | |
| def delete_conversation(self, conversation_id: str) -> bool: | |
| """ | |
| ๋ํ ์ญ์ | |
| Args: | |
| conversation_id: ์ญ์ ํ ๋ํ ID | |
| Returns: | |
| ์ฑ๊ณต ์ฌ๋ถ | |
| """ | |
| if not conversation_id: | |
| logger.warning("์ญ์ ํ ๋ํ ID๊ฐ ์์ต๋๋ค.") | |
| return False | |
| try: | |
| # ์บ์์์ ์ญ์ | |
| if conversation_id in self.conversations: | |
| del self.conversations[conversation_id] | |
| # MCP ํด๋ผ์ด์ธํธ์์ ์ญ์ (API ์ง์ ์) | |
| try: | |
| self.client.delete_conversation(conversation_id) | |
| except Exception as e: | |
| logger.warning(f"MCP ํด๋ผ์ด์ธํธ์์ ๋ํ ์ญ์ ์คํจ (๊ธฐ๋ฅ ๋ฏธ์ง์): {str(e)}") | |
| logger.info(f"๋ํ ID {conversation_id} ์ญ์ ์๋ฃ") | |
| return True | |
| except Exception as e: | |
| logger.error(f"๋ํ ์ญ์ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") | |
| return False | |
| # ์คํ ์์ | |
| if __name__ == "__main__": | |
| # ์ค์ ์ด๊ธฐํ | |
| config.init() | |
| # MCP ํ๋ก์ธ์ ์ด๊ธฐํ | |
| processor = MCPProcessor() | |
| # ํ ์คํธ ์ฟผ๋ฆฌ | |
| test_query = "์ฝํฌ๋ฆฌํธ ๊ธฐ๋ฅ์ ์ต์ ์ฒ ๊ทผ๋น๋ ์ผ๋ง์ธ๊ฐ์?" | |
| # ํ ์คํธ ์ปจํ ์คํธ | |
| test_context = [ | |
| { | |
| "id": "ctx-1", | |
| "content": "KDS 14 20 01 ์ฝํฌ๋ฆฌํธ๊ตฌ์กฐ ์ค๊ณ๊ธฐ์ค์ ๋ฐ๋ฅด๋ฉด, ๊ธฐ๋ฅ์ ์ต์ ์ฒ ๊ทผ๋น๋ ๋จ๋ฉด์ ์ 1%์ด๋ค.", | |
| "metadata": { | |
| "source": "KDS 14 20 01", | |
| "title": "์ฝํฌ๋ฆฌํธ๊ตฌ์กฐ ์ค๊ณ๊ธฐ์ค", | |
| "relevance": 0.95 | |
| } | |
| } | |
| ] | |
| # ์ฟผ๋ฆฌ ์ฒ๋ฆฌ | |
| result = processor.process_query(test_query, test_context) | |
| print(result["content"]) | |