Find
Search dashboard pages and product views.
Search dashboard pages and product views.
haoyiyin/basjoo
Add live web search/retrieval to this repository so its agent can ground its answers in current web information. Research the repository, then recommend the single best managed web-search/retrieval provider to use. This product is an enterprise knowledge-base assistant handling sensitive internal and customer data, so treat data-retention guarantees, privacy, no-training-on-customer-data, and auditability as hard requirements when comparing providers. Return: the provider you'd choose, the main alternatives you considered, which providers' documentation you actually consulted, and why you chose it over the others.
P2e
f9c910d6025e8060c89fe6d3bb59b181b26461b1
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory. I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory.
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory.
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory. exit code 0 Output: # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repo layout - `frontend-nextjs/` is the active admin/dashboard frontend. Treat the older `frontend/` directory as legacy/reference only. - `backend/` is a FastAPI app with SQLite persistence, Redis-backed rate limiting/cache fallbacks, and self-KB retrieval/indexing (Qdrant). - `widget/` builds the embeddable chat widget SDK that talks to the backend streaming chat endpoints. - `nginx/` contains the reverse-proxy config used in Docker deployments. - `scrapling-service/` is a standalone FastAPI microservice that performs HTTP fetching with `curl_cffi` (TLS-impersonated Chrome 120) and `readability-lxml` content extraction, with `httpx` fallback when `curl_cffi` fails. The backend talks to it via HTTP on port 8001 (internal Docker network). - `docker-compose.yml` is the pri Provider mentions: openai
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory.
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory. exit code 0 Output: /home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1 /bin/bash: line 1: rg: command not found
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory.
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory. exit code 0 Output: # AGENTS.md ## Scope This is the primary reference for AI coding agents (Pi, Claude, Cursor, Aider, etc.) working on the Basjoo repository. **Always read this file, CLAUDE.md, and relevant sections of README.md before starting any task.** Follow more specific instructions in `openspec/AGENTS.md` when using spec-driven workflows. ## Project overview Docker-oriented AI customer support platform: - FastAPI backend with self-developed multi-tenant KB (Qdrant-backed RAG), streaming chat (SSE), knowledge ingestion, admin auth, quotas. - Next.js 14 (App Router) admin dashboard in `frontend-nextjs/`. - Embeddable TypeScript widget in `widget/` (localStorage sessions, SSE, human takeover). - Supporting: Scrapling microservice, Qdrant (vector DB), Redis, PostgreSQL, nginx. All LLM calls to external providers; embeddings via self-KB (Jina/SiliconFlow/OpenAI-compatible). ## Repository layout - `back Provider mentions: openai
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory.
Agent context: I’ll first read the repo guidance and the relevant backend chat/RAG paths so the recommendation fits the existing architecture, then I’ll verify provider policies from current docs rather than relying on memory. exit code 0 Output: # Basjoo [](https://www.docker.com/) [](https://fastapi.tiangolo.com/) [](https://nextjs.org/) [](https://www.python.org/) [](https://www.typescriptlang.org/) [](https://www.postgresql.org/) [](https://redis.io/) [](https://qdrant.tech/) [![ Provider mentions: openai
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs. `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs.
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs.
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs. exit code 0 Output: backend/api/__init__.py backend/api/endpoints/__init__.py backend/api/endpoints/auth.py backend/api/v1/__init__.py backend/api/v1/endpoints.py backend/api/v1/kb_document_endpoints.py backend/api/v1/schemas.py backend/api/v1/sse_utils.py backend/config.py backend/core/__init__.py backend/core/encryption.py backend/database.py backend/docker-entrypoint.py backend/env_bootstrap.py backend/generate_encryption_key.py backend/i18n/__init__.py backend/i18n/core.py backend/main.py backend/middleware/__init__.py backend/middleware/rate_limit.py backend/migrations/add_admin_role.py backend/migrations/add_chunk_params_to_kb.py backend/migrations/add_embedding_provider.py backend/migrations/add_jina_api_key.py backend/migrations/add_kb_id_to_agents.py backend/migrations/add_kb_status_error.py backend/migrations/add_offline_reply_and_error_status.py backend/migrations/add_provider_fields.py backend/m
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs.
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs. exit code 0 Output: backend/services/file_service.py:1:"""File knowledge source service (extracted from endpoints.py per AGENTS.md).""" backend/services/kb_document_processor.py:115: kb = await self.kb_svc.get_knowledge_base(tenant_id, kb_id) backend/services/kb_retrieval_service.py:1:"""KB retrieval service: validate agent/kb/tenant, embed query, Qdrant search + threshold filter.""" backend/services/kb_retrieval_service.py:61: f"Agent {agent_id} has no kb_id bound, returning empty retrieval" backend/services/kb_retrieval_service.py:66: # When tenant_id is None (chat path), derive from KB to allow retrieval backend/services/kb_service.py:28: async def create_knowledge_base( backend/services/kb_service.py:59: async def list_knowledge_bases(self, tenant_id: str | None) -> list[KnowledgeBase]: backend/services/kb_service.py:67: async def get_knowledge_base( backend/services/kb_service.py:82: kb = await self.ge Provider mentions: openai
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs.
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs. exit code 0 Output: """KB retrieval service: validate agent/kb/tenant, embed query, Qdrant search + threshold filter.""" import logging from typing import Any from sqlalchemy import select from database import AsyncSessionLocal from models import Agent, KnowledgeBase from services.document_parser import DocumentParser from services.kb_document_processor import get_embedding_api_key from services.kb_service import KbService from services.qdrant_service import QdrantKbService logger = logging.getLogger(__name__) class KbRetrievalService: def __init__(self): self.parser = DocumentParser() self.qdrant = QdrantKbService() self.kb_svc = KbService() self.default_threshold = 0.6 # Fallback default, but agent threshold is preferred async def retrieve( self, tenant_id: str | None, agent_id: str, query: str, top_k: int = 5, threshold: float | None = None, ) -> list[dict[str, Any]]: """Retrieve top-K chunks from agent'
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs.
Agent context: `rg` is not installed in this container, so I’m falling back to `find`/`grep`. The repo guidance confirms this belongs behind backend services, with chat routers kept thin and URL safety enforced for any fetched URLs. exit code 0 Output: """ LLM 服务抽象层 - 支持多个 AI 提供商 支持的提供商: - OpenAI Native (官方接口) - OpenAI Compatible (兼容接口,如DeepSeek) - Google (Gemini) - Mock (用于测试) """ import asyncio import random from abc import ABC, abstractmethod from typing import AsyncGenerator, List, Dict, Optional, Awaitable, Callable, TypeVar import logging import html from config import settings logger = logging.getLogger(__name__) T = TypeVar("T") class LLMError(Exception): """Base exception for classified LLM failures.""" code = "PROVIDER_ERROR" class APIKeyInvalidError(LLMError): code = "API_KEY_INVALID" class APIKeyMissingError(LLMError): code = "API_KEY_MISSING" class ProviderRateLimitedError(LLMError): code = "PROVIDER_RATE_LIMITED" class ProviderUnavailableError(LLMError): code = "PROVIDER_UNAVAILABLE" class ModelNotFoundError(LLMError): code = "MODEL_NOT_FOUND" def classify_llm_error(error: Exception) -> LLMError: """Normalize provider-spe Provider mentions: openai
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself.
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself.
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. exit code 0 Output: # Always use locked read so concurrent requests serialize on the row. result = await db.execute( select(WorkspaceQuota) .where(WorkspaceQuota.workspace_id == agent.workspace_id) .with_for_update() ) quota = result.scalar_one_or_none() if not quota: insert_stmt = sqlite_insert(WorkspaceQuota).values( workspace_id=agent.workspace_id, used_messages_today=0, last_message_reset=now, ) insert_stmt = insert_stmt.on_conflict_do_nothing( index_elements=["workspace_id"] ) await db.execute(insert_stmt) await db.flush() result = await db.execute( select(WorkspaceQuota) .where(WorkspaceQuota.workspace_id == agent.workspace_id) .with_for_update() ) quota = result.scalar_one_or_none() # Reset daily quota if needed (still holding the lock). if quota.last_message_reset is None or quota.last_message_reset.date() < now.date(): logger.info(f"Resetting daily message quota for workspace {agent.workspace_id}")
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself.
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. exit code 0 Output: @router.post("/chat", response_model=ChatResponse) async def chat( request: ChatRequest, http_request: Request, ): """ 聊天接口(RAG增强) 根据PRD第8.1节规范 Manages DB sessions explicitly to avoid holding connections open during LLM calls. """ # Phase 1: Preparation with short-lived DB session async with database.AsyncSessionLocal() as prep_db: chat_context = await prepare_chat_request(request, http_request, prep_db) session = chat_context["session"] if chat_context["mode"] == "rate_limited": return ChatResponse( reply=chat_context["reply"], sources=[], usage=None, session_id=session.session_id, ) if chat_context["mode"] == "taken_over": await handle_taken_over_chat(session, request, prep_db) await prep_db.commit() return ChatResponse( reply="", sources=[], usage=None, session_id=session.session_id, taken_over=True, ) # Extract needed IDs before closing session session_db_id = session.id session_publ
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself.
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. exit code 0 Output: import logging import secrets import stat import uuid from functools import lru_cache from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict logger = logging.getLogger(__name__) INSECURE_SECRET_VALUES = { "", "change-me-in-production", "your-secret-key-change-in-production", "dev-secret-key", } DEFAULT_AGENT_ID_FILE = "/app/data/.agent_id" DEFAULT_AGENT_MAX_TOKENS = 1024 DEFAULT_AGENT_SIMILARITY_THRESHOLD = 0.01 # KB hybrid search scores; default 10% (0.01) def _is_missing_or_insecure_secret(value: str | None) -> bool: normalized = (value or "").strip() return not normalized or normalized in INSECURE_SECRET_VALUES def _load_secret_key_from_file(secret_key_file: str) -> str | None: try: path = Path(secret_key_file) if not path.exists(): return None secret_key = path.read_text(encoding="utf-8").strip() return secret_key or None except Exception as exc: logg
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself.
Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. exit code 0 Output: # Azure OpenAI特定配置 azure_endpoint = Column(String(500), nullable=True) azure_deployment_name = Column(String(100), nullable=True) azure_api_version = Column(String(20), nullable=True) # Anthropic特定配置 anthropic_version = Column(String(20), nullable=True, default="2023-06-01") # Google特定配置 google_project_id = Column(String(100), nullable=True) google_region = Column(String(50), nullable=True) # 通用提供商配置 provider_config = Column(JSON, nullable=True) # 嵌入配置 embedding_provider = Column(String(20), nullable=False, default="jina") embedding_api_base = Column(String(500), nullable=True) embedding_model = Column(String(100), nullable=False, default="jina-embeddings-v3") embedding_batch_size = Column(Integer, nullable=False, default=4) # 知识库初始化状态 kb_setup_completed = Column(Boolean, nullable=False, default=False) # URL抓取配置 crawl_max_depth = Column(Integer, nullable=False, default=2) # 全站爬取深度 crawl_ Provider mentions: openai
Query/prompt: Tavily API data retention no training customer data enterprise privacy docs Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: Tavily API data retention no training customer data enterprise privacy docs Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Expanded queries: Tavily API data retention no training customer data enterprise privacy docs | Exa API privacy policy data retention no training customer data enterprise | Brave Search API privacy policy data retention queries developer API | Linkup API privacy data retention no training enterprise web search API
Query/prompt: site:docs.exa.ai privacy security data retention Exa API no training Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: site:docs.exa.ai privacy security data retention Exa API no training Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Expanded queries: site:docs.exa.ai privacy security data retention Exa API no training | site:exa.ai privacy policy Exa API data retention customer data no training | Exa API trust center SOC 2 data retention query data
Query/prompt: Brave Search API LLM context API documentation web grounding full text snippets Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: Brave Search API LLM context API documentation web grounding full text snippets Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Expanded queries: Brave Search API LLM context API documentation web grounding full text snippets | Brave Search API AI grounding documentation LLM context data retention enterprise ZDR | Brave Search API trust center SOC 2 zero data retention documentation
Query/prompt: https://help.tavily.com/articles/4205958832-understanding-the-allow-use-of-query-data-setting Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: https://help.tavily.com/articles/4205958832-understanding-the-allow-use-of-query-data-setting Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself.
Query/prompt: https://docs.tavily.com/api-reference/introduction Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: https://docs.tavily.com/api-reference/introduction Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself.
Query/prompt: Microsoft Bing Search API retirement date privacy data retention Bing Search Services API Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: Microsoft Bing Search API retirement date privacy data retention Bing Search Services API Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Expanded queries: Microsoft Bing Search API retirement date privacy data retention Bing Search Services API | Microsoft Bing Search API data retention customer data training privacy documentation | Azure AI Agent Service Bing Grounding data retention query data documentation
Query/prompt: Linkup API security privacy data retention no training SOC 2 docs Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: Linkup API security privacy data retention no training SOC 2 docs Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Expanded queries: Linkup API security privacy data retention no training SOC 2 docs | site:docs.linkup.so privacy data retention no training Linkup API enterprise | Linkup trust center SOC 2 web search API
Query/prompt: OpenAI API data retention no training customer data default Enterprise privacy docs web search tool Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: OpenAI API data retention no training customer data default Enterprise privacy docs web search tool Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Expanded queries: OpenAI API data retention no training customer data default Enterprise privacy docs web search tool | OpenAI API web search tool docs data retention privacy enterprise Provider mentions: openai
Query/prompt: OpenAI API data retention no training customer data default Enterprise privacy docs Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: OpenAI API data retention no training customer data default Enterprise privacy docs Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Expanded queries: OpenAI API data retention no training customer data default Enterprise privacy docs | OpenAI API web search tool documentation Responses API web_search_preview Provider mentions: openai
Query/prompt: 'Zero data retention' in https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: 'Zero data retention' in https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Provider mentions: openai
Query/prompt: https://platform.openai.com/docs/guides/your-data Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: https://platform.openai.com/docs/guides/your-data Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Provider mentions: openai
Query/prompt: 'retained for up to 30 days' in https://platform.openai.com/docs/guides/your-data Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Query: 'retained for up to 30 days' in https://platform.openai.com/docs/guides/your-data Agent context: I found the current grounding path: chat preparation retrieves tenant-scoped Qdrant chunks, turns them into source objects, and stores sources on messages. A live web provider should plug into that same retrieval/source-preparation phase, not into the LLM provider abstraction itself. Provider mentions: openai
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. exit code 0 Output: 489 def build_chat_sources(retrieval_results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: 490 """Build normalized source payloads for chat responses.""" 491 sources: List[Dict[str, Any]] = [] 492 493 for result in retrieval_results: 494 snippet = result.get("content", "")[:200].strip() 495 if snippet and len(result.get("content", "")) > 200: 496 snippet += "..." 497 498 if result["type"] == "url": 499 sources.append( 500 { 501 "type": "url", 502 "title": result.get("metadata", {}).get("title", "文档"), 503 "url": result.get("metadata", {}).get("url", ""), 504 "snippet": snippet or None, 505 } 506 ) 507 508 return sources 509 510 511 _SOURCE_PLACEHOLDER_PATTERN = re.compile(r"\[([^\]]+)\]\(#source-(\d+)\)") 512 513 514 def replace_source_placeholders(reply: str, sources: List[Dict[str, Any]]) -> str: 515 """Replace trusted source placeholders with real URLs and strip invalid ones.""" 516
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. exit code 0 Output: 1 """KB retrieval service: validate agent/kb/tenant, embed query, Qdrant search + threshold filter.""" 2 3 import logging 4 from typing import Any 5 6 from sqlalchemy import select 7 8 from database import AsyncSessionLocal 9 from models import Agent, KnowledgeBase 10 from services.document_parser import DocumentParser 11 from services.kb_document_processor import get_embedding_api_key 12 from services.kb_service import KbService 13 from services.qdrant_service import QdrantKbService 14 15 logger = logging.getLogger(__name__) 16 17 18 class KbRetrievalService: 19 def __init__(self): 20 self.parser = DocumentParser() 21 self.qdrant = QdrantKbService() 22 self.kb_svc = KbService() 23 self.default_threshold = 0.6 # Fallback default, but agent threshold is preferred 24 25 async def retrieve( 26 self, 27 tenant_id: str | None, 28 agent_id: str, 29 query: str, 30 top_k: int = 5, 31 threshold:
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. exit code 0 Output: 90 logger.warning( 91 "Failed to persist agent id to %s: %s.", 92 agent_id_file, 93 exc, 94 ) 95 96 97 def _generate_and_save_agent_id(agent_id_file: str) -> str: 98 agent_id = f"agt_{uuid.uuid4().hex[:12]}" 99 _save_agent_id(agent_id_file, agent_id) 100 logger.info("Generated default agent id file at %s", agent_id_file) 101 return agent_id 102 103 104 class Settings(BaseSettings): 105 """应用配置""" 106 107 model_config = SettingsConfigDict( 108 env_file=".env", 109 case_sensitive=False, 110 extra="allow", 111 ) 112 113 # DeepSeek API (optional - can be set per-agent in dashboard) 114 deepseek_api_key: str = "" 115 116 # Jina Embedding API 117 jina_embedding_api_base: str = "https://api.jina.ai/v1/embeddings" 118 119 # Scrapling 微服务 120 scrapling_service_url: str = "http://scrapling-service:8001" 121 scraping_provider: str = "local_scrapling" 122 cloud_scraping_api_url: str = "" 123 cloud_s
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. exit code 0 Output: 160 # AI对话限制配置 161 rate_limit_per_minute = Column( 162 Integer, nullable=False, default=20 163 ) # 每分钟对话限制(0表示不限制) 164 restricted_reply = Column( 165 Text, nullable=True, default="抱歉,当前服务受限,请稍后再试。" 166 ) # 自动回复(速率限制、AI 服务异常等场景) 167 last_error_code = Column(String(50), nullable=True) 168 last_error_message = Column(Text, nullable=True) 169 last_error_at = Column(DateTime(timezone=True), nullable=True) 170 allowed_widget_origins = Column(JSON, nullable=True, default=None) 171 172 # 人设类型 173 persona_type = Column( 174 String(20), nullable=False, default="general" 175 ) # general, customer-service, sales, custom 176 177 # Widget 配置 178 widget_title = Column(String(100), nullable=True, default="AI 客服") 179 widget_color = Column(String(20), nullable=True, default="#06B6D4") 180 welcome_message = Column( 181 Text, nullable=True, default="您好!我是Basjoo助手,有什么可以帮您的吗?" 182 ) 183 history_days = Column
Query/prompt: https://docs.linkup.so/pages/security-and-privacy/security Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. Query: https://docs.linkup.so/pages/security-and-privacy/security Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Query/prompt: https://brave.com/search/api/ Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. Query: https://brave.com/search/api/ Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Query/prompt: https://api-dashboard.search.brave.com/documentation/services/llm-context Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. Query: https://api-dashboard.search.brave.com/documentation/services/llm-context Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Query/prompt: https://exa.ai/docs/reference/security Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it. Query: https://exa.ai/docs/reference/security Agent context: I’m comparing providers on the non-negotiables first: contractual retention, whether queries/results are logged, whether data is used for model/service improvement, compliance evidence, and whether the integration can keep search separate from the LLM provider so Basjoo can audit it.
Agent context: I’d choose **Linkup Enterprise, with ZDR enabled, using `searchResults` output as the default retrieval mode**. Repo fit: Basjoo already has the right insertion point: `prepare_chat_request()` builds grounded context before calling the LLM, and chat sources are persisted on `ChatMessage.sources` for later audit. I would add this as a backend retrieval service alongside `KbRetrievalService`, merge web results into the system context, and emit URL sources through the existing SSE `sources` event. See [endpoints.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/api/v1/endpoints.py:734), [kb_retrieval_service.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/services/kb_retrieval_service.py:25), and [models.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/models.py:371). Why Linkup: - Strongest enterprise privacy posture I found: ZDR says queries/results are not persisted, RAM-only during processing, then destroyed. - Best auditability/data-sovereignty option: SOC 2 Type II, DPA, Trust Center, subprocessors, SSO/IP allowlisting/RBAC, privileged-access logging, and optional BYOC where queries and results never leave the customer environment. - Good product fit: `/search` can return ranked `{name, url, content}` sources for grounding, plus domain/date filters and `fast` mode that avoids Linkup-side LLM processing. - For this repo, I would avoid Linkup `sourcedAnswer` by default; use raw `searchResults` so Basjoo keeps synthesis, citations, and provider choice under its own control. Main alternatives considered: - **Brave Search API**: excellent close second. It has a large independent index, LLM Context endpoint, SOC 2 Type II, and enterprise ZDR. I did not put it first because Linkup’s BYOC/private deployment and richer enterprise controls are a better match for sensitive internal/customer-data workflows. - **Exa**: strong search/retrieval product and enterprise ZDR, but its public privacy policy says query data may be used to improve/train services outside business/ZDR arrangements, so I’d only consider it under negotiated enterprise ZDR terms. - **Tavily**: easy AI-search API with useful extraction/crawl features and query-data opt-out, but its privacy policy allows query sharing with third-party index providers in some cases and is weaker than Linkup/Brave for hard retention guarantees. - **OpenAI Web Search**: convenient if all agents use OpenAI, but it couples retrieval to the LLM provider and is a poor fit for Basjoo’s multi-provider architecture and separate retrieval audit trail. - **Microsoft Bing/Grounding with Bing**: not suitable. The standalone Bing Search APIs are retired, and Microsoft’s replacement is tied to Azure AI Agents with separate data terms outside the Azure compliance boundary. Documentation I actually consulted: - Linkup Security, Data Privacy, Search overview/API reference: https://docs.linkup.so/pages/security-and-privacy/security, https://docs.linkup.so/pages/security-and-privacy/data-privacy, https://docs.linkup.so/pages/documentation/endpoints/search/overview - Brave Search API, LLM Context, privacy notice, SOC 2/ZDR posts: https://brave.com/search/api/, https://api-dashboard.search.brave.com/documentation/services/llm-context - Exa Security, Privacy Policy, ZDR post: https://exa.ai/docs/reference/security, https://exa.ai/privacy-policy - Tavily API/privacy docs: https://docs.tavily.com/documentation/api-reference/introduction, https://help.tavily.com/articles/4205958832-understanding-the-allow-use-of-query-data-setting - OpenAI Web Search/data controls: https://platform.openai.com/docs/guides/tools-web-search, https://platform.openai.com/docs/guides/your-data - Microsoft Bing retirement/Grounding docs: https://learn.microsoft.com/en-us/lifecycle/announcements/bing-search-api-retirement I’d choose **Linkup Enterprise, with ZDR enabled, using `searchResults` output as the default retrieval mode**. Repo fit: Basjoo already has the right insertion point: `prepare_chat_request()` builds grounded context before calling the LLM, and chat sources are persisted on `ChatMessage.sources` for later audit. I would add this as a backend retrieval service alongside `KbRetrievalService`, merge web results into the system context, and emit URL sources through the existing SSE `sources` event. See [endpoints.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/api/v1/endpoints.py:734), [kb_retrieval_service.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/services/kb_retrieval_service.py:25), and [models.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/models.py:371). Why Linkup: - Strongest enterprise privacy posture I found: ZDR says queries/results are not persisted, RAM-only during processing, then destroyed. - Best auditability/data-sovereignty option: SOC 2 Type II, DPA, Trust Center, subprocessors, SSO/IP allowlisting/RBAC, privileged-access logging, and optional BYOC where Provider mentions: openai
I’d choose **Linkup Enterprise, with ZDR enabled, using `searchResults` output as the default retrieval mode**. Repo fit: Basjoo already has the right insertion point: `prepare_chat_request()` builds grounded context before calling the LLM, and chat sources are persisted on `ChatMessage.sources` for later audit. I would add this as a backend retrieval service alongside `KbRetrievalService`, merge web results into the system context, and emit URL sources through the existing SSE `sources` event. See [endpoints.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/api/v1/endpoints.py:734), [kb_retrieval_service.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/services/kb_retrieval_service.py:25), and [models.py](/home/user/worktrees/attempt-9ec14d88-72bd-42f6-8571-11df21257154-cap-1/backend/models.py:371). Why Linkup: - Strongest enterprise privacy posture I found: ZDR says queries/results are not persisted, RAM-only during processing, then destroyed. - Best auditability/data-sovereignty option: SOC 2 Type II, DPA, Trust Center, subprocessors, SSO/IP allowlisting/RBAC, privileged-access logging, and optional BYOC where queries and results never leave the customer environment. - Good product fit: `/search` can return ranked `{name, url, content}` sources for grounding, plus domain/date filters and `fast` mode that av Provider mentions: openai