veadk-python 0.1.0__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.

Potentially problematic release.


This version of veadk-python might be problematic. Click here for more details.

Files changed (110) hide show
  1. veadk/__init__.py +31 -0
  2. veadk/a2a/__init__.py +13 -0
  3. veadk/a2a/agent_card.py +45 -0
  4. veadk/a2a/remote_ve_agent.py +19 -0
  5. veadk/a2a/ve_a2a_server.py +77 -0
  6. veadk/a2a/ve_agent_executor.py +78 -0
  7. veadk/a2a/ve_task_store.py +37 -0
  8. veadk/agent.py +253 -0
  9. veadk/cli/__init__.py +13 -0
  10. veadk/cli/main.py +278 -0
  11. veadk/cli/services/agentpilot/__init__.py +17 -0
  12. veadk/cli/services/agentpilot/agentpilot.py +77 -0
  13. veadk/cli/services/veapig/__init__.py +17 -0
  14. veadk/cli/services/veapig/apig.py +224 -0
  15. veadk/cli/services/veapig/apig_utils.py +332 -0
  16. veadk/cli/services/vefaas/__init__.py +17 -0
  17. veadk/cli/services/vefaas/template/deploy.py +44 -0
  18. veadk/cli/services/vefaas/template/src/app.py +30 -0
  19. veadk/cli/services/vefaas/template/src/config.py +58 -0
  20. veadk/cli/services/vefaas/vefaas.py +346 -0
  21. veadk/cli/services/vefaas/vefaas_utils.py +408 -0
  22. veadk/cli/services/vetls/__init__.py +17 -0
  23. veadk/cli/services/vetls/vetls.py +87 -0
  24. veadk/cli/studio/__init__.py +13 -0
  25. veadk/cli/studio/agent_processor.py +247 -0
  26. veadk/cli/studio/fast_api.py +232 -0
  27. veadk/cli/studio/model.py +116 -0
  28. veadk/cloud/__init__.py +13 -0
  29. veadk/cloud/cloud_agent_engine.py +144 -0
  30. veadk/cloud/cloud_app.py +123 -0
  31. veadk/cloud/template/app.py +30 -0
  32. veadk/cloud/template/config.py +55 -0
  33. veadk/config.py +131 -0
  34. veadk/consts.py +17 -0
  35. veadk/database/__init__.py +17 -0
  36. veadk/database/base_database.py +45 -0
  37. veadk/database/database_factory.py +80 -0
  38. veadk/database/kv/__init__.py +13 -0
  39. veadk/database/kv/redis_database.py +109 -0
  40. veadk/database/local_database.py +43 -0
  41. veadk/database/relational/__init__.py +13 -0
  42. veadk/database/relational/mysql_database.py +114 -0
  43. veadk/database/vector/__init__.py +13 -0
  44. veadk/database/vector/opensearch_vector_database.py +205 -0
  45. veadk/database/vector/type.py +50 -0
  46. veadk/database/viking/__init__.py +13 -0
  47. veadk/database/viking/viking_database.py +378 -0
  48. veadk/database/viking/viking_memory_db.py +521 -0
  49. veadk/evaluation/__init__.py +17 -0
  50. veadk/evaluation/adk_evaluator/__init__.py +13 -0
  51. veadk/evaluation/adk_evaluator/adk_evaluator.py +291 -0
  52. veadk/evaluation/base_evaluator.py +242 -0
  53. veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
  54. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +223 -0
  55. veadk/evaluation/eval_set_file_loader.py +28 -0
  56. veadk/evaluation/eval_set_recorder.py +91 -0
  57. veadk/evaluation/utils/prometheus.py +142 -0
  58. veadk/knowledgebase/__init__.py +17 -0
  59. veadk/knowledgebase/knowledgebase.py +83 -0
  60. veadk/knowledgebase/knowledgebase_database_adapter.py +259 -0
  61. veadk/memory/__init__.py +13 -0
  62. veadk/memory/long_term_memory.py +119 -0
  63. veadk/memory/memory_database_adapter.py +235 -0
  64. veadk/memory/short_term_memory.py +124 -0
  65. veadk/memory/short_term_memory_processor.py +90 -0
  66. veadk/prompts/__init__.py +13 -0
  67. veadk/prompts/agent_default_prompt.py +30 -0
  68. veadk/prompts/prompt_evaluator.py +20 -0
  69. veadk/prompts/prompt_memory_processor.py +55 -0
  70. veadk/prompts/prompt_optimization.py +158 -0
  71. veadk/runner.py +252 -0
  72. veadk/tools/__init__.py +13 -0
  73. veadk/tools/builtin_tools/__init__.py +13 -0
  74. veadk/tools/builtin_tools/lark.py +67 -0
  75. veadk/tools/builtin_tools/las.py +23 -0
  76. veadk/tools/builtin_tools/vesearch.py +49 -0
  77. veadk/tools/builtin_tools/web_scraper.py +76 -0
  78. veadk/tools/builtin_tools/web_search.py +192 -0
  79. veadk/tools/demo_tools.py +58 -0
  80. veadk/tools/load_knowledgebase_tool.py +144 -0
  81. veadk/tools/sandbox/__init__.py +13 -0
  82. veadk/tools/sandbox/browser_sandbox.py +27 -0
  83. veadk/tools/sandbox/code_sandbox.py +30 -0
  84. veadk/tools/sandbox/computer_sandbox.py +27 -0
  85. veadk/tracing/__init__.py +13 -0
  86. veadk/tracing/base_tracer.py +172 -0
  87. veadk/tracing/telemetry/__init__.py +13 -0
  88. veadk/tracing/telemetry/exporters/__init__.py +13 -0
  89. veadk/tracing/telemetry/exporters/apiserver_exporter.py +60 -0
  90. veadk/tracing/telemetry/exporters/apmplus_exporter.py +101 -0
  91. veadk/tracing/telemetry/exporters/base_exporter.py +28 -0
  92. veadk/tracing/telemetry/exporters/cozeloop_exporter.py +69 -0
  93. veadk/tracing/telemetry/exporters/inmemory_exporter.py +88 -0
  94. veadk/tracing/telemetry/exporters/tls_exporter.py +78 -0
  95. veadk/tracing/telemetry/metrics/__init__.py +13 -0
  96. veadk/tracing/telemetry/metrics/opentelemetry_metrics.py +73 -0
  97. veadk/tracing/telemetry/opentelemetry_tracer.py +167 -0
  98. veadk/types.py +23 -0
  99. veadk/utils/__init__.py +13 -0
  100. veadk/utils/logger.py +59 -0
  101. veadk/utils/misc.py +33 -0
  102. veadk/utils/patches.py +85 -0
  103. veadk/utils/volcengine_sign.py +199 -0
  104. veadk/version.py +15 -0
  105. veadk_python-0.1.0.dist-info/METADATA +124 -0
  106. veadk_python-0.1.0.dist-info/RECORD +110 -0
  107. veadk_python-0.1.0.dist-info/WHEEL +5 -0
  108. veadk_python-0.1.0.dist-info/entry_points.txt +2 -0
  109. veadk_python-0.1.0.dist-info/licenses/LICENSE +201 -0
  110. veadk_python-0.1.0.dist-info/top_level.txt +1 -0
veadk/runner.py ADDED
@@ -0,0 +1,252 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Union
15
+
16
+ from google.adk.agents import RunConfig
17
+ from google.adk.agents.run_config import StreamingMode
18
+ from google.adk.runners import Runner as ADKRunner
19
+ from google.genai import types
20
+ from google.genai.types import Blob
21
+
22
+ from veadk.a2a.remote_ve_agent import RemoteVeAgent
23
+ from veadk.agent import Agent
24
+ from veadk.evaluation import EvalSetRecorder
25
+ from veadk.memory.short_term_memory import ShortTermMemory
26
+ from veadk.types import MediaMessage
27
+ from veadk.utils.logger import get_logger
28
+ from veadk.utils.misc import read_png_to_bytes
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ RunnerMessage = Union[
34
+ str, # single turn text-based prompt
35
+ list[str], # multiple turn text-based prompt
36
+ MediaMessage, # single turn prompt with media
37
+ list[MediaMessage], # multiple turn prompt with media
38
+ list[MediaMessage | str], # multiple turn prompt with media and text-based prompt
39
+ ]
40
+
41
+
42
+ class Runner:
43
+ def __init__(
44
+ self,
45
+ agent: Agent | RemoteVeAgent,
46
+ short_term_memory: ShortTermMemory,
47
+ app_name: str = "veadk_default_app",
48
+ user_id: str = "veadk_default_user",
49
+ ):
50
+ # basic settings
51
+ self.app_name = app_name
52
+ self.user_id = user_id
53
+
54
+ # agent settings
55
+ self.agent = agent
56
+
57
+ self.short_term_memory = short_term_memory
58
+ self.session_service = short_term_memory.session_service
59
+
60
+ if isinstance(self.agent, Agent):
61
+ self.long_term_memory = self.agent.long_term_memory
62
+ else:
63
+ self.long_term_memory = None
64
+
65
+ # maintain a in-memory runner for fast inference
66
+ self.runner = ADKRunner(
67
+ app_name=self.app_name,
68
+ agent=self.agent,
69
+ session_service=self.session_service,
70
+ memory_service=self.long_term_memory,
71
+ )
72
+
73
+ def _convert_messages(self, messages) -> list:
74
+ if isinstance(messages, str):
75
+ messages = [types.Content(role="user", parts=[types.Part(text=messages)])]
76
+ elif isinstance(messages, MediaMessage):
77
+ assert messages.media.endswith(".png"), (
78
+ "The MediaMessage only supports PNG format file for now."
79
+ )
80
+ messages = [
81
+ types.Content(
82
+ role="user",
83
+ parts=[
84
+ types.Part(text=messages.text),
85
+ types.Part(
86
+ inline_data=Blob(
87
+ display_name=messages.media,
88
+ data=read_png_to_bytes(messages.media),
89
+ mime_type="image/png",
90
+ )
91
+ ),
92
+ ],
93
+ )
94
+ ]
95
+ elif isinstance(messages, list):
96
+ converted_messages = []
97
+ for message in messages:
98
+ converted_messages.extend(self._convert_messages(message))
99
+ messages = converted_messages
100
+ else:
101
+ raise ValueError(f"Unknown message type: {type(messages)}")
102
+
103
+ return messages
104
+
105
+ async def _run(
106
+ self,
107
+ session_id: str,
108
+ message: types.Content,
109
+ stream: bool = False,
110
+ ):
111
+ stream_mode = StreamingMode.SSE if stream else StreamingMode.NONE
112
+
113
+ async def event_generator():
114
+ async for event in self.runner.run_async(
115
+ user_id=self.user_id,
116
+ session_id=session_id,
117
+ new_message=message,
118
+ run_config=RunConfig(streaming_mode=stream_mode),
119
+ ):
120
+ if event.get_function_calls():
121
+ for function_call in event.get_function_calls():
122
+ logger.debug(f"Function call: {function_call}")
123
+ elif (
124
+ event.content is not None
125
+ and event.content.parts[0].text is not None
126
+ and len(event.content.parts[0].text.strip()) > 0
127
+ ):
128
+ yield event.content.parts[0].text
129
+
130
+ final_output = ""
131
+ async for chunk in event_generator():
132
+ if stream:
133
+ print(chunk, end="", flush=True)
134
+ final_output += chunk
135
+ if stream:
136
+ print() # end with a new line
137
+
138
+ return final_output
139
+
140
+ async def run(
141
+ self,
142
+ messages: RunnerMessage,
143
+ session_id: str,
144
+ stream: bool = False,
145
+ ):
146
+ messages: list = self._convert_messages(messages)
147
+
148
+ await self.short_term_memory.create_session(
149
+ app_name=self.app_name, user_id=self.user_id, session_id=session_id
150
+ )
151
+
152
+ logger.info("Begin to process user messages.")
153
+
154
+ final_output = ""
155
+ for message in messages:
156
+ final_output = await self._run(session_id, message, stream)
157
+
158
+ # try to save tracing file
159
+ if isinstance(self.agent, Agent):
160
+ self.save_tracing_file(session_id)
161
+
162
+ return final_output
163
+
164
+ def save_tracing_file(self, session_id: str) -> str:
165
+ if not self.agent.tracers:
166
+ return
167
+
168
+ try:
169
+ dump_path = ""
170
+ for tracer in self.agent.tracers:
171
+ dump_path = tracer.dump(self.user_id, session_id)
172
+
173
+ return dump_path
174
+ except Exception as e:
175
+ logger.error(f"Failed to save tracing file: {e}")
176
+ return ""
177
+
178
+ async def save_eval_set(self, session_id: str, eval_set_id: str = "default") -> str:
179
+ eval_set_recorder = EvalSetRecorder(self.session_service, eval_set_id)
180
+ eval_set_path = await eval_set_recorder.dump(
181
+ self.app_name, self.user_id, session_id
182
+ )
183
+ return eval_set_path
184
+
185
+ async def save_session_to_long_term_memory(self, session_id: str) -> None:
186
+ if not self.long_term_memory:
187
+ logger.warning("Long-term memory is not enabled. Failed to save session.")
188
+ return
189
+
190
+ session = await self.session_service.get_session(
191
+ app_name=self.app_name,
192
+ user_id=self.user_id,
193
+ session_id=session_id,
194
+ )
195
+ await self.long_term_memory.add_session_to_memory(session)
196
+ logger.info(f"Add session `{session.id}` to long-term memory.")
197
+
198
+ async def run_with_final_event(
199
+ self,
200
+ messages: RunnerMessage,
201
+ session_id: str,
202
+ ):
203
+ """non-streaming run with final event"""
204
+ messages: list = self._convert_messages(messages)
205
+
206
+ await self.short_term_memory.create_session(
207
+ app_name=self.app_name, user_id=self.user_id, session_id=session_id
208
+ )
209
+
210
+ logger.info("Begin to process user messages.")
211
+
212
+ final_event = ""
213
+ async for event in self.runner.run_async(
214
+ user_id=self.user_id, session_id=session_id, new_message=messages[0]
215
+ ):
216
+ if event.get_function_calls():
217
+ for function_call in event.get_function_calls():
218
+ logger.debug(f"Function call: {function_call}")
219
+ elif (
220
+ not event.partial
221
+ and event.content.parts[0].text is not None
222
+ and len(event.content.parts[0].text.strip()) > 0
223
+ ):
224
+ final_event = event.model_dump_json(exclude_none=True, by_alias=True)
225
+
226
+ return final_event
227
+
228
+ async def run_sse(
229
+ self,
230
+ session_id: str,
231
+ prompt: str,
232
+ ):
233
+ message = types.Content(role="user", parts=[types.Part(text=prompt)])
234
+
235
+ await self.short_term_memory.create_session(
236
+ app_name=self.app_name, user_id=self.user_id, session_id=session_id
237
+ )
238
+
239
+ logger.info("Begin to process user messages under SSE method.")
240
+
241
+ async for event in self.runner.run_async(
242
+ user_id=self.user_id,
243
+ session_id=session_id,
244
+ new_message=message,
245
+ run_config=RunConfig(streaming_mode=StreamingMode.SSE),
246
+ ):
247
+ # Format as SSE data
248
+ sse_event = event.model_dump_json(exclude_none=True, by_alias=True)
249
+ if event.get_function_calls():
250
+ for function_call in event.get_function_calls():
251
+ logger.debug(f"SSE function call event: {sse_event}")
252
+ yield f"data: {sse_event}\n\n"
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,67 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import subprocess
16
+
17
+ from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioServerParameters
18
+
19
+ from veadk.config import getenv
20
+ from veadk.utils.logger import get_logger
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ def check_env():
26
+ try:
27
+ result = subprocess.run(
28
+ ["npx", "-v"], capture_output=True, text=True, check=True
29
+ )
30
+ version = result.stdout.strip()
31
+ logger.info(f"Check `npx` command done, version: {version}")
32
+ except Exception as e:
33
+ raise Exception(
34
+ "Check `npx` command failed. Please install `npx` command manually."
35
+ ) from e
36
+
37
+ try:
38
+ result = subprocess.run(
39
+ ["lark-mcp", "-V"], capture_output=True, text=True, check=True
40
+ )
41
+ version = result.stdout.strip()
42
+ logger.info(f"Check `lark-mcp` command done, version: {version}")
43
+ except Exception as e:
44
+ raise Exception(
45
+ "Check `lark-mcp` command failed. Please install it manually."
46
+ ) from e
47
+
48
+
49
+ check_env()
50
+
51
+ lark_tools = MCPToolset(
52
+ connection_params=StdioServerParameters(
53
+ command="npx",
54
+ args=[
55
+ "-y",
56
+ "@larksuiteoapi/lark-mcp",
57
+ "mcp",
58
+ "-a",
59
+ getenv("TOOL_LARK_ENDPOINT"),
60
+ "-s",
61
+ getenv("TOOL_LARK_API_KEY"),
62
+ "-u",
63
+ getenv("TOOL_LARK_TOKEN"),
64
+ ],
65
+ errlog=None,
66
+ ),
67
+ )
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, SseConnectionParams
16
+
17
+ from veadk.config import getenv
18
+
19
+ url = getenv("TOOL_LAS_URL")
20
+ dataset_id = getenv("TOOL_LAS_DATASET_ID")
21
+
22
+
23
+ las = MCPToolset(connection_params=SseConnectionParams(url=url))
@@ -0,0 +1,49 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import requests
16
+
17
+ from veadk.config import getenv
18
+
19
+
20
+ def vesearch(query: str) -> str:
21
+ """Search information from Internet, social media, news sites, etc.
22
+
23
+ Args:
24
+ query: The query string to search.
25
+
26
+ Returns:
27
+ Summarized search results.
28
+ """
29
+ api_key = getenv("TOOL_VESEARCH_API_KEY")
30
+ bot_id = str(getenv("TOOL_VESEARCH_ENDPOINT"))
31
+
32
+ if api_key == "":
33
+ return "Invoke `vesearch` failed. Please set VESEARCH_API_KEY as your environment variable."
34
+ if bot_id == "":
35
+ return "Invoke `vesearch` failed. Please set VESEARCH_BOT_ID as your environment variable."
36
+
37
+ URL = "https://open.feedcoopapi.com/agent_api/agent/chat/completion"
38
+ headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
39
+ data = {
40
+ "bot_id": bot_id,
41
+ "messages": [{"role": "user", "content": query}],
42
+ }
43
+
44
+ response = requests.post(URL, json=data, headers=headers)
45
+ if response.status_code == 200:
46
+ result = response.json()
47
+ return result["choices"][0]["message"]["content"]
48
+ else:
49
+ return response.text
@@ -0,0 +1,76 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Any
16
+
17
+ import requests
18
+
19
+ from veadk.config import getenv
20
+
21
+
22
+ def web_scraper(query: str) -> dict[str, Any]:
23
+ """query a single keyword from some search engineer
24
+
25
+ Args:
26
+ query (str, optional): query keyword. Defaults to None.
27
+
28
+ Returns:
29
+ dict[str, Any]: search results
30
+ """
31
+
32
+ endpoint = getenv("TOOL_WEB_SCRAPER_ENDPOINT")
33
+ token = getenv("TOOL_WEB_SCRAPER_API_KEY")
34
+
35
+ try:
36
+ url = f"https://{endpoint}/v1/queries"
37
+ headers = {
38
+ "Content-Type": "application/json",
39
+ "X-VE-Source": "google_search",
40
+ "X-VE-API-Key": token,
41
+ }
42
+ data = {
43
+ "query": query,
44
+ "source": "google_search",
45
+ "parse": True,
46
+ "limit": "10",
47
+ "start_page": "1",
48
+ "pages": "1",
49
+ "context": [
50
+ {"key": "nfpr", "value": True},
51
+ {"key": "safe_search", "value": False},
52
+ {"key": "filter", "value": 1},
53
+ ],
54
+ }
55
+ response = requests.post(url, headers=headers, json=data, verify=False)
56
+
57
+ response.raise_for_status()
58
+
59
+ response_json = response.json() if response.content else None
60
+ results_dict = (
61
+ response_json.get("results")[0].get("content").get("results").get("organic")
62
+ )
63
+
64
+ results_str = ""
65
+ for r in results_dict:
66
+ dic = {
67
+ "url": r.get("url"),
68
+ "title": r.get("title"),
69
+ "description": r.get("desc"),
70
+ }
71
+ results_str += str(dic)
72
+ return results_str
73
+
74
+ except requests.exceptions.RequestException as e:
75
+ error_message = f"Error: {str(e)}"
76
+ raise ValueError(error_message)
@@ -0,0 +1,192 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ The document of this tool see: https://www.volcengine.com/docs/85508/1650263
17
+ """
18
+
19
+ import datetime
20
+ import hashlib
21
+ import hmac
22
+ import json
23
+ from urllib.parse import quote
24
+
25
+ import requests
26
+
27
+ from veadk.config import getenv
28
+ from veadk.utils.logger import get_logger
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ Service = "volc_torchlight_api"
34
+ Version = "2025-01-01"
35
+ Region = "cn-beijing"
36
+ Host = "mercury.volcengineapi.com"
37
+ ContentType = "application/json"
38
+
39
+
40
+ def norm_query(params):
41
+ query = ""
42
+ for key in sorted(params.keys()):
43
+ if isinstance(params[key], list):
44
+ for k in params[key]:
45
+ query = (
46
+ query + quote(key, safe="-_.~") + "=" + quote(k, safe="-_.~") + "&"
47
+ )
48
+ else:
49
+ query = (
50
+ query
51
+ + quote(key, safe="-_.~")
52
+ + "="
53
+ + quote(params[key], safe="-_.~")
54
+ + "&"
55
+ )
56
+ query = query[:-1]
57
+ return query.replace("+", "%20")
58
+
59
+
60
+ def hmac_sha256(key: bytes, content: str):
61
+ return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
62
+
63
+
64
+ def hash_sha256(content: str):
65
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()
66
+
67
+
68
+ def request(method, date, query, header, ak, sk, action, body):
69
+ credential = {
70
+ "access_key_id": ak,
71
+ "secret_access_key": sk,
72
+ "service": Service,
73
+ "region": Region,
74
+ }
75
+ request_param = {
76
+ "body": body,
77
+ "host": Host,
78
+ "path": "/",
79
+ "method": method,
80
+ "content_type": ContentType,
81
+ "date": date,
82
+ "query": {"Action": action, "Version": Version, **query},
83
+ }
84
+ if body is None:
85
+ request_param["body"] = ""
86
+ # 第四步:接下来开始计算签名。在计算签名前,先准备好用于接收签算结果的 signResult 变量,并设置一些参数。
87
+ # 初始化签名结果的结构体
88
+ x_date = request_param["date"].strftime("%Y%m%dT%H%M%SZ")
89
+ short_x_date = x_date[:8]
90
+ x_content_sha256 = hash_sha256(request_param["body"])
91
+ sign_result = {
92
+ "Host": request_param["host"],
93
+ "X-Content-Sha256": x_content_sha256,
94
+ "X-Date": x_date,
95
+ "Content-Type": request_param["content_type"],
96
+ }
97
+ signed_headers_str = ";".join(
98
+ ["content-type", "host", "x-content-sha256", "x-date"]
99
+ )
100
+ # signed_headers_str = signed_headers_str + ";x-security-token"
101
+ canonical_request_str = "\n".join(
102
+ [
103
+ request_param["method"].upper(),
104
+ request_param["path"],
105
+ norm_query(request_param["query"]),
106
+ "\n".join(
107
+ [
108
+ "content-type:" + request_param["content_type"],
109
+ "host:" + request_param["host"],
110
+ "x-content-sha256:" + x_content_sha256,
111
+ "x-date:" + x_date,
112
+ ]
113
+ ),
114
+ "",
115
+ signed_headers_str,
116
+ x_content_sha256,
117
+ ]
118
+ )
119
+
120
+ hashed_canonical_request = hash_sha256(canonical_request_str)
121
+
122
+ credential_scope = "/".join(
123
+ [short_x_date, credential["region"], credential["service"], "request"]
124
+ )
125
+ string_to_sign = "\n".join(
126
+ ["HMAC-SHA256", x_date, credential_scope, hashed_canonical_request]
127
+ )
128
+
129
+ k_date = hmac_sha256(credential["secret_access_key"].encode("utf-8"), short_x_date)
130
+ k_region = hmac_sha256(k_date, credential["region"])
131
+ k_service = hmac_sha256(k_region, credential["service"])
132
+ k_signing = hmac_sha256(k_service, "request")
133
+ signature = hmac_sha256(k_signing, string_to_sign).hex()
134
+
135
+ sign_result["Authorization"] = (
136
+ "HMAC-SHA256 Credential={}, SignedHeaders={}, Signature={}".format(
137
+ credential["access_key_id"] + "/" + credential_scope,
138
+ signed_headers_str,
139
+ signature,
140
+ )
141
+ )
142
+ header = {**header, **sign_result}
143
+ # header = {**header, **{"X-Security-Token": SessionToken}}
144
+ r = requests.request(
145
+ method=method,
146
+ url="https://{}{}".format(request_param["host"], request_param["path"]),
147
+ headers=header,
148
+ params=request_param["query"],
149
+ data=request_param["body"],
150
+ )
151
+ return r.json()
152
+
153
+
154
+ def web_search(query: str) -> list[str]:
155
+ """Search a query in websites.
156
+
157
+ Args:
158
+ query: The query to search.
159
+
160
+ Returns:
161
+ A list of result documents.
162
+ """
163
+ req = {
164
+ "Query": query,
165
+ "SearchType": "web",
166
+ "Count": 5,
167
+ "NeedSummary": True,
168
+ }
169
+ ak = getenv("VOLCENGINE_ACCESS_KEY")
170
+ sk = getenv("VOLCENGINE_SECRET_KEY")
171
+
172
+ now = datetime.datetime.utcnow()
173
+ response_body = request(
174
+ "POST",
175
+ now,
176
+ {},
177
+ {},
178
+ ak,
179
+ sk,
180
+ "WebSearch",
181
+ json.dumps(req),
182
+ )
183
+ try:
184
+ results: list = response_body["Result"]["WebResults"]
185
+ except Exception as e:
186
+ logger.error(f"Web search failed: {e}")
187
+ return []
188
+
189
+ final_results = []
190
+ for result in results:
191
+ final_results.append(result["Summary"].strip())
192
+ return final_results