From 8e53fd63453418f10cae5c12d3950891f79bd912 Mon Sep 17 00:00:00 2001 From: bsiggel Date: Sun, 15 Mar 2026 16:37:57 +0000 Subject: [PATCH] fix: Enhance tool binding in LangChainXAIService to support web search and update API handler for new parameters --- services/langchain_xai_service.py | 90 ++++++++++++++++++----- steps/vmh/xai_chat_completion_api_step.py | 33 ++++++--- 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/services/langchain_xai_service.py b/services/langchain_xai_service.py index 467efc4..28d0e99 100644 --- a/services/langchain_xai_service.py +++ b/services/langchain_xai_service.py @@ -84,6 +84,72 @@ class LangChainXAIService: return ChatXAI(**kwargs) + def bind_tools( + self, + model, + collection_id: Optional[str] = None, + enable_web_search: bool = False, + web_search_config: Optional[Dict[str, Any]] = None, + max_num_results: int = 10 + ): + """ + Bindet xAI Tools (file_search und/oder web_search) an Model. + + Args: + model: ChatXAI model instance + collection_id: Optional xAI Collection ID fΓΌr file_search + enable_web_search: Enable web search tool (default: False) + web_search_config: Optional web search configuration: + { + 'allowed_domains': ['example.com'], # Max 5 domains + 'excluded_domains': ['spam.com'], # Max 5 domains + 'enable_image_understanding': True + } + max_num_results: Max results from file search (default: 10) + + Returns: + Model with requested tools bound (file_search and/or web_search) + """ + tools = [] + + # Add file_search tool if collection_id provided + if collection_id: + self._log(f"πŸ” Binding file_search: collection={collection_id}") + tools.append({ + "type": "file_search", + "vector_store_ids": [collection_id], + "max_num_results": max_num_results + }) + + # Add web_search tool if enabled + if enable_web_search: + self._log("🌐 Binding web_search") + web_search_tool = {"type": "web_search"} + + # Add optional web search filters + if web_search_config: + if 'allowed_domains' in web_search_config: + domains = web_search_config['allowed_domains'][:5] # Max 5 + web_search_tool['filters'] = {'allowed_domains': domains} + self._log(f" Allowed domains: {domains}") + elif 'excluded_domains' in web_search_config: + domains = web_search_config['excluded_domains'][:5] # Max 5 + web_search_tool['filters'] = {'excluded_domains': domains} + self._log(f" Excluded domains: {domains}") + + if web_search_config.get('enable_image_understanding'): + web_search_tool['enable_image_understanding'] = True + self._log(" Image understanding: enabled") + + tools.append(web_search_tool) + + if not tools: + self._log("⚠️ No tools to bind (no collection_id and web_search disabled)", level='warn') + return model + + self._log(f"πŸ”§ Binding {len(tools)} tool(s) to model") + return model.bind_tools(tools) + def bind_file_search( self, model, @@ -91,25 +157,15 @@ class LangChainXAIService: max_num_results: int = 10 ): """ - Bindet xAI file_search Tool an Model. + Legacy method: Bindet nur file_search Tool an Model. - Args: - model: ChatXAI model instance - collection_id: xAI Collection ID (vector store) - max_num_results: Max results from file search (default: 10) - - Returns: - Model with bound file_search tool + Use bind_tools() for more flexibility. """ - self._log(f"πŸ” Binding file_search: collection={collection_id}, max_results={max_num_results}") - - tools = [{ - "type": "file_search", - "vector_store_ids": [collection_id], - "max_num_results": max_num_results - }] - - return model.bind_tools(tools) + return self.bind_tools( + model=model, + collection_id=collection_id, + max_num_results=max_num_results + ) async def invoke_chat( self, diff --git a/steps/vmh/xai_chat_completion_api_step.py b/steps/vmh/xai_chat_completion_api_step.py index 14d9315..36d1e69 100644 --- a/steps/vmh/xai_chat_completion_api_step.py +++ b/steps/vmh/xai_chat_completion_api_step.py @@ -34,7 +34,13 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse: "max_tokens": 2000, "stream": false, "extra_body": { - "collection_id": "col_abc123" // Optional: override auto-detection + "collection_id": "col_abc123", // Optional: override auto-detection + "enable_web_search": true, // Optional: enable web search (default: false) + "web_search_config": { // Optional: web search configuration + "allowed_domains": ["example.com"], + "excluded_domains": ["spam.com"], + "enable_image_understanding": true + } } } @@ -91,9 +97,16 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse: stream = body.get('stream', False) extra_body = body.get('extra_body', {}) + # Web Search parameters (default: disabled) + enable_web_search = extra_body.get('enable_web_search', False) + web_search_config = extra_body.get('web_search_config', {}) + ctx.logger.info(f"πŸ“‹ Model: {model_name}") ctx.logger.info(f"πŸ“‹ Messages: {len(messages)}") ctx.logger.info(f"πŸ“‹ Stream: {stream}") + ctx.logger.info(f"πŸ“‹ Web Search: {'enabled' if enable_web_search else 'disabled'}") + if enable_web_search and web_search_config: + ctx.logger.debug(f"Web Search Config: {json.dumps(web_search_config, indent=2)}") ctx.logger.debug(f"Messages: {json.dumps(messages, indent=2, ensure_ascii=False)}") # Validate messages @@ -141,15 +154,15 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse: break # Only check first user message - # Priority 3: Error if no collection_id (strict mode) - if not collection_id: - ctx.logger.error("❌ No collection_id found (neither extra_body nor Aktenzeichen)") - ctx.logger.error(" Provide collection_id in extra_body or start message with Aktenzeichen") + # Priority 3: Error if no collection_id AND web_search disabled + if not collection_id and not enable_web_search: + ctx.logger.error("❌ No collection_id found and web_search disabled") + ctx.logger.error(" Provide collection_id, enable web_search, or both") return ApiResponse( status=400, body={ - 'error': 'collection_id required', - 'message': 'Provide collection_id in extra_body or start message with Aktenzeichen (e.g., "1234/56 question")' + 'error': 'collection_id or web_search required', + 'message': 'Provide collection_id in extra_body, enable web_search, or start message with Aktenzeichen (e.g., "1234/56 question")' } ) @@ -170,10 +183,12 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse: max_tokens=max_tokens ) - # Bind file_search tool - model_with_tools = langchain_service.bind_file_search( + # Bind tools (file_search and/or web_search) + model_with_tools = langchain_service.bind_tools( model=model, collection_id=collection_id, + enable_web_search=enable_web_search, + web_search_config=web_search_config, max_num_results=10 )