cherry-studio/src/renderer/src/providers/WebSearchProvider/BochaProvider.ts
one d463d6ea2e
feat(WebSearch): support RAG for external websearch, improve feedback (#7446)
* feat(WebSearch, RAG): support RAG for external websearch

* refactor(WebSearch): handle content limit in service

* refactor: update migrate

* refactor: UI, constants, types

* refactor: migrate contentLimit to cutoffLimit

* refactor: update default rag document count

* refactor: add a helper function for merging references

* refactor: reference filtering

* feat: feedback for websearch phases

* feat: support cutoff by token

* refactor: add a warning and fix the bound of cutoff limit

* fix: not pass `dimensions` if it is not set by the user

* refactor: update i18n and error message

* refactor: improve UI

* fix: cutoff unit style
2025-06-27 18:04:42 +08:00

70 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { WebSearchState } from '@renderer/store/websearch'
import { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types'
import { BochaSearchParams, BochaSearchResponse } from '@renderer/utils/bocha'
import BaseWebSearchProvider from './BaseWebSearchProvider'
export default class BochaProvider extends BaseWebSearchProvider {
constructor(provider: WebSearchProvider) {
super(provider)
if (!this.apiKey) {
throw new Error('API key is required for Bocha provider')
}
if (!this.apiHost) {
throw new Error('API host is required for Bocha provider')
}
}
public async search(query: string, websearch: WebSearchState): Promise<WebSearchProviderResponse> {
try {
if (!query.trim()) {
throw new Error('Search query cannot be empty')
}
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`
}
const params: BochaSearchParams = {
query,
count: websearch.maxResults,
exclude: websearch.excludeDomains.join(','),
freshness: websearch.searchWithTime ? 'oneDay' : 'noLimit',
summary: true,
page: 1
}
const response = await fetch(`${this.apiHost}/v1/web-search`, {
method: 'POST',
body: JSON.stringify(params),
headers: {
...this.defaultHeaders(),
...headers
}
})
if (!response.ok) {
throw new Error(`Bocha search failed: ${response.status} ${response.statusText}`)
}
const resp: BochaSearchResponse = await response.json()
if (resp.code !== 200) {
throw new Error(`Bocha search failed: ${resp.msg}`)
}
return {
query: resp.data.queryContext.originalQuery,
results: resp.data.webPages.value.map((result) => ({
title: result.name,
// 优先使用 summary更详细如果没有则使用 snippet
content: result.summary || result.snippet || '',
url: result.url
}))
}
} catch (error) {
console.error('Bocha search failed:', error)
throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
}