|
12 | 12 | import mcp.types as types |
13 | 13 | from mcp.server.fastmcp import FastMCP |
14 | 14 |
|
| 15 | +from omop_mcp import utils |
15 | 16 | from omop_mcp.prompts import EXAMPLE_INPUT, EXAMPLE_OUTPUT, MCP_DOC_INSTRUCTION |
16 | 17 |
|
17 | 18 | BASE_DIR = Path(__file__).parent |
@@ -122,83 +123,56 @@ async def find_omop_concept( |
122 | 123 | max_results: Maximum number of candidate concepts to return |
123 | 124 |
|
124 | 125 | Returns: |
125 | | - Dict containing candidate concepts or error information. |
| 126 | + Dict containing candidate concepts or error information if no results found. |
126 | 127 | """ |
127 | 128 | logging.info( |
128 | 129 | f"find_omop_concept called with keyword='{keyword}', omop_table='{omop_table}', omop_field='{omop_field}'" |
129 | 130 | ) |
130 | 131 |
|
131 | | - # Create a new session for each request |
132 | | - async with aiohttp.ClientSession() as session: |
133 | | - url = "https://athena.ohdsi.org/api/v1/concepts" |
134 | | - params = {"query": keyword} |
135 | | - headers = { |
136 | | - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", |
137 | | - "Accept": "application/json, text/plain, */*", |
138 | | - "Accept-Language": "en-US,en;q=0.5", |
139 | | - "Referer": "https://athena.ohdsi.org/search-terms", |
140 | | - "Origin": "https://athena.ohdsi.org", |
141 | | - } |
142 | | - |
143 | | - try: |
144 | | - async with session.get(url, params=params, headers=headers) as response: |
145 | | - response.raise_for_status() |
146 | | - data = await response.json() |
147 | | - except aiohttp.ClientError as e: |
148 | | - return { |
149 | | - "error": f"Failed to query Athena: {str(e)}", |
150 | | - } |
151 | | - |
152 | | - logging.debug(f"Athena response: {data}") |
153 | | - concepts = [] |
154 | | - if isinstance(data, dict) and "content" in data: |
155 | | - concepts = data["content"] |
156 | | - elif isinstance(data, list): |
157 | | - concepts = data |
158 | | - elif isinstance(data, dict): |
159 | | - for key in ("content", "results", "items", "concepts"): |
160 | | - if key in data and isinstance(data[key], list): |
161 | | - concepts = data[key] |
162 | | - break |
163 | | - |
164 | | - if not concepts: |
165 | | - return { |
166 | | - "error": "No results found or unexpected response structure.", |
167 | | - } |
168 | | - |
169 | | - # Return multiple candidates with all their metadata for LLM to evaluate |
170 | | - candidates = [] |
171 | | - for i, c in enumerate(concepts[:max_results]): |
172 | | - candidate = { |
173 | | - "concept_id": c.get("id", ""), |
174 | | - "code": c.get("code", ""), |
175 | | - "name": c.get("name", ""), |
176 | | - "class": c.get("className", ""), |
177 | | - "concept": c.get("standardConcept", ""), |
178 | | - "validity": c.get("invalidReason", c.get("validity", "")), |
179 | | - "domain": c.get("domain", c.get("domainId", "")), |
180 | | - "vocab": c.get("vocabulary", c.get("vocabularyId", "")), |
181 | | - "url": f"https://athena.ohdsi.org/search-terms/terms/{c.get('id', '')}", |
182 | | - } |
183 | | - candidates.append(candidate) |
| 132 | + try: |
| 133 | + concepts = await utils.search_athena_concept_async(keyword) |
| 134 | + except Exception as e: |
| 135 | + logging.error(f"Athena API call failed: {e}") |
| 136 | + raise RuntimeError(f"Athena API is not accessible: {e}") from e |
184 | 137 |
|
| 138 | + if not concepts: |
185 | 139 | return { |
186 | | - "candidates": candidates, |
187 | | - "search_metadata": { |
188 | | - "keyword_searched": keyword, |
189 | | - "omop_table": omop_table, |
190 | | - "omop_field": omop_field, |
191 | | - "total_found": len(concepts), |
192 | | - "candidates_returned": len(candidates), |
193 | | - "selection_guidance": ( |
194 | | - "Select the most appropriate concept based on clinical context. " |
195 | | - "Access omop://preferred_vocabularies for vocabulary preferences. " |
196 | | - "Generally prefer Standard + Valid concepts from recommended vocabularies, " |
197 | | - "but context may require different choices (e.g., research needs, " |
198 | | - "specific vocabulary requirements, or non-standard mappings)." |
199 | | - ), |
200 | | - }, |
| 140 | + "error": f"No results found for keyword '{keyword}'. The search term may not exist in the OMOP vocabulary.", |
| 141 | + } |
| 142 | + |
| 143 | + # Return multiple candidates with all their metadata for LLM to evaluate |
| 144 | + candidates = [] |
| 145 | + for i, c in enumerate(concepts[:max_results]): |
| 146 | + candidate = { |
| 147 | + "concept_id": c.get("id", ""), |
| 148 | + "code": c.get("code", ""), |
| 149 | + "name": c.get("name", ""), |
| 150 | + "class": c.get("className", ""), |
| 151 | + "concept": c.get("standardConcept", ""), |
| 152 | + "validity": c.get("invalidReason", c.get("validity", "")), |
| 153 | + "domain": c.get("domain", c.get("domainId", "")), |
| 154 | + "vocab": c.get("vocabulary", c.get("vocabularyId", "")), |
| 155 | + "url": f"https://athena.ohdsi.org/search-terms/terms/{c.get('id', '')}", |
201 | 156 | } |
| 157 | + candidates.append(candidate) |
| 158 | + |
| 159 | + return { |
| 160 | + "candidates": candidates, |
| 161 | + "search_metadata": { |
| 162 | + "keyword_searched": keyword, |
| 163 | + "omop_table": omop_table, |
| 164 | + "omop_field": omop_field, |
| 165 | + "total_found": len(concepts), |
| 166 | + "candidates_returned": len(candidates), |
| 167 | + "selection_guidance": ( |
| 168 | + "Select the most appropriate concept based on clinical context. " |
| 169 | + "Access omop://preferred_vocabularies for vocabulary preferences. " |
| 170 | + "Generally prefer Standard + Valid concepts from recommended vocabularies, " |
| 171 | + "but context may require different choices (e.g., research needs, " |
| 172 | + "specific vocabulary requirements, or non-standard mappings)." |
| 173 | + ), |
| 174 | + }, |
| 175 | + } |
202 | 176 |
|
203 | 177 |
|
204 | 178 | @mcp.tool() |
|
0 commit comments