feat: support setting concurrency limits in web search

This commit is contained in:
AnotiaWang
2025-02-17 16:20:25 +08:00
parent c10090d7d9
commit fc83d9387e
9 changed files with 80 additions and 22 deletions

View File

@ -24,6 +24,10 @@ Please give a 🌟 Star if you like this project!
## Recent updates ## Recent updates
25/02/17
- Added: set rate limits for web search
25/02/16 25/02/16
- Refactored the search visualization using VueFlow - Refactored the search visualization using VueFlow
@ -48,7 +52,6 @@ Please give a 🌟 Star if you like this project!
- Added Docker support - Added Docker support
- Fixed "export as PDF" issues - Fixed "export as PDF" issues
## How to use ## How to use
Live demo: <a href="https://deep-research.ataw.top" target="_blank">https://deep-research.ataw.top</a> Live demo: <a href="https://deep-research.ataw.top" target="_blank">https://deep-research.ataw.top</a>

View File

@ -25,7 +25,7 @@
25/02/15 25/02/15
- AI 提供商支持 DeepSeekOpenRouter 和 Ollama联网搜支持 Firecrawl - AI 提供商支持 DeepSeekOpenRouter 和 Ollama联网搜支持 Firecrawl
- 支持检查项目更新 - 支持检查项目更新
- 支持重新生成报告 - 支持重新生成报告
- 一般性优化和改进 - 一般性优化和改进

View File

@ -239,6 +239,19 @@
private private
/> />
</UFormField> </UFormField>
<UFormField :label="$t('settings.webSearch.concurrencyLimit')">
<template #help>
{{ $t('settings.webSearch.concurrencyLimitHelp') }}
</template>
<UInput
v-model="config.webSearch.concurrencyLimit"
class="w-15"
type="number"
:min="1"
:max="5"
:step="1"
/>
</UFormField>
</div> </div>
</template> </template>
<template #footer> <template #footer>

View File

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineProps } from 'vue'
import { Handle, Position } from '@vue-flow/core' import { Handle, Position } from '@vue-flow/core'
import type { ButtonProps } from '@nuxt/ui' import type { ButtonProps } from '@nuxt/ui'
import type { SearchNodeData } from './SearchFlow.vue' import type { SearchNodeData } from './SearchFlow.vue'

30
composables/usePLimit.ts Normal file
View File

@ -0,0 +1,30 @@
import pLimit from 'p-limit'
/**
* The concurrency value used by the global limit.
* This represents the *actual* limit value.
* The value in `globalLimit` should not be used, because `deepResearch` uses recursive calls,
* and `globalLimit.concurrency` can be much higher than the actual one.
*/
let globalLimitConcurrency = 2
const globalLimit = pLimit(globalLimitConcurrency)
export function usePLimit() {
const { config } = useConfigStore()
if (
config.webSearch.concurrencyLimit &&
config.webSearch.concurrencyLimit >= 1 &&
globalLimitConcurrency !== config.webSearch.concurrencyLimit
) {
console.log(
`[usePLimit] Updating concurrency from ${globalLimitConcurrency} to ${config.webSearch.concurrencyLimit}. Current concurrency: ${globalLimit.concurrency}`,
)
let newLimit = config.webSearch.concurrencyLimit
let diff = newLimit - globalLimitConcurrency
globalLimitConcurrency = newLimit
globalLimit.concurrency += diff
}
return globalLimit
}

View File

@ -38,7 +38,9 @@
"firecrawl": { "firecrawl": {
"help": "Get one API key at {0}." "help": "Get one API key at {0}."
} }
} },
"concurrencyLimitHelp": "Limit the concurrent search tasks. This is useful to avoid overloading the search provider and causing requests to fail.",
"concurrencyLimit": "Concurrency Limit"
} }
}, },
"researchTopic": { "researchTopic": {

View File

@ -38,7 +38,9 @@
"tavily": { "tavily": {
"help": "和 Firecrawl 类似,不过提供了每月 1000 次免费搜索。在 {0} 获取一个 API key。" "help": "和 Firecrawl 类似,不过提供了每月 1000 次免费搜索。在 {0} 获取一个 API key。"
} }
} },
"concurrencyLimit": "并发数",
"concurrencyLimitHelp": "限制同时进行的搜索数量。这样可以避免被 API 服务限流,导致请求失败。"
} }
}, },
"researchTopic": { "researchTopic": {

View File

@ -61,9 +61,6 @@ export type ResearchStep =
| { type: 'error'; message: string; nodeId: string } | { type: 'error'; message: string; nodeId: string }
| { type: 'complete'; learnings: string[]; visitedUrls: string[] } | { type: 'complete'; learnings: string[]; visitedUrls: string[] }
// increase this if you have higher API rate limits
const ConcurrencyLimit = 2
/** /**
* Schema for {@link generateSearchQueries} without dynamic descriptions * Schema for {@link generateSearchQueries} without dynamic descriptions
*/ */
@ -242,6 +239,7 @@ export async function deepResearch({
}): Promise<ResearchResult> { }): Promise<ResearchResult> {
const { t } = useNuxtApp().$i18n const { t } = useNuxtApp().$i18n
const language = t('language', {}, { locale: languageCode }) const language = t('language', {}, { locale: languageCode })
const globalLimit = usePLimit()
onProgress({ onProgress({
type: 'generating_query', type: 'generating_query',
@ -257,7 +255,6 @@ export async function deepResearch({
language, language,
searchLanguage, searchLanguage,
}) })
const limit = pLimit(ConcurrencyLimit)
let searchQueries: PartialSearchQuery[] = [] let searchQueries: PartialSearchQuery[] = []
@ -322,7 +319,7 @@ export async function deepResearch({
// Run in parallel and limit the concurrency // Run in parallel and limit the concurrency
const results = await Promise.all( const results = await Promise.all(
searchQueries.map((searchQuery, i) => searchQueries.map((searchQuery, i) =>
limit(async () => { globalLimit(async () => {
if (!searchQuery?.query) { if (!searchQuery?.query) {
return { return {
learnings: [], learnings: [],
@ -433,17 +430,26 @@ export async function deepResearch({
.join('')} .join('')}
`.trim() `.trim()
return deepResearch({ // Add concurrency by 1, and do next recursive search
query: nextQuery, globalLimit.concurrency++
breadth: nextBreadth, try {
maxDepth, const r = await deepResearch({
learnings: allLearnings, query: nextQuery,
visitedUrls: allUrls, breadth: nextBreadth,
onProgress, maxDepth,
currentDepth: nextDepth, learnings: allLearnings,
nodeId: childNodeId(nodeId, i), visitedUrls: allUrls,
languageCode, onProgress,
}) currentDepth: nextDepth,
nodeId: childNodeId(nodeId, i),
languageCode,
})
return r
} catch (error) {
throw error
} finally {
globalLimit.concurrency--
}
} else { } else {
return { return {
learnings: allLearnings, learnings: allLearnings,

View File

@ -21,6 +21,8 @@ export interface ConfigWebSearch {
apiKey?: string apiKey?: string
/** Force the LLM to generate serp queries in a certain language */ /** Force the LLM to generate serp queries in a certain language */
searchLanguage?: Locale searchLanguage?: Locale
/** Limit the number of concurrent tasks globally */
concurrencyLimit?: number
} }
export interface Config { export interface Config {
@ -37,8 +39,9 @@ export const useConfigStore = defineStore('config', () => {
}, },
webSearch: { webSearch: {
provider: 'tavily', provider: 'tavily',
concurrencyLimit: 2,
}, },
}) } satisfies Config)
// The version user dismissed the update notification // The version user dismissed the update notification
const dismissUpdateVersion = useLocalStorage<string>( const dismissUpdateVersion = useLocalStorage<string>(
'dismiss-update-version', 'dismiss-update-version',