search_within_tuzuk
Search specific Turkish statutes' articles using keyword queries with Boolean operators or semantic AI search for natural language questions.
Instructions
Search within a specific Statute's (Tüzük) articles using keyword or semantic search.
Modes:
semantic=False (default): Keyword search with Boolean operators (AND/OR/NOT, uppercase required)
semantic=True: Natural language semantic search using AI embeddings (requires OPENROUTER_API_KEY)
Keyword examples: "tapu AND sicil", '"sicil kayıt"', "tescil OR ilan" Semantic examples: "tapu sicil kayıt işlemleri", "vakıf tescil süreci"
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| mevzuat_no | Yes | The statute number to search within (e.g., '20135150', '20134513', '200814001') | |
| keyword | Yes | Search query. For keyword mode: supports AND/OR/NOT operators (uppercase). For semantic mode: use natural language. | |
| mevzuat_tertip | No | Statute series from search results (e.g., '5') | 5 |
| case_sensitive | No | Whether to match case when searching (default: False). Only used in keyword mode. | |
| max_results | No | Maximum number of matching articles to return (1-50, default: 25) | |
| semantic | No | True: semantic search (natural language query, requires OPENROUTER_API_KEY). False: keyword search (Boolean operators AND/OR/NOT). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- mevzuat_mcp_server.py:1406-1444 (registration)The @app.tool() decorator registers 'search_within_tuzuk' as an MCP tool on the FastMCP server.
@app.tool() async def search_within_tuzuk( mevzuat_no: str = Field( ..., description="The statute number to search within (e.g., '20135150', '20134513', '200814001')" ), keyword: str = Field( ..., description='Search query. For keyword mode: supports AND/OR/NOT operators (uppercase). For semantic mode: use natural language.' ), mevzuat_tertip: str = Field( "5", description="Statute series from search results (e.g., '5')" ), case_sensitive: bool = Field( False, description="Whether to match case when searching (default: False). Only used in keyword mode." ), max_results: int = Field( 25, ge=1, le=50, description="Maximum number of matching articles to return (1-50, default: 25)" ), semantic: bool = Field( False, description="True: semantic search (natural language query, requires OPENROUTER_API_KEY). False: keyword search (Boolean operators AND/OR/NOT)." ) ) -> str: """ Search within a specific Statute's (Tüzük) articles using keyword or semantic search. Modes: - semantic=False (default): Keyword search with Boolean operators (AND/OR/NOT, uppercase required) - semantic=True: Natural language semantic search using AI embeddings (requires OPENROUTER_API_KEY) Keyword examples: "tapu AND sicil", '"sicil kayıt"', "tescil OR ilan" Semantic examples: "tapu sicil kayıt işlemleri", "vakıf tescil süreci" """ - mevzuat_mcp_server.py:1407-1476 (handler)Handler function for 'search_within_tuzuk'. For semantic=True, delegates to _semantic_search_within with mevzuat_tur=2 (Tüzük). For keyword mode, fetches content via mevzuat_client.get_content with tur=2, then searches articles using search_articles_by_keyword, and formats results with format_search_results.
async def search_within_tuzuk( mevzuat_no: str = Field( ..., description="The statute number to search within (e.g., '20135150', '20134513', '200814001')" ), keyword: str = Field( ..., description='Search query. For keyword mode: supports AND/OR/NOT operators (uppercase). For semantic mode: use natural language.' ), mevzuat_tertip: str = Field( "5", description="Statute series from search results (e.g., '5')" ), case_sensitive: bool = Field( False, description="Whether to match case when searching (default: False). Only used in keyword mode." ), max_results: int = Field( 25, ge=1, le=50, description="Maximum number of matching articles to return (1-50, default: 25)" ), semantic: bool = Field( False, description="True: semantic search (natural language query, requires OPENROUTER_API_KEY). False: keyword search (Boolean operators AND/OR/NOT)." ) ) -> str: """ Search within a specific Statute's (Tüzük) articles using keyword or semantic search. Modes: - semantic=False (default): Keyword search with Boolean operators (AND/OR/NOT, uppercase required) - semantic=True: Natural language semantic search using AI embeddings (requires OPENROUTER_API_KEY) Keyword examples: "tapu AND sicil", '"sicil kayıt"', "tescil OR ilan" Semantic examples: "tapu sicil kayıt işlemleri", "vakıf tescil süreci" """ logger.info(f"Tool 'search_within_tuzuk' called: {mevzuat_no}, keyword: '{keyword}', semantic: {semantic}") try: if semantic: if not SEMANTIC_SEARCH_AVAILABLE: return "Error: Semantic search requires OPENROUTER_API_KEY environment variable." return await _semantic_search_within( mevzuat_no=mevzuat_no, query=keyword, mevzuat_tur=2, mevzuat_tertip=mevzuat_tertip, max_results=max_results ) content_result = await mevzuat_client.get_content( mevzuat_no=mevzuat_no, mevzuat_tur=2, mevzuat_tertip=mevzuat_tertip ) if content_result.error_message: return f"Error fetching statute content: {content_result.error_message}" matches = search_articles_by_keyword( markdown_content=content_result.markdown_content, keyword=keyword, case_sensitive=case_sensitive, max_results=max_results ) result = ArticleSearchResult( mevzuat_no=mevzuat_no, mevzuat_tur=2, keyword=keyword, total_matches=len(matches), matching_articles=matches ) if len(matches) == 0: return f"No articles found containing '{keyword}' in Tüzük {mevzuat_no}" return format_search_results(result) except Exception as e: logger.exception(f"Error in tool 'search_within_tuzuk' for {mevzuat_no}") return f"An unexpected error occurred while searching Tüzük {mevzuat_no}: {str(e)}" - mevzuat_models.py:32-84 (schema)Schema model used for search operations (MevzuatTurLiteral includes 'Tuzuk').
class MevzuatSearchRequestNew(BaseModel): """Request model for searching legislation on mevzuat.gov.tr""" mevzuat_tur: MevzuatTurLiteral = Field( "Kanun", description="Type of legislation. Currently only 'Kanun' (laws) are fully supported for content extraction." ) aranacak_ifade: Optional[str] = Field( None, description="Search term or phrase to look for in legislation" ) aranacak_yer: int = Field( 3, ge=1, le=3, description="Where to search: 1=Title only, 2=Article titles, 3=Full text (default)" ) tam_cumle: bool = Field( False, description="Exact phrase match (true) or any word match (false, default)" ) mevzuat_no: Optional[str] = Field( None, description="Specific legislation number to search for" ) baslangic_tarihi: Optional[str] = Field( None, description="Start date for filtering (format: DD.MM.YYYY)" ) bitis_tarihi: Optional[str] = Field( None, description="End date for filtering (format: DD.MM.YYYY)" ) page_number: int = Field( 1, ge=1, description="Page number of results" ) page_size: int = Field( 10, ge=1, le=100, description="Number of results per page" ) - mevzuat_mcp_server.py:94-182 (helper)Shared helper function used for semantic search. The search_within_tuzuk handler calls this with mevzuat_tur=2 (Tuzuk type code). It fetches content, processes into chunks, builds a vector store, queries with the user's query, and returns formatted semantic results.
async def _semantic_search_within( mevzuat_no: str, query: str, mevzuat_tur: int, mevzuat_tertip: str = "5", max_results: int = 10, threshold: float = 0.3, resmi_gazete_tarihi: Optional[str] = None, ) -> str: """Shared helper for semantic search within any legislation type.""" # 1. Get content with tertip fallback (already cached by mevzuat_client) content_result = await _get_content_with_tertip_fallback( mevzuat_no=mevzuat_no, mevzuat_tur=mevzuat_tur, mevzuat_tertip=mevzuat_tertip, resmi_gazete_tarihi=resmi_gazete_tarihi, ) if content_result.error_message: return f"Error fetching content: {content_result.error_message}" if not content_result.markdown_content: return f"Error: No content found for mevzuat {mevzuat_no}" content = content_result.markdown_content # 2. Check embedding cache cached = _embedding_cache.get(mevzuat_tur, mevzuat_tertip, mevzuat_no, content) if cached: vector_store, chunks = cached else: # 3. Process into chunks chunks = _processor.process_legislation(content, mevzuat_no, mevzuat_tur) if not chunks: return f"Error: Could not split content into searchable segments for mevzuat {mevzuat_no}" # 4. Encode documents texts = [c.text for c in chunks] titles = [c.title for c in chunks] embeddings = _embedder.encode_documents(texts, titles) # 5. Build vector store vector_store = VectorStore(dimension=_embedder.dimension) vector_store.add_documents( ids=[c.chunk_id for c in chunks], texts=texts, embeddings=embeddings, metadata=[c.metadata for c in chunks], ) # 6. Cache _embedding_cache.put(mevzuat_tur, mevzuat_tertip, mevzuat_no, content, vector_store, chunks) # 7. Search query_embedding = _embedder.encode_query(query) results = vector_store.search(query_embedding, top_k=max_results, threshold=threshold) if not results: return f"No semantically similar content found for '{query}' in mevzuat {mevzuat_no}" # 8. Format results # Determine method description chunk_type = chunks[0].metadata.get('type', 'chunk') if chunks else 'chunk' method = "Article-based semantic search" if chunk_type == 'article' else "Chunk-based semantic search" output = [] output.append("Semantic Search Results") output.append(f"Query: \"{query}\"") output.append(f"Legislation: {mevzuat_no} (type: {mevzuat_tur})") output.append(f"Method: {method} | Results: {len(results)}") output.append("") for doc, score in results: if chunk_type == 'article': madde_no = doc.metadata.get('madde_no', '?') madde_title = doc.metadata.get('madde_title', '') output.append(f"=== MADDE {madde_no} === (Similarity: {score:.2f})") if madde_title: output.append(f"Title: {madde_title}") else: chunk_idx = doc.metadata.get('chunk_index', 0) total = doc.metadata.get('total_chunks', 0) output.append(f"=== Chunk {chunk_idx + 1}/{total} === (Similarity: {score:.2f})") output.append("") output.append(doc.text) output.append("") return "\n".join(output)