feat: add google provider for web search
Signed-off-by: joeyczheng <joeyczheng@tencent.com>
This commit is contained in:
@@ -101,24 +101,24 @@ WeKnora employs a modern modular design to build a complete document understandi
|
||||
|
||||
## 🧩 Feature Matrix
|
||||
|
||||
| Module | Support | Description |
|
||||
|---------|---------|------|
|
||||
| Agent Mode | ✅ ReACT Agent Mode | Support for using built-in tools to retrieve knowledge bases, MCP tools, and web search, with cross-knowledge base retrieval and multiple iterations |
|
||||
| Knowledge Base Types | ✅ FAQ / Document | Support for creating FAQ and document knowledge base types, with folder import, URL import, tag management, and online entry |
|
||||
| Document Formats | ✅ PDF / Word / Txt / Markdown / Images (with OCR / Caption) | Support for structured and unstructured documents with text extraction from images |
|
||||
| Model Management | ✅ Centralized configuration, built-in model sharing | Centralized model configuration with model selection in knowledge base settings, support for multi-tenant shared built-in models |
|
||||
| Embedding Models | ✅ Local models, BGE / GTE APIs, etc. | Customizable embedding models, compatible with local deployment and cloud vector generation APIs |
|
||||
| Vector DB Integration | ✅ PostgreSQL (pgvector), Elasticsearch | Support for mainstream vector index backends, flexible switching for different retrieval scenarios |
|
||||
| Retrieval Strategies | ✅ BM25 / Dense Retrieval / GraphRAG | Support for sparse/dense recall and knowledge graph-enhanced retrieval with customizable retrieve-rerank-generate pipelines |
|
||||
| LLM Integration | ✅ Support for Qwen, DeepSeek, etc., with thinking/non-thinking mode switching | Compatible with local models (e.g., via Ollama) or external API services with flexible inference configuration |
|
||||
| Module | Support | Description |
|
||||
|---------|--------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Agent Mode | ✅ ReACT Agent Mode | Support for using built-in tools to retrieve knowledge bases, MCP tools, and web search, with cross-knowledge base retrieval and multiple iterations |
|
||||
| Knowledge Base Types | ✅ FAQ / Document | Support for creating FAQ and document knowledge base types, with folder import, URL import, tag management, and online entry |
|
||||
| Document Formats | ✅ PDF / Word / Txt / Markdown / Images (with OCR / Caption) | Support for structured and unstructured documents with text extraction from images |
|
||||
| Model Management | ✅ Centralized configuration, built-in model sharing | Centralized model configuration with model selection in knowledge base settings, support for multi-tenant shared built-in models |
|
||||
| Embedding Models | ✅ Local models, BGE / GTE APIs, etc. | Customizable embedding models, compatible with local deployment and cloud vector generation APIs |
|
||||
| Vector DB Integration | ✅ PostgreSQL (pgvector), Elasticsearch | Support for mainstream vector index backends, flexible switching for different retrieval scenarios |
|
||||
| Retrieval Strategies | ✅ BM25 / Dense Retrieval / GraphRAG | Support for sparse/dense recall and knowledge graph-enhanced retrieval with customizable retrieve-rerank-generate pipelines |
|
||||
| LLM Integration | ✅ Support for Qwen, DeepSeek, etc., with thinking/non-thinking mode switching | Compatible with local models (e.g., via Ollama) or external API services with flexible inference configuration |
|
||||
| Conversation Strategy | ✅ Agent models, normal mode models, retrieval thresholds, Prompt configuration | Support for configuring Agent models, normal mode models, retrieval thresholds, online Prompt configuration, precise control over multi-turn conversation behavior |
|
||||
| Web Search | ✅ Extensible search engines, DuckDuckGo | Support for extensible web search engines with built-in DuckDuckGo search engine |
|
||||
| MCP Tools | ✅ uvx, npx launchers, Stdio/HTTP Streamable/SSE | Support for extending Agent capabilities through MCP, with built-in uvx and npx launchers, supporting three transport methods |
|
||||
| QA Capabilities | ✅ Context-aware, multi-turn dialogue, prompt templates | Support for complex semantic modeling, instruction control and chain-of-thought Q&A with configurable prompts and context windows |
|
||||
| E2E Testing | ✅ Retrieval+generation process visualization and metric evaluation | End-to-end testing tools for evaluating recall hit rates, answer coverage, BLEU/ROUGE and other metrics |
|
||||
| Deployment Modes | ✅ Support for local deployment / Docker images | Meets private, offline deployment and flexible operation requirements, with fast development mode support |
|
||||
| User Interfaces | ✅ Web UI + RESTful API | Interactive interface and standard API endpoints, with Agent mode/normal mode switching and tool call process display |
|
||||
| Task Management | ✅ MQ async tasks, automatic database migration | MQ-based async task state maintenance, support for automatic database schema and data migration during version upgrades |
|
||||
| Web Search | ✅ Extensible search engines, DuckDuckGo / Google | Support for extensible web search engines with built-in DuckDuckGo search engine |
|
||||
| MCP Tools | ✅ uvx, npx launchers, Stdio/HTTP Streamable/SSE | Support for extending Agent capabilities through MCP, with built-in uvx and npx launchers, supporting three transport methods |
|
||||
| QA Capabilities | ✅ Context-aware, multi-turn dialogue, prompt templates | Support for complex semantic modeling, instruction control and chain-of-thought Q&A with configurable prompts and context windows |
|
||||
| E2E Testing | ✅ Retrieval+generation process visualization and metric evaluation | End-to-end testing tools for evaluating recall hit rates, answer coverage, BLEU/ROUGE and other metrics |
|
||||
| Deployment Modes | ✅ Support for local deployment / Docker images | Meets private, offline deployment and flexible operation requirements, with fast development mode support |
|
||||
| User Interfaces | ✅ Web UI + RESTful API | Interactive interface and standard API endpoints, with Agent mode/normal mode switching and tool call process display |
|
||||
| Task Management | ✅ MQ async tasks, automatic database migration | MQ-based async task state maintenance, support for automatic database schema and data migration during version upgrades |
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
|
||||
+17
-17
@@ -101,24 +101,24 @@ WeKnora 采用现代化模块化设计,构建了一条完整的文档理解与
|
||||
|
||||
## 🧩 功能模块能力
|
||||
|
||||
| 功能模块 | 支持情况 | 说明 |
|
||||
|---------|---------|------|
|
||||
| Agent模式 | ✅ ReACT Agent模式 | 支持使用内置工具检索知识库、MCP工具和网络搜索,跨知识库检索,多次迭代和反思 |
|
||||
| 知识库类型 | ✅ FAQ / 文档 | 支持创建FAQ和文档两种类型知识库,支持文件夹导入、URL导入、标签管理和在线录入 |
|
||||
| 功能模块 | 支持情况 | 说明 |
|
||||
|---------|-----------------------------------------------------|------|
|
||||
| Agent模式 | ✅ ReACT Agent模式 | 支持使用内置工具检索知识库、MCP工具和网络搜索,跨知识库检索,多次迭代和反思 |
|
||||
| 知识库类型 | ✅ FAQ / 文档 | 支持创建FAQ和文档两种类型知识库,支持文件夹导入、URL导入、标签管理和在线录入 |
|
||||
| 文档格式支持 | ✅ PDF / Word / Txt / Markdown / 图片(含 OCR / Caption) | 支持多种结构化与非结构化文档内容解析,支持图文混排与图像文字提取 |
|
||||
| 模型管理 | ✅ 集中配置、内置模型共享 | 模型集中配置,知识库设置页增加模型选择,支持多租户共享内置模型 |
|
||||
| 嵌入模型支持 | ✅ 本地模型、BGE / GTE API 等 | 支持自定义 embedding 模型,兼容本地部署与云端向量生成接口 |
|
||||
| 向量数据库接入 | ✅ PostgreSQL(pgvector)、Elasticsearch | 支持主流向量索引后端,可灵活切换与扩展,适配不同检索场景 |
|
||||
| 检索机制 | ✅ BM25 / Dense Retrieve / GraphRAG | 支持稠密/稀疏召回、知识图谱增强检索等多种策略,可自由组合召回-重排-生成流程 |
|
||||
| 大模型集成 | ✅ 支持 Qwen、DeepSeek 等,思考/非思考模式切换 | 可接入本地大模型(如 Ollama 启动)或调用外部 API 服务,支持推理模式灵活配置 |
|
||||
| 对话策略 | ✅ Agent模型、普通模式模型、检索阈值、Prompt配置 | 支持配置Agent模型、普通模式所需的模型、检索阈值,在线配置Prompt,精确控制多轮对话行为 |
|
||||
| 网络搜索 | ✅ 可扩展搜索引擎、DuckDuckGo | 支持可扩展的网络搜索引擎,内置DuckDuckGo搜索引擎 |
|
||||
| MCP工具 | ✅ uvx、npx启动工具,Stdio/HTTP Streamable/SSE | 支持通过MCP扩展Agent能力,内置uvx、npx两种MCP启动工具,支持三种传输方式 |
|
||||
| 问答能力 | ✅ 上下文感知、多轮对话、提示词模板 | 支持复杂语义建模、指令控制与链式问答,可配置提示词与上下文窗口 |
|
||||
| 端到端测试支持 | ✅ 检索+生成过程可视化与指标评估 | 提供一体化链路测试工具,支持评估召回命中率、回答覆盖度、BLEU / ROUGE 等主流指标 |
|
||||
| 部署模式 | ✅ 支持本地部署 / Docker 镜像 | 满足私有化、离线部署与灵活运维的需求,支持快速开发模式 |
|
||||
| 用户界面 | ✅ Web UI + RESTful API | 提供交互式界面与标准 API 接口,支持Agent模式/普通模式切换,展示工具调用过程 |
|
||||
| 任务管理 | ✅ MQ异步任务、数据库自动迁移 | 引入MQ对异步任务进行状态维护,支持版本升级时的数据库表结构和数据自动迁移 |
|
||||
| 模型管理 | ✅ 集中配置、内置模型共享 | 模型集中配置,知识库设置页增加模型选择,支持多租户共享内置模型 |
|
||||
| 嵌入模型支持 | ✅ 本地模型、BGE / GTE API 等 | 支持自定义 embedding 模型,兼容本地部署与云端向量生成接口 |
|
||||
| 向量数据库接入 | ✅ PostgreSQL(pgvector)、Elasticsearch | 支持主流向量索引后端,可灵活切换与扩展,适配不同检索场景 |
|
||||
| 检索机制 | ✅ BM25 / Dense Retrieve / GraphRAG | 支持稠密/稀疏召回、知识图谱增强检索等多种策略,可自由组合召回-重排-生成流程 |
|
||||
| 大模型集成 | ✅ 支持 Qwen、DeepSeek 等,思考/非思考模式切换 | 可接入本地大模型(如 Ollama 启动)或调用外部 API 服务,支持推理模式灵活配置 |
|
||||
| 对话策略 | ✅ Agent模型、普通模式模型、检索阈值、Prompt配置 | 支持配置Agent模型、普通模式所需的模型、检索阈值,在线配置Prompt,精确控制多轮对话行为 |
|
||||
| 网络搜索 | ✅ 可扩展搜索引擎、DuckDuckGo / Google | 支持可扩展的网络搜索引擎,内置DuckDuckGo搜索引擎 |
|
||||
| MCP工具 | ✅ uvx、npx启动工具,Stdio/HTTP Streamable/SSE | 支持通过MCP扩展Agent能力,内置uvx、npx两种MCP启动工具,支持三种传输方式 |
|
||||
| 问答能力 | ✅ 上下文感知、多轮对话、提示词模板 | 支持复杂语义建模、指令控制与链式问答,可配置提示词与上下文窗口 |
|
||||
| 端到端测试支持 | ✅ 检索+生成过程可视化与指标评估 | 提供一体化链路测试工具,支持评估召回命中率、回答覆盖度、BLEU / ROUGE 等主流指标 |
|
||||
| 部署模式 | ✅ 支持本地部署 / Docker 镜像 | 满足私有化、离线部署与灵活运维的需求,支持快速开发模式 |
|
||||
| 用户界面 | ✅ Web UI + RESTful API | 提供交互式界面与标准 API 接口,支持Agent模式/普通模式切换,展示工具调用过程 |
|
||||
| 任务管理 | ✅ MQ异步任务、数据库自动迁移 | 引入MQ对异步任务进行状态维护,支持版本升级时的数据库表结构和数据自动迁移 |
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
|
||||
+17
-17
@@ -101,24 +101,24 @@ WeKnoraは現代的なモジュラー設計を採用し、完全な文書理解
|
||||
|
||||
## 🧩 機能モジュール能力
|
||||
|
||||
| 機能モジュール | サポート状況 | 説明 |
|
||||
|---------|---------|------|
|
||||
| Agentモード | ✅ ReACT Agentモード | 組み込みツールでナレッジベースを検索、MCPツールとWeb検索を使用、クロスナレッジベース検索、複数回の反復とリフレクションをサポート |
|
||||
| ナレッジベースタイプ | ✅ FAQ / ドキュメント | FAQとドキュメントの2種類のナレッジベースの作成をサポート、フォルダーインポート、URLインポート、タグ管理、オンライン入力機能 |
|
||||
| 機能モジュール | サポート状況 | 説明 |
|
||||
|---------|-----------------------------------------------------|------|
|
||||
| Agentモード | ✅ ReACT Agentモード | 組み込みツールでナレッジベースを検索、MCPツールとWeb検索を使用、クロスナレッジベース検索、複数回の反復とリフレクションをサポート |
|
||||
| ナレッジベースタイプ | ✅ FAQ / ドキュメント | FAQとドキュメントの2種類のナレッジベースの作成をサポート、フォルダーインポート、URLインポート、タグ管理、オンライン入力機能 |
|
||||
| 文書フォーマットサポート | ✅ PDF / Word / Txt / Markdown / 画像(OCR / Caption含む) | 様々な構造化・非構造化文書コンテンツの解析をサポート、図文混在と画像文字抽出をサポート |
|
||||
| モデル管理 | ✅ 集中設定、組み込みモデル共有 | モデルの集中設定、ナレッジベース設定ページにモデル選択を追加、マルチテナント間での組み込みモデル共有をサポート |
|
||||
| 埋め込みモデルサポート | ✅ ローカルモデル、BGE / GTE API等 | カスタムembeddingモデルをサポート、ローカルデプロイとクラウドベクトル生成インターフェースに対応 |
|
||||
| ベクトルデータベース接続 | ✅ PostgreSQL(pgvector)、Elasticsearch | 主流のベクトルインデックスバックエンドをサポート、柔軟な切り替えと拡張が可能、異なる検索シナリオに適応 |
|
||||
| 検索メカニズム | ✅ BM25 / Dense Retrieve / GraphRAG | 密・疎検索、ナレッジグラフ強化検索など複数の戦略をサポート、検索-再ランキング-生成プロセスを自由に組み合わせ可能 |
|
||||
| 大規模モデル統合 | ✅ Qwen、DeepSeek等をサポート、思考/非思考モード切り替え | ローカル大規模モデル(Ollama起動など)に接続可能、または外部APIサービスを呼び出し、推論モードの柔軟な設定をサポート |
|
||||
| 対話戦略 | ✅ Agentモデル、通常モードモデル、検索閾値、Prompt設定 | Agentモデル、通常モードに必要なモデル、検索閾値の設定をサポート、オンラインPrompt設定、マルチターン対話の動作を精密に制御 |
|
||||
| Web検索 | ✅ 拡張可能な検索エンジン、DuckDuckGo | 拡張可能なWeb検索エンジンをサポート、DuckDuckGo検索エンジンを組み込み |
|
||||
| MCPツール | ✅ uvx、npx起動ツール、Stdio/HTTP Streamable/SSE | MCPを通じてAgent機能を拡張、uvx、npxの2種類のMCP起動ツールを組み込み、3種類の転送方式をサポート |
|
||||
| Q&A能力 | ✅ コンテキスト認識、マルチターン対話、プロンプトテンプレート | 複雑な意味モデリング、指示制御、チェーンQ&Aをサポート、プロンプトとコンテキストウィンドウを設定可能 |
|
||||
| エンドツーエンドテストサポート | ✅ 検索+生成プロセスの可視化と指標評価 | 一体化されたリンクテストツールを提供、リコール的中率、回答カバレッジ、BLEU / ROUGE等の主流指標の評価をサポート |
|
||||
| デプロイメントモード | ✅ ローカルデプロイメント / Dockerイメージ | プライベート化、オフラインデプロイメント、柔軟な運用保守のニーズに対応、高速開発モードをサポート |
|
||||
| ユーザーインターフェース | ✅ Web UI + RESTful API | インタラクティブインターフェースと標準APIインターフェースを提供、Agentモード/通常モードの切り替え、ツール呼び出しプロセスの表示をサポート |
|
||||
| タスク管理 | ✅ MQ非同期タスク、データベース自動マイグレーション | MQによる非同期タスクの状態維持を導入、バージョンアップ時のデータベーステーブル構造とデータの自動マイグレーションをサポート |
|
||||
| モデル管理 | ✅ 集中設定、組み込みモデル共有 | モデルの集中設定、ナレッジベース設定ページにモデル選択を追加、マルチテナント間での組み込みモデル共有をサポート |
|
||||
| 埋め込みモデルサポート | ✅ ローカルモデル、BGE / GTE API等 | カスタムembeddingモデルをサポート、ローカルデプロイとクラウドベクトル生成インターフェースに対応 |
|
||||
| ベクトルデータベース接続 | ✅ PostgreSQL(pgvector)、Elasticsearch | 主流のベクトルインデックスバックエンドをサポート、柔軟な切り替えと拡張が可能、異なる検索シナリオに適応 |
|
||||
| 検索メカニズム | ✅ BM25 / Dense Retrieve / GraphRAG | 密・疎検索、ナレッジグラフ強化検索など複数の戦略をサポート、検索-再ランキング-生成プロセスを自由に組み合わせ可能 |
|
||||
| 大規模モデル統合 | ✅ Qwen、DeepSeek等をサポート、思考/非思考モード切り替え | ローカル大規模モデル(Ollama起動など)に接続可能、または外部APIサービスを呼び出し、推論モードの柔軟な設定をサポート |
|
||||
| 対話戦略 | ✅ Agentモデル、通常モードモデル、検索閾値、Prompt設定 | Agentモデル、通常モードに必要なモデル、検索閾値の設定をサポート、オンラインPrompt設定、マルチターン対話の動作を精密に制御 |
|
||||
| Web検索 | ✅ 拡張可能な検索エンジン、DuckDuckGo / Google | 拡張可能なWeb検索エンジンをサポート、DuckDuckGo検索エンジンを組み込み |
|
||||
| MCPツール | ✅ uvx、npx起動ツール、Stdio/HTTP Streamable/SSE | MCPを通じてAgent機能を拡張、uvx、npxの2種類のMCP起動ツールを組み込み、3種類の転送方式をサポート |
|
||||
| Q&A能力 | ✅ コンテキスト認識、マルチターン対話、プロンプトテンプレート | 複雑な意味モデリング、指示制御、チェーンQ&Aをサポート、プロンプトとコンテキストウィンドウを設定可能 |
|
||||
| エンドツーエンドテストサポート | ✅ 検索+生成プロセスの可視化と指標評価 | 一体化されたリンクテストツールを提供、リコール的中率、回答カバレッジ、BLEU / ROUGE等の主流指標の評価をサポート |
|
||||
| デプロイメントモード | ✅ ローカルデプロイメント / Dockerイメージ | プライベート化、オフラインデプロイメント、柔軟な運用保守のニーズに対応、高速開発モードをサポート |
|
||||
| ユーザーインターフェース | ✅ Web UI + RESTful API | インタラクティブインターフェースと標準APIインターフェースを提供、Agentモード/通常モードの切り替え、ツール呼び出しプロセスの表示をサポート |
|
||||
| タスク管理 | ✅ MQ非同期タスク、データベース自動マイグレーション | MQによる非同期タスクの状態維持を導入、バージョンアップ時のデータベーステーブル構造とデータの自動マイグレーションをサポート |
|
||||
|
||||
## 🚀 クイックスタート
|
||||
|
||||
|
||||
@@ -41,19 +41,23 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0
|
||||
go.opentelemetry.io/otel/sdk v1.37.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
go.uber.org/dig v1.18.1
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/sync v0.19.0
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
google.golang.org/api v0.259.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.18.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
@@ -82,6 +86,7 @@ require (
|
||||
github.com/duckdb/duckdb-go/mapping v0.0.27 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
@@ -103,6 +108,9 @@ require (
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/google/flatbuffers v25.9.23+incompatible // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
@@ -149,6 +157,7 @@ require (
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
@@ -157,13 +166,14 @@ require (
|
||||
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20251208220230-2638a1023523 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
|
||||
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ=
|
||||
entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
@@ -176,9 +182,15 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
|
||||
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -402,10 +414,10 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgT
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
||||
@@ -450,6 +462,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -516,16 +530,20 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ=
|
||||
google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -304,6 +304,8 @@ func NewWebSearchService(cfg *config.Config) (interfaces.WebSearchService, error
|
||||
switch providerConfig.ID {
|
||||
case "duckduckgo":
|
||||
provider, err = web_search.NewDuckDuckGoProvider(providerConfig)
|
||||
case "google":
|
||||
provider, err = web_search.NewGoogleProvider(providerConfig)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown web search provider: %s", providerConfig.ID)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package web_search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"google.golang.org/api/customsearch/v1"
|
||||
"google.golang.org/api/option"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/config"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
)
|
||||
|
||||
// GoogleProvider implements web search using Google Custom Search Engine API
|
||||
type GoogleProvider struct {
|
||||
srv *customsearch.Service
|
||||
apiKey string
|
||||
engineID string
|
||||
baseURL string
|
||||
}
|
||||
|
||||
// NewGoogleProvider creates a new Google provider
|
||||
func NewGoogleProvider(cfg config.WebSearchProviderConfig) (interfaces.WebSearchProvider, error) {
|
||||
u, err := url.Parse(cfg.APIURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
engineID := u.Query().Get("engine_id")
|
||||
if engineID == "" {
|
||||
return nil, fmt.Errorf("engine_id is empty")
|
||||
}
|
||||
apiKey := u.Query().Get("api_key")
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("api_key is empty")
|
||||
}
|
||||
clientOpts := make([]option.ClientOption, 0)
|
||||
clientOpts = append(clientOpts, option.WithAPIKey(apiKey))
|
||||
clientOpts = append(clientOpts, option.WithEndpoint(u.Scheme+"://"+u.Host))
|
||||
srv, err := customsearch.NewService(context.Background(), clientOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GoogleProvider{
|
||||
srv: srv,
|
||||
apiKey: apiKey,
|
||||
engineID: engineID,
|
||||
baseURL: cfg.APIURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name returns the provider name
|
||||
func (p *GoogleProvider) Name() string {
|
||||
return "google"
|
||||
}
|
||||
|
||||
// Search performs a web search using Google Custom Search Engine API
|
||||
func (p *GoogleProvider) Search(
|
||||
ctx context.Context,
|
||||
query string,
|
||||
maxResults int,
|
||||
includeDate bool,
|
||||
) ([]*types.WebSearchResult, error) {
|
||||
if len(query) == 0 {
|
||||
return nil, fmt.Errorf("query is empty")
|
||||
}
|
||||
cseCall := p.srv.Cse.List().Context(ctx).Cx(p.engineID).Q(query)
|
||||
|
||||
if maxResults > 0 {
|
||||
cseCall = cseCall.Num(int64(maxResults))
|
||||
} else {
|
||||
cseCall = cseCall.Num(5)
|
||||
}
|
||||
cseCall = cseCall.Hl("ch-zh")
|
||||
|
||||
resp, err := cseCall.Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]*types.WebSearchResult, 0)
|
||||
for _, item := range resp.Items {
|
||||
result := &types.WebSearchResult{
|
||||
Title: item.Title,
|
||||
URL: item.Link,
|
||||
Snippet: item.Snippet,
|
||||
Source: "google",
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package web_search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/config"
|
||||
)
|
||||
|
||||
func TestNewGoogleProvider(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
cfg config.WebSearchProviderConfig
|
||||
expected error
|
||||
}{
|
||||
{
|
||||
name: "valid config",
|
||||
cfg: config.WebSearchProviderConfig{
|
||||
APIURL: "https://customsearch.googleapis.com/customsearch/v1?api_key=test&engine_id=test",
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "missing engine id",
|
||||
cfg: config.WebSearchProviderConfig{
|
||||
APIURL: "https://customsearch.googleapis.com/customsearch/v1?api_key=test",
|
||||
},
|
||||
expected: fmt.Errorf("engine_id is empty"),
|
||||
},
|
||||
{
|
||||
name: "missing api key",
|
||||
cfg: config.WebSearchProviderConfig{
|
||||
APIURL: "https://customsearch.googleapis.com/customsearch/v1?engine_id=test",
|
||||
},
|
||||
expected: fmt.Errorf("api_key is empty"),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := NewGoogleProvider(tc.cfg)
|
||||
if tc.expected == nil {
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error %v, got nil", tc.expected)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.expected.Error()) {
|
||||
t.Fatalf("expected error %v, got %v", tc.expected, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleProvider_Name(t *testing.T) {
|
||||
cfg := config.WebSearchProviderConfig{
|
||||
APIURL: "https://customsearch.googleapis.com/customsearch/v1?api_key=test&engine_id=test",
|
||||
}
|
||||
p, err := NewGoogleProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create Google provider: %v", err)
|
||||
}
|
||||
if p.Name() != "google" {
|
||||
t.Fatalf("expected provider name google, got %s", p.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleProvider_Search(t *testing.T) {
|
||||
mockResponse := map[string]interface{}{
|
||||
"items": []map[string]interface{}{
|
||||
{
|
||||
"title": "Example Search Result One",
|
||||
"link": "https://example.com/page1",
|
||||
"snippet": "This is the first search result snippet describing the content.",
|
||||
},
|
||||
{
|
||||
"title": "Example Search Result Two",
|
||||
"link": "https://example.org/page2",
|
||||
"snippet": "This is the second search result snippet with more details.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/customsearch/v1" {
|
||||
t.Fatalf("unexpected request path: %s", r.URL.Path)
|
||||
}
|
||||
|
||||
query := r.URL.Query().Get("q")
|
||||
if query != "weknora" {
|
||||
t.Fatalf("unexpected query: %s", query)
|
||||
}
|
||||
|
||||
cx := r.URL.Query().Get("cx")
|
||||
if cx != "test-engine-id" {
|
||||
t.Fatalf("unexpected engine ID: %s", cx)
|
||||
}
|
||||
|
||||
num := r.URL.Query().Get("num")
|
||||
if num != "5" {
|
||||
t.Fatalf("unexpected num parameter: %s", num)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
enc := json.NewEncoder(w)
|
||||
_ = enc.Encode(mockResponse)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfg := config.WebSearchProviderConfig{
|
||||
APIURL: fmt.Sprintf("%s/customsearch/v1?api_key=test-key&engine_id=test-engine-id", ts.URL),
|
||||
}
|
||||
prov, err := NewGoogleProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create Google provider: %v", err)
|
||||
}
|
||||
|
||||
gp := prov.(*GoogleProvider)
|
||||
if gp == nil {
|
||||
t.Fatalf("failed to cast to GoogleProvider")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := prov.Search(ctx, "weknora", 5, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(results) != 2 {
|
||||
t.Fatalf("expected 2 results, got %d", len(results))
|
||||
}
|
||||
|
||||
if results[0].Title != "Example Search Result One" ||
|
||||
results[0].URL != "https://example.com/page1" ||
|
||||
results[0].Snippet != "This is the first search result snippet describing the content." ||
|
||||
results[0].Source != "google" {
|
||||
t.Fatalf("unexpected first result: %+v", results[0])
|
||||
}
|
||||
|
||||
if results[1].Title != "Example Search Result Two" ||
|
||||
results[1].URL != "https://example.org/page2" ||
|
||||
results[1].Snippet != "This is the second search result snippet with more details." ||
|
||||
results[1].Source != "google" {
|
||||
t.Fatalf("unexpected second result: %+v", results[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleProvider_Search_EmptyQuery(t *testing.T) {
|
||||
cfg := config.WebSearchProviderConfig{
|
||||
APIURL: "https://customsearch.googleapis.com/customsearch/v1?api_key=test&engine_id=test",
|
||||
}
|
||||
prov, err := NewGoogleProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create Google provider: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := prov.Search(ctx, "", 5, false)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty query, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "query is empty") {
|
||||
t.Fatalf("expected 'query is empty' error, got: %v", err)
|
||||
}
|
||||
if results != nil {
|
||||
t.Fatalf("expected nil results for empty query, got: %v", results)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleProvider_Search_NoResults(t *testing.T) {
|
||||
mockResponse := map[string]interface{}{
|
||||
"items": []map[string]interface{}{},
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
enc := json.NewEncoder(w)
|
||||
_ = enc.Encode(mockResponse)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfg := config.WebSearchProviderConfig{
|
||||
APIURL: fmt.Sprintf("%s/customsearch/v1?api_key=test-key&engine_id=test-engine-id", ts.URL),
|
||||
}
|
||||
prov, err := NewGoogleProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create Google provider: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := prov.Search(ctx, "nonexistent", 5, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(results) != 0 {
|
||||
t.Fatalf("expected 0 results, got %d", len(results))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleProvider_Search_ErrorResponse(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("Internal Server Error"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfg := config.WebSearchProviderConfig{
|
||||
APIURL: fmt.Sprintf("%s/customsearch/v1?api_key=test-key&engine_id=test-engine-id", ts.URL),
|
||||
}
|
||||
prov, err := NewGoogleProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create Google provider: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := prov.Search(ctx, "test", 5, false)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for server error response, got nil")
|
||||
}
|
||||
if results != nil {
|
||||
t.Fatalf("expected nil results for error response, got: %v", results)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleProvider_Search_MaxResults(t *testing.T) {
|
||||
mockResponse := map[string]interface{}{
|
||||
"items": []map[string]interface{}{
|
||||
{"title": "Result 1", "link": "https://example.com/1", "snippet": "Snippet 1"},
|
||||
{"title": "Result 2", "link": "https://example.com/2", "snippet": "Snippet 2"},
|
||||
{"title": "Result 3", "link": "https://example.com/3", "snippet": "Snippet 3"},
|
||||
},
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
num := r.URL.Query().Get("num")
|
||||
if num != "2" {
|
||||
t.Fatalf("expected num=2, got %s", num)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
enc := json.NewEncoder(w)
|
||||
_ = enc.Encode(mockResponse)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfg := config.WebSearchProviderConfig{
|
||||
APIURL: fmt.Sprintf("%s/customsearch/v1?api_key=test-key&engine_id=test-engine-id", ts.URL),
|
||||
}
|
||||
prov, err := NewGoogleProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create Google provider: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
results, err := prov.Search(ctx, "test", 2, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(results) != 3 {
|
||||
t.Fatalf("expected 3 results, got %d", len(results))
|
||||
}
|
||||
|
||||
if results[0].Title != "Result 1" || results[1].Title != "Result 2" || results[2].Title != "Result 3" {
|
||||
t.Fatalf("unexpected results order or content")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user