| import requests
|
| import json
|
| from typing import Dict, Optional
|
| from fastapi import Request
|
| from langchain_core.documents import Document
|
| from ..core.logger import SingletonLogger
|
| from ..utils.api_key_utils import get_api_key_for_service
|
|
|
| RERANK_BASE_URL = "https://api.langsearch.com/v1/rerank"
|
| SEARCH_BASE_URL = "https://api.langsearch.com/v1/web-search"
|
| logger = SingletonLogger().get_logger()
|
|
|
|
|
| class LangSearchClient:
|
| @staticmethod
|
| async def rerank_docs(
|
| query: str,
|
| documents: list[Document],
|
| top_n: int = 5,
|
| api_key: Optional[str] = None,
|
| request: Optional[Request] = None,
|
| ) -> list[Document]:
|
|
|
| if not documents:
|
| logger.warning("No documents to rerank")
|
| return [], []
|
|
|
| if not query or not query.strip():
|
| logger.warning("Empty query provided for reranking")
|
| return documents[:top_n], [1.0] * min(top_n, len(documents))
|
|
|
|
|
| doc_contents = []
|
| valid_doc_indices = []
|
| for idx, doc in enumerate(documents):
|
| content = (
|
| doc.page_content.strip()
|
| if hasattr(doc, "page_content")
|
| else str(doc).strip()
|
| )
|
| if content:
|
| doc_contents.append(content)
|
| valid_doc_indices.append(idx)
|
|
|
| if not doc_contents:
|
| logger.warning("All documents have empty content")
|
| return [], []
|
|
|
| if not api_key and request:
|
| api_key = get_api_key_for_service(request, "langsearch")
|
|
|
| payload = json.dumps(
|
| {
|
| "model": "langsearch-reranker-v1",
|
| "query": query,
|
| "top_n": min(top_n, len(doc_contents)),
|
| "return_documents": False,
|
| "documents": doc_contents,
|
| }
|
| )
|
| headers = {
|
| "Authorization": f"Bearer {api_key}",
|
| "Content-Type": "application/json",
|
| }
|
|
|
| try:
|
| response = requests.post(RERANK_BASE_URL, headers=headers, data=payload)
|
| response.raise_for_status()
|
| result = response.json()
|
|
|
|
|
| reranked_docs = []
|
| relevance_scores = []
|
| for doc in result["results"]:
|
| original_idx = valid_doc_indices[doc["index"]]
|
| reranked_docs.append(documents[original_idx])
|
| relevance_scores.append(doc["relevance_score"])
|
|
|
| return reranked_docs, relevance_scores
|
| except requests.RequestException as e:
|
| logger.error(f"Error reranking documents: {e}")
|
| if hasattr(e, "response") and e.response is not None:
|
| try:
|
| error_detail = e.response.json()
|
| logger.error(f"API error details: {error_detail}")
|
| except:
|
| logger.error(f"API response text: {e.response.text}")
|
| return documents[:top_n], [1.0] * min(top_n, len(documents))
|
|
|
| @staticmethod
|
| async def search(
|
| query: str, num: int = 10, freshness: str = "noLimit", api_key: Optional[str] = None, request: Optional[Request] = None
|
| ) -> list[Dict[str, str]]:
|
| """
|
| Perform a web search using LangSearch API.
|
|
|
| Args:
|
| query (str): The search query.
|
| num (int, optional): The number of results to return. Defaults to 10.
|
| freshness (str, optional): The freshness filter for search results. Defaults to "noLimit". Options include "oneDay", "oneWeek", "oneMonth", "oneYear", and "noLimit".
|
| api_key (str, optional): LangSearch API key. If not provided, will fetch from request.
|
| request (Request, optional): FastAPI Request object to fetch API key from.
|
|
|
| Returns:
|
| list[Dict[str, str]]: A list of dictionaries representing the search results.
|
| """
|
| if not api_key and request:
|
| api_key = get_api_key_for_service(request, "langsearch")
|
|
|
| payload = json.dumps(
|
| {
|
| "query": query,
|
| "freshness": freshness,
|
| "count": num,
|
| }
|
| )
|
| headers = {
|
| "Authorization": f'Bearer {api_key}',
|
| "Content-Type": "application/json",
|
| }
|
| try:
|
| response = requests.request(
|
| "POST", SEARCH_BASE_URL, headers=headers, data=payload
|
| )
|
| response.raise_for_status()
|
| result = response.json()
|
| web_pages = result.get("data", {}).get("webPages", {}).get("value", [])
|
| documents = [
|
| {
|
| "title": page.get("name", ""),
|
| "url": page.get("url", ""),
|
| "snippet": page.get("snippet", ""),
|
| }
|
| for page in web_pages
|
| ]
|
| return documents
|
| except requests.RequestException as e:
|
| logger.error(f"Error performing web search: {e}")
|
| return []
|
|
|