hyperforge-google 1.0.0.post29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ from .agent import GoogleAgent
2
+ from .driver import GoogleDriver
3
+
4
+ __all__ = ["GoogleAgent", "GoogleDriver"]
@@ -0,0 +1,199 @@
1
+ # GOOD REFERENCE: https://github.com/philschmid/gemini-samples/blob/main/examples/gemini-google-search.ipynb
2
+
3
+ from time import time
4
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple, cast
5
+ from uuid import uuid4
6
+
7
+ from google.genai.types import (
8
+ GenerateContentConfig,
9
+ GoogleSearch,
10
+ ThinkingConfig,
11
+ Tool,
12
+ )
13
+ from hyperforge.agent import Agent
14
+ from hyperforge.configure import agent
15
+ from hyperforge.context.agent import ContextAgent
16
+ from hyperforge.definition import FunctionDefinition
17
+ from hyperforge.manager import Manager
18
+ from hyperforge.memory.memory import Chunk, Context, QuestionMemory
19
+ from hyperforge.utils.http import safe_http_client
20
+
21
+ from hyperforge_google.config import GoogleAgentConfig
22
+ from hyperforge_google.driver import GoogleDriver
23
+
24
+
25
+ @agent(
26
+ id="google",
27
+ agent_type="context",
28
+ title="Google Search",
29
+ description="Use Google Search to get information from the internet to answer questions.",
30
+ config_schema=GoogleAgentConfig,
31
+ )
32
+ class GoogleAgent(ContextAgent, Agent[GoogleAgentConfig]):
33
+ __published_functions__: ClassVar[Dict[str, FunctionDefinition]] = {
34
+ "internet_search": FunctionDefinition(
35
+ name="internet_search",
36
+ description="Performs an internet search using Google Search to get context to answer questions.",
37
+ parameters={
38
+ "question": {
39
+ "type": "string",
40
+ "description": "The question to search for on the internet.",
41
+ },
42
+ },
43
+ )
44
+ }
45
+ google_search_tool: Optional[Tool] = None
46
+
47
+ async def inner_from_config(
48
+ self, config: GoogleAgentConfig, agent_id: Optional[str] = None
49
+ ):
50
+ await self.context_from_config(config)
51
+ self.google_search_tool = Tool(google_search=GoogleSearch())
52
+
53
+ def driver(self, manager: Manager) -> GoogleDriver:
54
+ return cast(GoogleDriver, manager.drivers[self.config.source])
55
+
56
+ async def internet_search(
57
+ self,
58
+ question: str,
59
+ memory: QuestionMemory,
60
+ manager: Manager,
61
+ question_uuid: Optional[str] = None,
62
+ flow_id: Optional[str] = None,
63
+ ) -> Context:
64
+ if question_uuid is None:
65
+ question_uuid = uuid4().hex
66
+
67
+ driver = self.driver(manager)
68
+ t0 = time()
69
+ assert self.google_search_tool is not None, "google_search_tool not initialized"
70
+ response = await driver.client.aio.models.generate_content(
71
+ model=self.config.gen_model_id,
72
+ contents=question,
73
+ config=GenerateContentConfig(
74
+ tools=[self.google_search_tool],
75
+ response_modalities=["TEXT"],
76
+ thinking_config=ThinkingConfig(include_thoughts=True),
77
+ ),
78
+ )
79
+ if (
80
+ response is not None
81
+ and response.usage_metadata is not None
82
+ and response.usage_metadata.total_token_count is not None
83
+ and response.usage_metadata.prompt_token_count is not None
84
+ ):
85
+ tokens = response.usage_metadata.total_token_count * 0.05
86
+ input_tokens = response.usage_metadata.prompt_token_count * 0.05
87
+ output_tokens = tokens - input_tokens
88
+ else:
89
+ input_tokens = 0.5
90
+ output_tokens = 0.5
91
+ context = Context(
92
+ agent_id=self.config.id or self.agent_id,
93
+ original_question_uuid=memory.original_question_uuid,
94
+ actual_question_uuid=question_uuid,
95
+ question=question,
96
+ source="google",
97
+ agent="google",
98
+ title=self.config.title
99
+ if self.config.title
100
+ else "Internet search with Google",
101
+ )
102
+ http_client = safe_http_client()
103
+ chunks: Dict[int, str] = {}
104
+ answer = ""
105
+ reasoning = ""
106
+ count = 0
107
+ if (
108
+ response
109
+ and response.candidates
110
+ and len(response.candidates) > 0
111
+ and response.candidates[0].content
112
+ and response.candidates[0].content.parts
113
+ ):
114
+ candidate = response.candidates[0]
115
+ for each in response.candidates[0].content.parts:
116
+ if each.thought is True and each.text is not None:
117
+ reasoning += each.text
118
+ elif each.text is not None:
119
+ answer += each.text
120
+ if (
121
+ candidate.grounding_metadata
122
+ and candidate.grounding_metadata.grounding_chunks
123
+ ):
124
+ for chunk_id, chunk in enumerate(
125
+ candidate.grounding_metadata.grounding_chunks
126
+ ):
127
+ if chunk.web is not None and chunk.web.uri is not None:
128
+ resp = await http_client.get(chunk.web.uri)
129
+ chunks[chunk_id] = resp.headers.get("location", "")
130
+
131
+ if (
132
+ candidate.grounding_metadata
133
+ and candidate.grounding_metadata.grounding_supports
134
+ ):
135
+ for ground in candidate.grounding_metadata.grounding_supports:
136
+ if ground.grounding_chunk_indices:
137
+ url = [
138
+ chunks[indice] for indice in ground.grounding_chunk_indices
139
+ ]
140
+ else:
141
+ url = []
142
+ if ground.segment and ground.segment.text:
143
+ text = ground.segment.text
144
+ else:
145
+ text = ""
146
+ count += 1
147
+ context.chunks.append(
148
+ Chunk(
149
+ chunk_id=uuid4().hex,
150
+ text=text,
151
+ labels=[],
152
+ url=url,
153
+ origin_agent=self.config.module,
154
+ )
155
+ )
156
+ # if candidate.grounding_metadata.web_search_queries:
157
+ # for query in candidate.grounding_metadata.web_search_queries:
158
+ # # Queries
159
+ await memory.add_answer(
160
+ answer,
161
+ module=self.config.module,
162
+ agent_path=f"/context/{self.config.id if self.config.id else 'default'}",
163
+ )
164
+ context.summary = answer
165
+ await memory.add_step(
166
+ step_module=self.config.module,
167
+ step_title=self.step_title("Search results"),
168
+ step_agent_path=f"/context/{self.config.id if self.config.id else 'default'}",
169
+ step_reason=reasoning,
170
+ step_value=f"{count} results found",
171
+ timeit=time() - t0,
172
+ input_nuclia_tokens=input_tokens,
173
+ output_nuclia_tokens=output_tokens,
174
+ )
175
+ return context
176
+
177
+ async def _get_question_context(
178
+ self,
179
+ memory: QuestionMemory,
180
+ manager: Manager,
181
+ question_uuid: str,
182
+ question: str,
183
+ flow_id: str,
184
+ extra_context: Optional[Dict[str, Any]] = None,
185
+ ) -> List[Tuple[str, str]]:
186
+ context = await self.internet_search(
187
+ question=question,
188
+ memory=memory,
189
+ manager=manager,
190
+ question_uuid=question_uuid,
191
+ )
192
+ missing = await self.save_ctx_and_return_missing(
193
+ context=context,
194
+ question=question,
195
+ memory=memory,
196
+ manager=manager,
197
+ flow_id=flow_id,
198
+ )
199
+ return [missing] if missing is not None else []
@@ -0,0 +1,38 @@
1
+ from typing import ClassVar, Literal, Optional, Tuple
2
+
3
+ from hyperforge.context.config import ContextAgentConfig
4
+ from hyperforge.driver import DriverConfig, EncryptedPayload
5
+ from hyperforge.utils import WidgetType
6
+ from pydantic import Field
7
+ from pydantic.config import ConfigDict
8
+
9
+
10
+ class GoogleInnerConfig(EncryptedPayload):
11
+ encrypted_fields: ClassVar[list[str]] = ["api_key", "credentials"]
12
+
13
+ api_key: Optional[str] = None
14
+ credentials: Optional[str] = None
15
+ vertexai: bool = False
16
+ project: Optional[str] = None
17
+ location: Optional[str] = None
18
+
19
+
20
+ class GoogleDriverConfig(DriverConfig[GoogleInnerConfig]):
21
+ model_config = ConfigDict(title="Google Gemini")
22
+ provider: Literal["google"] = "google"
23
+ config: GoogleInnerConfig
24
+
25
+
26
+ class GoogleAgentConfig(ContextAgentConfig):
27
+ model_config = ConfigDict(title="Google Gemini")
28
+ module: Literal["google"] = "google"
29
+ gen_model_id: str = "gemini-2.5-flash"
30
+ source: str = "google"
31
+ published_functions: Optional[Tuple[str, ...]] = Field(
32
+ default=("internet_search",),
33
+ title="Published functions",
34
+ description="List of functions published by this agent to be used by other agents in the chain",
35
+ json_schema_extra={
36
+ "widget": WidgetType.NOT_SHOWN,
37
+ },
38
+ )
@@ -0,0 +1,36 @@
1
+ from google.genai import Client
2
+ from hyperforge.configure import driver
3
+ from hyperforge.driver import Driver
4
+ from typing_extensions import Self
5
+
6
+ from hyperforge_google.config import GoogleDriverConfig
7
+
8
+
9
+ @driver(
10
+ id="google",
11
+ title="Google Driver",
12
+ description="Driver for interacting with the Google API.",
13
+ config_schema=GoogleDriverConfig,
14
+ )
15
+ class GoogleDriver(Driver):
16
+ client: Client
17
+
18
+ @classmethod
19
+ async def init(cls, driver: GoogleDriverConfig) -> Self:
20
+ creds = None
21
+ if driver.config.vertexai:
22
+ from google.auth import load_credentials_from_file
23
+
24
+ creds, _ = load_credentials_from_file(driver.config.credentials)
25
+ client = Client(
26
+ vertexai=driver.config.vertexai,
27
+ credentials=creds,
28
+ api_key=driver.config.api_key,
29
+ project=driver.config.project,
30
+ location=driver.config.location,
31
+ )
32
+ return cls(
33
+ client=client,
34
+ name=driver.name,
35
+ provider=driver.provider,
36
+ )
File without changes
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyperforge_google
3
+ Version: 1.0.0.post29
4
+ Summary: Google Search Hyperforge agent
5
+ Author-email: Nuclia <nucliadb@nuclia.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://progress.com
8
+ Project-URL: Repository, https://github.com/nuclia/hyperforge
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Python: <4,>=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: hyperforge
18
+ Requires-Dist: google-genai
19
+
20
+ # Google Hyperforge agents
@@ -0,0 +1,9 @@
1
+ hyperforge_google/__init__.py,sha256=UVK3qX2D5WR9YdIKZP1xiAI1Z2TSP4XxFQLxArR0skU,107
2
+ hyperforge_google/agent.py,sha256=akRUIqYCc52v7PMmfbUY85x6IYxDB2Q9wVHMSfqh2sA,7509
3
+ hyperforge_google/config.py,sha256=RKTN5kAJlkifzTfXdB-2nP9PAaas7vPzu5J8dxvH1Yc,1291
4
+ hyperforge_google/driver.py,sha256=BiqYSjGWwtfzwWsY97fDJkkOr4WB6Nvl7DJaUxpAMG0,1049
5
+ hyperforge_google/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ hyperforge_google-1.0.0.post29.dist-info/METADATA,sha256=e24YbJqvB2nmGadVYnQy1-94GOfAZWjjbm419vIvwi4,762
7
+ hyperforge_google-1.0.0.post29.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ hyperforge_google-1.0.0.post29.dist-info/top_level.txt,sha256=Yv1yQbtJuaWMZk2wOXZQmYzFIvwnV18vJC0AJQSS6L0,18
9
+ hyperforge_google-1.0.0.post29.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ hyperforge_google