@@ -2440,6 +2440,12 @@ async def _web_search(arguments: dict, agent_id: uuid.UUID | None = None) -> str
24402440 return await _search_google (query , api_key , max_results , language )
24412441 elif engine == "bing" and api_key :
24422442 return await _search_bing (query , api_key , max_results , language )
2443+ elif engine == "tencentcloud" :
2444+ secret_id = config .get ("tencent_secret_id" , "" )
2445+ secret_key = config .get ("tencent_secret_key" , "" )
2446+ if secret_id and secret_key :
2447+ return await _search_tencentcloud (query , secret_id , secret_key , max_results )
2448+ return "❌ Tencent Cloud WSA requires SecretId and SecretKey configuration"
24432449 elif engine == "exa" and api_key :
24442450 return await _search_exa (query , api_key , max_results )
24452451 else :
@@ -2664,6 +2670,149 @@ async def _search_bing(query: str, api_key: str, max_results: int, language: str
26642670 return f'🔍 Bing search for "{ query } " ({ len (results )} items):\n \n ' + "\n \n ---\n \n " .join (results )
26652671
26662672
2673+ async def _search_tencentcloud (query : str , secret_id : str , secret_key : str , max_results : int ) -> str :
2674+ """Search via Tencent Cloud WSA (Web Search API) - SearchPro."""
2675+ import httpx
2676+ import hashlib
2677+ import hmac
2678+ import time
2679+ import json
2680+ from datetime import datetime
2681+
2682+ # WSA API configuration
2683+ service = "wsa"
2684+ host = "wsa.tencentcloudapi.com"
2685+ action = "SearchPro"
2686+ version = "2025-05-08"
2687+ algorithm = "TC3-HMAC-SHA256"
2688+
2689+ timestamp = int (time .time ())
2690+ date = datetime .utcfromtimestamp (timestamp ).strftime ("%Y-%m-%d" )
2691+
2692+ # Request body - Cnt supports 10/20/30/40/50
2693+ cnt = min (max (max_results , 10 ), 50 )
2694+ # Round up to nearest valid value
2695+ valid_cnts = [10 , 20 , 30 , 40 , 50 ]
2696+ cnt = min ([c for c in valid_cnts if c >= cnt ], default = 50 )
2697+
2698+ payload = {
2699+ "Query" : query ,
2700+ "Mode" : 0 , # 0 = natural search results
2701+ "Cnt" : cnt ,
2702+ }
2703+ payload_str = json .dumps (payload , separators = ("," , ":" ), ensure_ascii = False )
2704+
2705+ # Build canonical request
2706+ ct = "application/json"
2707+ canonical_headers = f"content-type:{ ct } \n host:{ host } \n "
2708+ signed_headers = "content-type;host"
2709+ hashed_payload = hashlib .sha256 (payload_str .encode ("utf-8" )).hexdigest ()
2710+
2711+ canonical_request = "\n " .join ([
2712+ "POST" ,
2713+ "/" ,
2714+ "" ,
2715+ canonical_headers ,
2716+ signed_headers ,
2717+ hashed_payload
2718+ ])
2719+
2720+ # Build string to sign
2721+ credential_scope = f"{ date } /{ service } /tc3_request"
2722+ hashed_request = hashlib .sha256 (canonical_request .encode ("utf-8" )).hexdigest ()
2723+ string_to_sign = "\n " .join ([
2724+ algorithm ,
2725+ str (timestamp ),
2726+ credential_scope ,
2727+ hashed_request
2728+ ])
2729+
2730+ # Calculate signature
2731+ secret_date = hmac .new (
2732+ f"TC3{ secret_key } " .encode ("utf-8" ),
2733+ date .encode ("utf-8" ),
2734+ hashlib .sha256
2735+ ).digest ()
2736+
2737+ secret_service = hmac .new (
2738+ secret_date ,
2739+ service .encode ("utf-8" ),
2740+ hashlib .sha256
2741+ ).digest ()
2742+
2743+ secret_signing = hmac .new (
2744+ secret_service ,
2745+ b"tc3_request" ,
2746+ hashlib .sha256
2747+ ).digest ()
2748+
2749+ signature = hmac .new (
2750+ secret_signing ,
2751+ string_to_sign .encode ("utf-8" ),
2752+ hashlib .sha256
2753+ ).hexdigest ()
2754+
2755+ # Build authorization header
2756+ authorization = (
2757+ f"{ algorithm } Credential={ secret_id } /{ credential_scope } , "
2758+ f"SignedHeaders={ signed_headers } , Signature={ signature } "
2759+ )
2760+
2761+ headers = {
2762+ "Authorization" : authorization ,
2763+ "Content-Type" : ct ,
2764+ "Host" : host ,
2765+ "X-TC-Action" : action ,
2766+ "X-TC-Timestamp" : str (timestamp ),
2767+ "X-TC-Version" : version ,
2768+ }
2769+
2770+ try :
2771+ async with httpx .AsyncClient () as client :
2772+ resp = await client .post (
2773+ f"https://{ host } " ,
2774+ content = payload_str .encode ("utf-8" ),
2775+ headers = headers ,
2776+ timeout = 30 ,
2777+ )
2778+ data = resp .json ()
2779+
2780+ # Parse response
2781+ response = data .get ("Response" , {})
2782+ if "Error" in response :
2783+ error = response ["Error" ]
2784+ return f"❌ Tencent Cloud WSA error: { error .get ('Code' )} - { error .get ('Message' )} "
2785+
2786+ results = []
2787+ pages = response .get ("Pages" , [])
2788+
2789+ for i , page_str in enumerate (pages [:max_results ], 1 ):
2790+ try :
2791+ page = json .loads (page_str )
2792+ title = page .get ("title" , "Untitled" )
2793+ url = page .get ("url" , "" )
2794+ site = page .get ("site" , "" )
2795+ passage = page .get ("passage" , "" )
2796+
2797+ result_text = f"**{ i } . { title } **"
2798+ if site :
2799+ result_text += f" ({ site } )"
2800+ result_text += f"\n { url } "
2801+ if passage :
2802+ result_text += f"\n { passage } "
2803+ results .append (result_text )
2804+ except json .JSONDecodeError :
2805+ continue
2806+
2807+ if not results :
2808+ return f'🔍 Tencent Cloud WSA found no results for "{ query } "'
2809+
2810+ return f'🔍 Tencent Cloud WSA results for "{ query } " ({ len (results )} items):\n \n ' + "\n \n ---\n \n " .join (results )
2811+
2812+ except Exception as e :
2813+ return f"❌ Tencent Cloud WSA error: { str (e )[:300 ]} "
2814+
2815+
26672816async def _search_exa (query : str , api_key : str , max_results : int ) -> str :
26682817 """Search via Exa AI API (exa.ai). Used by the web_search engine selector."""
26692818 import httpx
0 commit comments