File size: 4,524 Bytes
9c48ae2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time    : 2023/5/23 18:27
@Author  : alexanderwu
@File    : search_engine.py
@From    : https://github.com/geekan/MetaGPT/blob/main/metagpt/tools/search_engine.py
"""
from __future__ import annotations

import json

from autoagents.system.config import Config
from autoagents.system.logs import logger
from .search_engine_serpapi import SerpAPIWrapper
from .search_engine_serper import SerperWrapper

config = Config()
from autoagents.system.tools import SearchEngineType


class SearchEngine:
    """
    TODO: 合入Google Search 并进行反代
    注:这里Google需要挂Proxifier或者类似全局代理
    - DDG: https://pypi.org/project/duckduckgo-search/
    - GOOGLE: https://programmablesearchengine.google.com/controlpanel/overview?cx=63f9de531d0e24de9
    """
    def __init__(self, engine=None, run_func=None, serpapi_api_key=None):
        self.config = Config()
        self.run_func = run_func
        self.engine = engine or self.config.search_engine
        self.serpapi_api_key = serpapi_api_key

    @classmethod
    def run_google(cls, query, max_results=8):
        # results = ddg(query, max_results=max_results)
        results = google_official_search(query, num_results=max_results)
        logger.info(results)
        return results

    async def run(self, query: str, max_results=8):
        if self.engine == SearchEngineType.SERPAPI_GOOGLE:
            if self.serpapi_api_key is not None:
                api = SerpAPIWrapper(serpapi_api_key=self.serpapi_api_key)
            else:
                api = SerpAPIWrapper()
            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()
            rsp = await api.run(query)
        elif self.engine == SearchEngineType.CUSTOM_ENGINE:
            rsp = self.run_func(query)
        else:
            raise NotImplementedError
        return rsp


def google_official_search(query: str, num_results: int = 8, focus=['snippet', 'link', 'title']) -> dict | list[dict]:
    """Return the results of a Google search using the official Google API

    Args:
        query (str): The search query.
        num_results (int): The number of results to return.

    Returns:
        str: The results of the search.
    """

    from googleapiclient.discovery import build
    from googleapiclient.errors import HttpError

    try:
        api_key = config.google_api_key
        custom_search_engine_id = config.google_cse_id

        with build("customsearch", "v1", developerKey=api_key) as service:

            result = (
                service.cse()
                .list(q=query, cx=custom_search_engine_id, num=num_results)
                .execute()
            )
            logger.info(result)
            # Extract the search result items from the response
        search_results = result.get("items", [])

        # Create a list of only the URLs from the search results
        search_results_details = [{i: j for i, j in item_dict.items() if i in focus} for item_dict in search_results]

    except HttpError as e:
        # Handle errors in the API call
        error_details = json.loads(e.content.decode())

        # Check if the error is related to an invalid or missing API key
        if error_details.get("error", {}).get(
            "code"
        ) == 403 and "invalid API key" in error_details.get("error", {}).get(
            "message", ""
        ):
            return "Error: The provided Google API key is invalid or missing."
        else:
            return f"Error: {e}"
    # google_result can be a list or a string depending on the search results

    # Return the list of search result URLs
    return search_results_details


def safe_google_results(results: str | list) -> str:
    """
        Return the results of a google search in a safe format.

    Args:
        results (str | list): The search results.

    Returns:
        str: The results of the search.
    """
    if isinstance(results, list):
        safe_message = json.dumps(
            # FIXME: # .encode("utf-8", "ignore") 这里去掉了,但是AutoGPT里有,很奇怪
            [result for result in results]
        )
    else:
        safe_message = results.encode("utf-8", "ignore").decode("utf-8")
    return safe_message


if __name__ == '__main__':
    SearchEngine.run(query='wtf')