diff --git a/config/config.yaml b/config/config.yaml index b0264e908..eef3ed26f 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -4,7 +4,7 @@ #### if OpenAI -#OPENAI_API_KEY: "YOUR_API_KEY" +OPENAI_API_KEY: "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" #OPENAI_API_BASE: "YOUR_API_BASE" OPENAI_API_MODEL: "gpt-4" MAX_TOKENS: 1500 @@ -26,6 +26,8 @@ RPM: 10 #GOOGLE_API_KEY: "YOUR_API_KEY" ## Visit https://programmablesearchengine.google.com/controlpanel/create to get id. #GOOGLE_CSE_ID: "YOUR_CSE_ID" +## Visit https://serper.dev/ to get key. +# SERPER_API_KEY: "YOUR_API_KEY" #### for TTS diff --git a/examples/Untitled.ipynb b/examples/Untitled.ipynb new file mode 100644 index 000000000..ab32dbf3a --- /dev/null +++ b/examples/Untitled.ipynb @@ -0,0 +1,150 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "id": "b5359d43", + "metadata": {}, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'metagpt'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[3], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01masyncio\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mmetagpt\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mconfig\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Config\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mmetagpt\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mroles\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Searcher\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'metagpt'" + ] + } + ], + "source": [ + "import asyncio\n", + "from metagpt.config import Config\n", + "from metagpt.roles import Searcher" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c01e1f86", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: metagpt in /Users/leon/miniconda3/lib/python3.10/site-packages/metagpt-0.1-py3.10.egg (0.1)\n", + "Requirement already satisfied: aiohttp==3.8.4 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (3.8.4)\n", + "Requirement already satisfied: channels==4.0.0 in /Users/leon/miniconda3/lib/python3.10/site-packages/channels-4.0.0-py3.10.egg (from metagpt) (4.0.0)\n", + "Requirement already satisfied: duckduckgo_search==2.9.4 in /Users/leon/miniconda3/lib/python3.10/site-packages/duckduckgo_search-2.9.4-py3.10.egg (from metagpt) (2.9.4)\n", + "Requirement already satisfied: faiss_cpu==1.7.4 in /Users/leon/miniconda3/lib/python3.10/site-packages/faiss_cpu-1.7.4-py3.10-macosx-11.1-arm64.egg (from metagpt) (1.7.4)\n", + "Requirement already satisfied: fire==0.4.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (0.4.0)\n", + "Requirement already satisfied: langchain==0.0.231 in /Users/leon/miniconda3/lib/python3.10/site-packages/langchain-0.0.231-py3.10.egg (from metagpt) (0.0.231)\n", + "Requirement already satisfied: loguru==0.6.0 in /Users/leon/miniconda3/lib/python3.10/site-packages/loguru-0.6.0-py3.10.egg (from metagpt) (0.6.0)\n", + "Requirement already satisfied: meilisearch==0.21.0 in /Users/leon/miniconda3/lib/python3.10/site-packages/meilisearch-0.21.0-py3.10.egg (from metagpt) (0.21.0)\n", + "Requirement already satisfied: numpy==1.24.3 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (1.24.3)\n", + "Requirement already satisfied: openai==0.27.8 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (0.27.8)\n", + "Requirement already satisfied: openpyxl in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (3.1.2)\n", + "Requirement already satisfied: pandas==1.4.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (1.4.1)\n", + "Requirement already satisfied: pydantic==1.10.7 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (1.10.7)\n", + "Requirement already satisfied: pytest==7.2.2 in /Users/leon/miniconda3/lib/python3.10/site-packages/pytest-7.2.2-py3.10.egg (from metagpt) (7.2.2)\n", + "Requirement already satisfied: python_docx==0.8.11 in /Users/leon/miniconda3/lib/python3.10/site-packages/python_docx-0.8.11-py3.10.egg (from metagpt) (0.8.11)\n", + "Requirement already satisfied: PyYAML==6.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (6.0)\n", + "Requirement already satisfied: setuptools==65.6.3 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (65.6.3)\n", + "Requirement already satisfied: tenacity==8.2.2 in /Users/leon/miniconda3/lib/python3.10/site-packages/tenacity-8.2.2-py3.10.egg (from metagpt) (8.2.2)\n", + "Requirement already satisfied: tiktoken==0.3.3 in /Users/leon/miniconda3/lib/python3.10/site-packages/tiktoken-0.3.3-py3.10-macosx-11.1-arm64.egg (from metagpt) (0.3.3)\n", + "Requirement already satisfied: tqdm==4.64.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from metagpt) (4.64.0)\n", + "Requirement already satisfied: attrs>=17.3.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from aiohttp==3.8.4->metagpt) (23.1.0)\n", + "Requirement already satisfied: charset-normalizer<4.0,>=2.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from aiohttp==3.8.4->metagpt) (2.0.4)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /Users/leon/miniconda3/lib/python3.10/site-packages (from aiohttp==3.8.4->metagpt) (6.0.4)\n", + "Requirement already satisfied: async-timeout<5.0,>=4.0.0a3 in /Users/leon/miniconda3/lib/python3.10/site-packages (from aiohttp==3.8.4->metagpt) (4.0.2)\n", + "Requirement already satisfied: yarl<2.0,>=1.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from aiohttp==3.8.4->metagpt) (1.9.2)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from aiohttp==3.8.4->metagpt) (1.3.3)\n", + "Requirement already satisfied: aiosignal>=1.1.2 in /Users/leon/miniconda3/lib/python3.10/site-packages (from aiohttp==3.8.4->metagpt) (1.3.1)\n", + "Requirement already satisfied: Django>=3.2 in /Users/leon/miniconda3/lib/python3.10/site-packages (from channels==4.0.0->metagpt) (4.2.3)\n", + "Requirement already satisfied: asgiref<4,>=3.5.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from channels==4.0.0->metagpt) (3.7.2)\n", + "Requirement already satisfied: click>=8.1.3 in /Users/leon/miniconda3/lib/python3.10/site-packages (from duckduckgo_search==2.9.4->metagpt) (8.1.3)\n", + "Requirement already satisfied: diskcache>=5.6.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from duckduckgo_search==2.9.4->metagpt) (5.6.1)\n", + "Requirement already satisfied: requests>=2.29.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from duckduckgo_search==2.9.4->metagpt) (2.31.0)\n", + "Requirement already satisfied: six in /Users/leon/miniconda3/lib/python3.10/site-packages (from fire==0.4.0->metagpt) (1.16.0)\n", + "Requirement already satisfied: termcolor in /Users/leon/miniconda3/lib/python3.10/site-packages (from fire==0.4.0->metagpt) (2.3.0)\n", + "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /Users/leon/miniconda3/lib/python3.10/site-packages (from langchain==0.0.231->metagpt) (2.0.19)\n", + "Requirement already satisfied: dataclasses-json<0.6.0,>=0.5.7 in /Users/leon/miniconda3/lib/python3.10/site-packages (from langchain==0.0.231->metagpt) (0.5.9)\n", + "Requirement already satisfied: langchainplus-sdk<0.0.21,>=0.0.20 in /Users/leon/miniconda3/lib/python3.10/site-packages (from langchain==0.0.231->metagpt) (0.0.20)\n", + "Requirement already satisfied: numexpr<3.0.0,>=2.8.4 in /Users/leon/miniconda3/lib/python3.10/site-packages (from langchain==0.0.231->metagpt) (2.8.4)\n", + "Requirement already satisfied: openapi-schema-pydantic<2.0,>=1.2 in /Users/leon/miniconda3/lib/python3.10/site-packages (from langchain==0.0.231->metagpt) (1.2.4)\n", + "Requirement already satisfied: camel-converter[pydantic] in /Users/leon/miniconda3/lib/python3.10/site-packages (from meilisearch==0.21.0->metagpt) (3.0.2)\n", + "Requirement already satisfied: python-dateutil>=2.8.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from pandas==1.4.1->metagpt) (2.8.2)\n", + "Requirement already satisfied: pytz>=2020.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from pandas==1.4.1->metagpt) (2023.3)\n", + "Requirement already satisfied: typing-extensions>=4.2.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from pydantic==1.10.7->metagpt) (4.6.3)\n", + "Requirement already satisfied: iniconfig in /Users/leon/miniconda3/lib/python3.10/site-packages (from pytest==7.2.2->metagpt) (2.0.0)\n", + "Requirement already satisfied: packaging in /Users/leon/miniconda3/lib/python3.10/site-packages (from pytest==7.2.2->metagpt) (23.0)\n", + "Requirement already satisfied: pluggy<2.0,>=0.12 in /Users/leon/miniconda3/lib/python3.10/site-packages (from pytest==7.2.2->metagpt) (1.0.0)\n", + "Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /Users/leon/miniconda3/lib/python3.10/site-packages (from pytest==7.2.2->metagpt) (1.1.1)\n", + "Requirement already satisfied: tomli>=1.0.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from pytest==7.2.2->metagpt) (2.0.1)\n", + "Requirement already satisfied: lxml>=2.3.2 in /Users/leon/miniconda3/lib/python3.10/site-packages (from python_docx==0.8.11->metagpt) (4.9.3)\n", + "Requirement already satisfied: regex>=2022.1.18 in /Users/leon/miniconda3/lib/python3.10/site-packages (from tiktoken==0.3.3->metagpt) (2023.6.3)\n", + "Requirement already satisfied: et-xmlfile in /Users/leon/miniconda3/lib/python3.10/site-packages (from openpyxl->metagpt) (1.1.0)\n", + "Requirement already satisfied: marshmallow<4.0.0,>=3.3.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.231->metagpt) (3.19.0)\n", + "Requirement already satisfied: marshmallow-enum<2.0.0,>=1.5.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.231->metagpt) (1.5.1)\n", + "Requirement already satisfied: typing-inspect>=0.4.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.231->metagpt) (0.9.0)\n", + "Requirement already satisfied: sqlparse>=0.3.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from Django>=3.2->channels==4.0.0->metagpt) (0.4.4)\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/leon/miniconda3/lib/python3.10/site-packages (from requests>=2.29.0->duckduckgo_search==2.9.4->metagpt) (3.4)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/leon/miniconda3/lib/python3.10/site-packages (from requests>=2.29.0->duckduckgo_search==2.9.4->metagpt) (1.26.15)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/leon/miniconda3/lib/python3.10/site-packages (from requests>=2.29.0->duckduckgo_search==2.9.4->metagpt) (2022.12.7)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: mypy-extensions>=0.3.0 in /Users/leon/miniconda3/lib/python3.10/site-packages (from typing-inspect>=0.4.0->dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.231->metagpt) (1.0.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.1.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.2\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "!pip install metagpt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b48f4dab", + "metadata": {}, + "outputs": [], + "source": [ + "async def main():\n", + " await Searcher().run(\"What are some good sun protection products?\")\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " asyncio.run(main())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/serper_api.py b/examples/serper_api.py new file mode 100644 index 000000000..9888276db --- /dev/null +++ b/examples/serper_api.py @@ -0,0 +1,10 @@ +import asyncio +from metagpt.config import Config +from metagpt.roles import Searcher +from metagpt.tools import SearchEngineType + +async def main(): + await Searcher(engine = SearchEngineType.SERPER_GOOGLE).run("What are the best ski brands for advanced skiers?") + + +asyncio.run(main()) diff --git a/metagpt/actions/search_and_summarize.py b/metagpt/actions/search_and_summarize.py index 06ddc5daf..7dce790d2 100644 --- a/metagpt/actions/search_and_summarize.py +++ b/metagpt/actions/search_and_summarize.py @@ -110,10 +110,14 @@ class SearchAndSummarize(Action): super().__init__(name, context, llm) async def run(self, context: list[Message], system_text=SEARCH_AND_SUMMARIZE_SYSTEM) -> str: - if not self.config.serpapi_api_key or 'YOUR_API_KEY' == self.config.serpapi_api_key: - logger.warning('Configure SERPAPI_API_KEY to unlock full feature') + no_serpapi = not self.config.serpapi_api_key or 'YOUR_API_KEY' == self.config.serpapi_api_key + no_serper = not self.config.serper_api_key or 'YOUR_API_KEY' == self.config.serper_api_key + no_google= not self.config.google_api_key or 'YOUR_API_KEY' == self.config.google_api_key + + if no_serpapi and no_google and no_serper: + logger.warning('Configure one of SERPAPI_API_KEY, SERPER_API_KEY, GOOGLE_API_KEY to unlock full feature') return "" - + query = context[-1].content # logger.debug(query) rsp = await self.search_engine.run(query) diff --git a/metagpt/config.py b/metagpt/config.py index 5c6693dd8..e60bc1927 100644 --- a/metagpt/config.py +++ b/metagpt/config.py @@ -55,6 +55,7 @@ class Config(metaclass=Singleton): self.deployment_id = self._get('DEPLOYMENT_ID') self.serpapi_api_key = self._get('SERPAPI_API_KEY') + self.serper_api_key = self._get('SERPER_API_KEY') self.google_api_key = self._get('GOOGLE_API_KEY') self.google_cse_id = self._get('GOOGLE_CSE_ID') self.search_engine = self._get('SEARCH_ENGINE', SearchEngineType.SERPAPI_GOOGLE) diff --git a/metagpt/roles/seacher.py b/metagpt/roles/seacher.py index bc7bd7470..c4f3ffb56 100644 --- a/metagpt/roles/seacher.py +++ b/metagpt/roles/seacher.py @@ -14,9 +14,9 @@ from metagpt.schema import Message class Searcher(Role): def __init__(self, name='Alice', profile='Smart Assistant', goal='Provide search services for users', - constraints='Answer is rich and complete', **kwargs): + constraints='Answer is rich and complete', engine=SearchEngineType.SERPAPI_GOOGLE, **kwargs): super().__init__(name, profile, goal, constraints, **kwargs) - self._init_actions([SearchAndSummarize]) + self._init_actions([SearchAndSummarize(engine = engine)]) def set_search_func(self, search_func): action = SearchAndSummarize("", engine=SearchEngineType.CUSTOM_ENGINE, search_func=search_func) @@ -24,7 +24,6 @@ class Searcher(Role): async def _act_sp(self) -> Message: logger.info(f"{self._setting}: ready to {self._rc.todo}") - logger.info(self._rc.memory.get(k=0)) response = await self._rc.todo.run(self._rc.memory.get(k=0)) # logger.info(response) if isinstance(response, ActionOutput): diff --git a/metagpt/tools/__init__.py b/metagpt/tools/__init__.py index f42d46457..46ee0a0a0 100644 --- a/metagpt/tools/__init__.py +++ b/metagpt/tools/__init__.py @@ -13,4 +13,5 @@ from enum import Enum, auto class SearchEngineType(Enum): SERPAPI_GOOGLE = auto() DIRECT_GOOGLE = auto() + SERPER_GOOGLE = auto() CUSTOM_ENGINE = auto() diff --git a/metagpt/tools/search_engine.py b/metagpt/tools/search_engine.py index 83eab3fc0..5b9e1cd23 100644 --- a/metagpt/tools/search_engine.py +++ b/metagpt/tools/search_engine.py @@ -14,6 +14,7 @@ from duckduckgo_search import ddg from metagpt.config import Config from metagpt.tools.search_engine_serpapi import SerpAPIWrapper +from metagpt.tools.search_engine_serper import SerperWrapper config = Config() from metagpt.tools import SearchEngineType @@ -44,6 +45,12 @@ class SearchEngine: rsp = await api.run(query) elif self.engine == SearchEngineType.DIRECT_GOOGLE: rsp = SearchEngine.run_google(query, max_results) + elif self.engine == SearchEngineType.SERPER_GOOGLE: + api = SerperWrapper() + if isinstance(query, list): + rsp = await api.run(query) + elif isinstance(query, str): + rsp = await api.run([query]) elif self.engine == SearchEngineType.CUSTOM_ENGINE: rsp = self.run_func(query) else: diff --git a/metagpt/tools/search_engine_serper.py b/metagpt/tools/search_engine_serper.py new file mode 100644 index 000000000..91a8afce9 --- /dev/null +++ b/metagpt/tools/search_engine_serper.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +@Time : 2023/5/23 18:27 +@Author : alexanderwu +@File : search_engine_serpapi.py +""" +from typing import Any, Dict, Optional, Tuple +from metagpt.logs import logger +import aiohttp +import json +from pydantic import BaseModel, Field + +from metagpt.config import Config + + +class SerperWrapper(BaseModel): + """Wrapper around SerpAPI. + + To use, you should have the ``google-search-results`` python package installed, + and the environment variable ``SERPAPI_API_KEY`` set with your API key, or pass + `serpapi_api_key` as a named parameter to the constructor. + """ + + search_engine: Any #: :meta private: + payload: dict = Field( + default={ + "page": 1, + "num": 10 + } + ) + config = Config() + serper_api_key: Optional[str] = config.serper_api_key + aiosession: Optional[aiohttp.ClientSession] = None + + class Config: + arbitrary_types_allowed = True + + async def run(self, query: str, **kwargs: Any) -> str: + """Run query through Serper and parse result async.""" + return ";".join([self._process_response(res) for res in await self.results(query)]) + + async def results(self, queries: list[str]) -> dict: + """Use aiohttp to run query through Serper and return the results async.""" + + def construct_url_and_payload_and_headers() -> Tuple[str, Dict[str, str]]: + payloads = self.get_payloads(queries) + url = "https://google.serper.dev/search" + headers = self.get_headers() + return url, payloads, headers + + url, payloads, headers = construct_url_and_payload_and_headers() + if not self.aiosession: + async with aiohttp.ClientSession() as session: + async with session.post(url, data=payloads, headers=headers) as response: + res = await response.json() + + else: + async with self.aiosession.get.post(url, data=payloads, headers=headers) as response: + res = await response.json() + + return res + + def get_payloads(self, queries: list[str]) -> Dict[str, str]: + """Get payloads for Serper.""" + payloads = [] + for query in queries: + _payload = { + "q": query, + } + payloads.append({**self.payload, **_payload}) + return json.dumps(payloads, sort_keys=True) + + def get_headers(self) -> Dict[str, str]: + headers = { + 'X-API-KEY': self.serper_api_key, + 'Content-Type': 'application/json' + } + return headers + + @staticmethod + def _process_response(res: dict) -> str: + """Process response from SerpAPI.""" + # logger.debug(res) + focus = ['title', 'snippet', 'link'] + def get_focused(x): return {i: j for i, j in x.items() if i in focus} + + if "error" in res.keys(): + raise ValueError(f"Got error from SerpAPI: {res['error']}") + if "answer_box" in res.keys() and "answer" in res["answer_box"].keys(): + toret = res["answer_box"]["answer"] + elif "answer_box" in res.keys() and "snippet" in res["answer_box"].keys(): + toret = res["answer_box"]["snippet"] + elif ( + "answer_box" in res.keys() + and "snippet_highlighted_words" in res["answer_box"].keys() + ): + toret = res["answer_box"]["snippet_highlighted_words"][0] + elif ( + "sports_results" in res.keys() + and "game_spotlight" in res["sports_results"].keys() + ): + toret = res["sports_results"]["game_spotlight"] + elif ( + "knowledge_graph" in res.keys() + and "description" in res["knowledge_graph"].keys() + ): + toret = res["knowledge_graph"]["description"] + elif "snippet" in res["organic"][0].keys(): + toret = res["organic"][0]["snippet"] + else: + toret = "No good search result found" + + toret_l = [] + if "answer_box" in res.keys() and "snippet" in res["answer_box"].keys(): + toret_l += [get_focused(res["answer_box"])] + if res.get("organic"): + toret_l += [get_focused(i) for i in res.get("organic")] + + return str(toret) + '\n' + str(toret_l)