當我希望搜索"王者榮耀現在是什么賽季"時,我會按照以下格式進行操作:
現在是2024年,因此我應該搜索王者榮耀賽季關鍵詞
<|action_start|><|plugin|>{{"name": "FastWebBrowser.search", "parameters":
{{"query": ["王者榮耀 賽季", "2024年王者榮耀賽季"]}}}}<|action_end|>

(2)根據 MindSearch 里的 SearcherAgent 提示詞中的 few shot 例子,可以看出其實現的 搜索 API Action需要有個可以進一步檢索網站內容的函數(網頁內容的抓取),也就是 select 函數 。這是因為多數 搜索 API 返回的內容都是網站內容里的 片段,并不會包含太多有用的信息。

### select
為了找到王者榮耀s36賽季最強射手,我需要尋找提及王者榮耀s36射手的網頁。初步瀏覽網頁后,
發現網頁0提到王者榮耀s36賽季的信息,但沒有具體提及射手的相關信息。網頁3提到“s36最強射手出現?”,
有可能包含最強射手信息。網頁13提到“四大T0英雄崛起,射手榮耀降臨”,可能包含最強射手的信息。
因此,我選擇了網頁3和網頁13進行進一步閱讀。
<|action_start|><|plugin|>{{"name": "FastWebBrowser.select",
"parameters": {{"index": [3, 13]}}}}<|action_end|>
"""

(3)搜索 API Action 中的 search 函數 返回的最終內容最好符合以下所期望的格式,否則在 MindSearch 內部代碼解析內容時有可能會報錯,MindSearch 里的 SeacherAgent 會根據此結果來調用 select 函數: 

[{'type': 'text', 'content': '{"0": {"url": "https:", "summ": "...", "title": "..."},]

為了在 MindSearch 中支持新的搜索 API,我們可以采用兩種方法:一是在 lagent/actions/ 文件夾下新建文件從零開始實現,二是直接在現有的 bing_browser.py 中進行功能擴展。

本文將著重介紹第二種方法,即在現有代碼基礎上引入新的搜索 API,這樣不僅避免了重復開發,還能確保代碼的一致性和可維護性。鑒于 bing_browser.py 已內置了 search 函數和 select 函數等功能,并已妥善處理了前文提到的關鍵注意事項,我們將聚焦于通過這種方法來擴展現有功能。接下來,我們將詳細解析 bing_browser.py 文件的內容。


2. 淺析 bing_broswer.py

SearcherAgent 調用 BingBrowser 類的代碼流程如下:def search() -> self.searcher.search() -> self.searcher._call_serper_api() -> self.searcher._parse_response() ->  self.searcher._filter_results() -> def select() -> self.fetcher.fetch()

在上述流程中,標記為黃色以及藍色的函數是 SearcherAgent 觸發 search() 函數時會執行到的關鍵函數。其中標記為藍色的函數則是我們在支持新的 搜索 API 時,需要在新的 searcher 類中實現的函數。而標記為綠色的函數,則是在 SearcherAgent 觸發 select() 函數時會執行到的函數。

在 bing_browser.py 文件中,定義了三個關鍵的類,它們分別是:

class BingBrowser(BaseAction) 類

此類是被設計為 SearcherAgent 中的 Action 組件,負責處理搜索相關的核心邏輯。此類含有兩個重要的函數,分別是 search() 和 select(),分別對應前置內容中的 第一點 和 第二點。

def search() 函數

當接收到 SearcherAgent 生成的多個 query(以列表形式表示)后,單獨給每個在 queries 列表中的 query 開啟一個線程,并且調用對應的 searcher.serach() 函數來執行相應的 搜索 API 調用。

@tool_api
def search(self, query: Union[str, List[str]]) -> dict:
"""BING search API
Args:
query (List[str]): list of search query strings
"""
queries = query if isinstance(query, list) else [query]
search_results = {}

with ThreadPoolExecutor() as executor:
future_to_query = {
executor.submit(self.searcher.search, q): q
for q in queries
}

def select() 函數

在 SearcherAgent 接收到 search() 函數返回的搜索 API 結果后,它會判斷哪些網站的內容需要進一步深入查詢,并調用 select() 函數來處理這些需求。select()函數會為每個需要深入查詢的網頁(通過索引值標識)單獨開啟一個線程,并利用 ContentFetcher 類(即 fetcher)來抓取這些網站的詳細內容。值得注意的是,所有的 searcher 都共享同一個 ContentFetcher 實例。

@tool_api
def select(self, select_ids: List[int]) -> dict:
"""get the detailed content on the selected pages.

Args:
select_ids (List[int]): list of index to select. Max number of index to be selected is no more than 4.
"""
if not self.search_results:
raise ValueError('No search results to select from.')

new_search_results = {}
with ThreadPoolExecutor() as executor:
future_to_id = {
executor.submit(self.fetcher.fetch,
self.search_results[select_id]['url']):
select_id
for select_id in select_ids if select_id in self.search_results
}

class ContentFetcher 類

ContentFetcher 類中的 fetch 函數負責使用 Pythonrequests 模塊從網站抓取內容,并通過 BeautifulSoup 庫將獲取的 HTML 文檔結構化。

注意,需要 cookie 授權的網站會訪問失敗。

class ContentFetcher:
@cached(cache=TTLCache(maxsize=100, ttl=600))
def fetch(self, url: str) -> Tuple[bool, str]:
try:
response = requests.get(url, timeout=self.timeout)
response.raise_for_status()
html = response.content
except requests.RequestException as e:
return False, str(e)

text = BeautifulSoup(html, 'html.parser').get_text()
cleaned_text = re.sub(r'\n+', '\n', text)
return True, cleaned_text

class BaseSearch 類

這是實現新的 Searcher 類時需要繼承的一個基類,其主要目的是調用內部的 _filter_results 函數。該函數的作用是確保從 searcher 返回的內容不包含黑名單中的 URL ,并且確保返回的內容數量不超過 topk。同時對內容進行統一格式化,這對應于前置內容中的 第三點 要求。

class BaseSearch:
def _filter_results(self, results: List[tuple]) -> dict:
filtered_results = {}
count = 0
for url, snippet, title in results:
if all(domain not in url
for domain in self.black_list) and not url.endswith('.pdf'):
filtered_results[count] = {
'url': url,
'summ': json.dumps(snippet, ensure_ascii=False)[1:-1],
'title': title
}
count += 1
if count >= self.topk:
break
return filtered_results

3. 實現新的 Searcher 類

綜上所述,bing_broswer.py 里已經提供了核心的相關類以及函數,現在只需要實現一個新的 Searcher 類(對應 bing_broswer.py 里的 def search() 函數中的 self.searcher)

當前,SearcherAgent 中的提示詞設計緊密圍繞 BingBrowser 類展開。因此,為了最便捷地支持新的搜索 API,我們只需在現有基礎上新增一個 Searcher 類即可實現(方法二),這樣的改動既直接又高效。否則,如果基于方法一實現且未遵循前置內容中的注意事項,則可能需要對 SearcherAgent 中的提示詞進行調整。在開始實現新的 searcher 類之前,需要在 conda 環境中對 lagent 進行源碼安裝,以便 lagent 文件夾中的代碼改動能夠即時生效。

以 GoogleSearch Seacher 為例(Google Serper API),需要實現的函數有:def search(),def _call_serper_api() 和 def _parse_response(),其中 def search() 是 Searcher 的主函數。

首先定義一個 GoogleSearch 類,繼承 BaseSearch 類,并且將參數賦值為對象的屬性(參數由 BingBrowser 類傳入)。black_list 參數由 BaseSearch 類中的 _filter_results 函數調用。api_key ,search_type,kwargs 參數都是和 Google Serper API 相關的參數,使用于對 搜索 API 發送請求。topk 參數在向 搜索 API 發送請求時使用,并在 _filter_results 函數中再次被調用,以進一步確保最終返回的內容數量不超過 topk 。

class GoogleSearch(BaseSearch):
def __init__(self,
api_key: str,
topk: int = 3,
black_list: List[str] = [
'enoN',
'youtube.com',
'bilibili.com',
'researchgate.net',
],
**kwargs):
self.api_key = api_key
self.proxy = kwargs.get('proxy')
self.search_type = kwargs.get('search_type', 'search')
self.kwargs = kwargs
super().__init__(topk, black_list)

def search() 函數

調用內部的 _call_serper_api 函數進行搜索,并隨后調用內部 _parse_response 函數對返回的結果進行結構化處理。在調用過程中,如果發生異常,該函數會實施重試機制,即在短暫等待后重新嘗試,直至達到預設的最大重試次數。

對于有每秒訪問限制的搜索 API,由于用的多線程調用,此函數在嘗試最大重試次數之后仍可能報錯。

@cached(cache=TTLCache(maxsize=100, ttl=600))
def search(self, query: str, max_retry: int = 3) -> dict:
for attempt in range(max_retry):
try:
response = self._call_serper_api(query)
return self._parse_response(response)
except Exception as e:
logging.exception(str(e))
warnings.warn(
f'Retry {attempt + 1}/{max_retry} due to error: {e}')
time.sleep(random.randint(2, 5))
raise Exception(
'Failed to get search results from Google Serper Search after retries.'
)

def _call_serper_api() 函數

對相對應的 搜索 API 發送請求,并且獲得對應結果,其參數以及請求時的格式請參考對應的搜索 API 文檔。

def _call_serper_api(self, query: str) -> dict:
endpoint = f'https://google.serper.dev/{self.search_type}'
params = {
'q': query,
'num': self.topk,
**{
key: value
for key, value in self.kwargs.items() if value is not None
},
}
headers = {
'X-API-KEY': self.api_key or '',
'Content-Type': 'application/json'
}
response = requests.get(
endpoint, headers=headers, params=params, proxies=self.proxy)
response.raise_for_status()
return response.json()

def _parse_response() 函數

對于 搜索API 返回的每一個結果,將其提取并包裝成 (url,snippest,title) 格式的元組,將這些元組添加到一個名為 raw_results 的列表中,隨后將 raw_results 列表作為參數傳遞給 BaseSearch 類中的 _filter_results 函數。

def _parse_response(self, response: dict) -> dict:
raw_results = []

for result in response[self.result_key_for_type[
self.search_type]][:self.topk]:
description = result.get('snippet', '')
attributes = '. '.join(
f'{attribute}: {value}'
for attribute, value in result.get('attributes', {}).items())
raw_results.append(
(result.get('link', ''),
f'{description}. {attributes}' if attributes else description,
result.get('title', '')))

return self._filter_results(raw_results)

4. 總結

本文深入探討了在 MindSearch 中實現新的 搜索 API 所需注意的關鍵事項,并詳細介紹了 SearcherAgent 的調用流程,包括涉及的類和函數。特別地,我們重點介紹了如何在 bing_browser.py 中支持新的搜索 API,具體包括實現新的 Searcher 類,以及定義 def search()、def _call_serper_api()和def _parse_response()函數,以確保新的搜索 API 能夠無縫集成并擴展現有功能。

文章轉自微信公眾號@OpenMMLab

熱門推薦
一個賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
3000+提示詞助力AI大模型
和專業工程師共享工作效率翻倍的秘密
熱門推薦
一個賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
返回頂部
上一篇
Kimi API 還沒用起來?請看這篇無門檻快速入門指南
下一篇
GLM-4-AllTools API革新大模型使用體驗
国内精品久久久久影院日本,日本中文字幕视频,99久久精品99999久久,又粗又大又黄又硬又爽毛片
亚洲一区二区精品久久av| 99久久99久久精品免费观看| 日韩电影免费一区| 日韩中文字幕av电影| 在线播放中文一区| 亚洲女人****多毛耸耸8| 日韩有码一区二区三区| 色噜噜狠狠色综合中国| 欧美日韩在线播| 亚洲国产高清aⅴ视频| 激情欧美日韩一区二区| 欧美巨大另类极品videosbest| 7777精品伊人久久久大香线蕉经典版下载 | 国产精品女同一区二区三区| 国产麻豆欧美日韩一区| 国产精品乱子久久久久| 91福利在线免费观看| 久久国产三级精品| 亚洲午夜免费电影| 久久精品人人做人人爽97| 久久精品国产第一区二区三区| 国产婷婷色一区二区三区| 欧美亚洲国产一区在线观看网站| 免费人成网站在线观看欧美高清| 久久久久九九视频| 欧美日韩一区二区三区四区| 国产99久久久国产精品潘金 | 国产精品毛片大码女人| 久久久久久久久蜜桃| 美女高潮久久久| 国产美女av一区二区三区| 男男视频亚洲欧美| 日本aⅴ精品一区二区三区| 亚洲福利国产精品| 午夜亚洲国产au精品一区二区| 亚洲精品乱码久久久久久| 一区二区三区在线免费观看 | 美日韩一区二区三区| 亚洲18影院在线观看| 日韩在线一二三区| 国产美女一区二区| 91视频xxxx| 91麻豆精品国产无毒不卡在线观看| 欧美日本一区二区| 欧美激情在线一区二区三区| 在线观看视频一区二区| 欧美一区二区在线不卡| 欧美亚洲一区二区在线观看| 免费日本视频一区| 在线免费不卡视频| 日韩欧美一区二区免费| 一区二区三区四区视频精品免费| 久久er99热精品一区二区| 色老综合老女人久久久| 91麻豆精品国产91久久久更新时间| 欧美一级在线免费| 国产精品美女一区二区三区| 亚洲一区在线观看网站| 成人精品视频网站| 精品国产不卡一区二区三区| 国产欧美精品国产国产专区| 午夜精品免费在线| 91社区在线播放| 国产精品久久久久国产精品日日| 国内久久婷婷综合| 精品播放一区二区| 国产传媒久久文化传媒| 国产精品毛片高清在线完整版| 成人h精品动漫一区二区三区| 精品国产乱码91久久久久久网站| 国产精品私人影院| 欧美亚洲禁片免费| 国产一区二区0| 久久久久久久电影| 激情五月激情综合网| 欧美变态tickle挠乳网站| 免费在线看一区| 中文字幕亚洲在| 欧美三级三级三级| 国产一区二区在线视频| 亚洲日本免费电影| 94色蜜桃网一区二区三区| 日韩制服丝袜先锋影音| 中文字幕免费不卡在线| 日本成人超碰在线观看| 一区二区三区在线视频观看58| 欧美精品久久久久久久多人混战| 亚洲激情校园春色| 亚洲免费观看高清完整| 精品久久久久久久久久久久久久久| 国产成人午夜精品5599| 麻豆极品一区二区三区| 亚洲精品中文在线| 日韩精品中文字幕一区二区三区 | 国产午夜精品福利| 亚洲三级在线播放| 一区二区三区欧美| 7777精品伊人久久久大香线蕉 | 福利电影一区二区| 成人黄动漫网站免费app| 免费成人在线观看| 国产精品影视网| eeuss影院一区二区三区 | 99久久99久久精品免费观看| 在线观看日韩电影| 99久久精品久久久久久清纯| 成人一区二区三区| 日韩精品中文字幕在线一区| 久久久国产一区二区三区四区小说| 欧美激情在线免费观看| 一区二区三区四区av| 高清不卡一区二区| 欧美群妇大交群中文字幕| 91精品国产黑色紧身裤美女| 日韩欧美二区三区| 亚洲小说欧美激情另类| 国产伦精品一区二区三区免费迷 | 欧美视频完全免费看| 久久精品亚洲精品国产欧美| 国产情人综合久久777777| 欧美激情一区在线| 精品久久久久久久久久久院品网| 中文字幕一区二区三区蜜月| 蜜臀va亚洲va欧美va天堂| 欧美色图天堂网| 亚洲va欧美va人人爽| 欧美一区二区三区精品| 激情综合网天天干| 精品成人a区在线观看| 狠狠色狠狠色综合日日91app| 久久亚洲精品国产精品紫薇| 国产精品天美传媒| 91在线视频免费观看| 亚洲一区二区五区| 亚洲国产精品t66y| 欧美大胆一级视频| 国产一区二三区| 日本国产一区二区| 久久99蜜桃精品| 国产蜜臀97一区二区三区| 国产精品亚洲成人| 爽好久久久欧美精品| 最新日韩av在线| 国产精品色在线| 欧美美女直播网站| 一本一道久久a久久精品综合蜜臀| 奇米一区二区三区av| 亚洲精品视频在线看| 久久久久国产免费免费| 99九九99九九九视频精品| 国产精品进线69影院| 精品日韩在线一区| 久久久一区二区三区| 久久久久久久久久久久电影| 91 com成人网| 久久精品视频在线看| 精品国产99国产精品| 中文字幕中文在线不卡住| 国产精品国产三级国产普通话99 | 欧美性受xxxx| 日韩小视频在线观看专区| 国产亚洲va综合人人澡精品| 久久一区二区三区四区| 亚洲欧美成aⅴ人在线观看| 一色屋精品亚洲香蕉网站| 日韩主播视频在线| 国产盗摄一区二区三区| 九色综合狠狠综合久久| 成人三级在线视频| 日韩一区二区免费在线观看| 亚洲国产精品t66y| 丝袜美腿亚洲综合| 日本乱人伦一区| 国产日韩欧美综合一区| 日韩一级完整毛片| 欧美亚洲一区二区三区四区| 国产视频一区在线播放| 欧美日韩精品欧美日韩精品一| 91精品午夜视频| 欧美大片一区二区| 久久影院视频免费| 久久久久免费观看| 天堂久久久久va久久久久| 99re成人精品视频| 中文字幕一区二区三区不卡在线 | 国产在线不卡一卡二卡三卡四卡| 欧美巨大另类极品videosbest | 亚洲精品免费在线播放| 欧美艳星brazzers| 一区二区三区电影在线播| 欧美日韩国产精品自在自线| 久久先锋影音av鲁色资源网| 美女被吸乳得到大胸91| 91蜜桃视频在线| 奇米一区二区三区| 日韩美女视频一区| 久久久久久**毛片大全| 欧美特级限制片免费在线观看| 国产剧情一区二区| 久久国产精品99久久久久久老狼|