veadk-python 0.2.27__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.
- veadk/__init__.py +37 -0
- veadk/a2a/__init__.py +13 -0
- veadk/a2a/agent_card.py +45 -0
- veadk/a2a/remote_ve_agent.py +390 -0
- veadk/a2a/utils/__init__.py +13 -0
- veadk/a2a/utils/agent_to_a2a.py +170 -0
- veadk/a2a/ve_a2a_server.py +93 -0
- veadk/a2a/ve_agent_executor.py +78 -0
- veadk/a2a/ve_middlewares.py +313 -0
- veadk/a2a/ve_task_store.py +37 -0
- veadk/agent.py +402 -0
- veadk/agent_builder.py +93 -0
- veadk/agents/loop_agent.py +68 -0
- veadk/agents/parallel_agent.py +72 -0
- veadk/agents/sequential_agent.py +64 -0
- veadk/auth/__init__.py +13 -0
- veadk/auth/base_auth.py +22 -0
- veadk/auth/ve_credential_service.py +203 -0
- veadk/auth/veauth/__init__.py +13 -0
- veadk/auth/veauth/apmplus_veauth.py +58 -0
- veadk/auth/veauth/ark_veauth.py +75 -0
- veadk/auth/veauth/base_veauth.py +50 -0
- veadk/auth/veauth/cozeloop_veauth.py +13 -0
- veadk/auth/veauth/opensearch_veauth.py +75 -0
- veadk/auth/veauth/postgresql_veauth.py +75 -0
- veadk/auth/veauth/prompt_pilot_veauth.py +60 -0
- veadk/auth/veauth/speech_veauth.py +54 -0
- veadk/auth/veauth/utils.py +69 -0
- veadk/auth/veauth/vesearch_veauth.py +62 -0
- veadk/auth/veauth/viking_mem0_veauth.py +91 -0
- veadk/cli/__init__.py +13 -0
- veadk/cli/cli.py +58 -0
- veadk/cli/cli_clean.py +87 -0
- veadk/cli/cli_create.py +163 -0
- veadk/cli/cli_deploy.py +233 -0
- veadk/cli/cli_eval.py +215 -0
- veadk/cli/cli_init.py +214 -0
- veadk/cli/cli_kb.py +110 -0
- veadk/cli/cli_pipeline.py +285 -0
- veadk/cli/cli_prompt.py +86 -0
- veadk/cli/cli_update.py +106 -0
- veadk/cli/cli_uploadevalset.py +139 -0
- veadk/cli/cli_web.py +143 -0
- veadk/cloud/__init__.py +13 -0
- veadk/cloud/cloud_agent_engine.py +485 -0
- veadk/cloud/cloud_app.py +475 -0
- veadk/config.py +115 -0
- veadk/configs/__init__.py +13 -0
- veadk/configs/auth_configs.py +133 -0
- veadk/configs/database_configs.py +132 -0
- veadk/configs/model_configs.py +78 -0
- veadk/configs/tool_configs.py +54 -0
- veadk/configs/tracing_configs.py +110 -0
- veadk/consts.py +74 -0
- veadk/evaluation/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/adk_evaluator.py +302 -0
- veadk/evaluation/base_evaluator.py +642 -0
- veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
- veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +339 -0
- veadk/evaluation/eval_set_file_loader.py +48 -0
- veadk/evaluation/eval_set_recorder.py +146 -0
- veadk/evaluation/types.py +65 -0
- veadk/evaluation/utils/prometheus.py +196 -0
- veadk/integrations/__init__.py +13 -0
- veadk/integrations/ve_apig/__init__.py +13 -0
- veadk/integrations/ve_apig/ve_apig.py +349 -0
- veadk/integrations/ve_apig/ve_apig_utils.py +332 -0
- veadk/integrations/ve_code_pipeline/__init__.py +13 -0
- veadk/integrations/ve_code_pipeline/ve_code_pipeline.py +431 -0
- veadk/integrations/ve_cozeloop/__init__.py +13 -0
- veadk/integrations/ve_cozeloop/ve_cozeloop.py +96 -0
- veadk/integrations/ve_cr/__init__.py +13 -0
- veadk/integrations/ve_cr/ve_cr.py +220 -0
- veadk/integrations/ve_faas/__init__.py +13 -0
- veadk/integrations/ve_faas/template/cookiecutter.json +15 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/config.yaml.example +6 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/deploy.py +106 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/agent.py +25 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +202 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/requirements.txt +3 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +49 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/__init__.py +14 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/agent.py +27 -0
- veadk/integrations/ve_faas/ve_faas.py +754 -0
- veadk/integrations/ve_faas/ve_faas_utils.py +408 -0
- veadk/integrations/ve_faas/web_template/cookiecutter.json +20 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example +2 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py +44 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py +123 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py +46 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py +36 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt +4 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css +368 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js +0 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html +24 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html +53 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html +45 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html +29 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html +14 -0
- veadk/integrations/ve_identity/__init__.py +110 -0
- veadk/integrations/ve_identity/auth_config.py +261 -0
- veadk/integrations/ve_identity/auth_mixins.py +650 -0
- veadk/integrations/ve_identity/auth_processor.py +385 -0
- veadk/integrations/ve_identity/function_tool.py +158 -0
- veadk/integrations/ve_identity/identity_client.py +864 -0
- veadk/integrations/ve_identity/mcp_tool.py +181 -0
- veadk/integrations/ve_identity/mcp_toolset.py +431 -0
- veadk/integrations/ve_identity/models.py +228 -0
- veadk/integrations/ve_identity/token_manager.py +188 -0
- veadk/integrations/ve_identity/utils.py +151 -0
- veadk/integrations/ve_prompt_pilot/__init__.py +13 -0
- veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +85 -0
- veadk/integrations/ve_tls/__init__.py +13 -0
- veadk/integrations/ve_tls/utils.py +116 -0
- veadk/integrations/ve_tls/ve_tls.py +212 -0
- veadk/integrations/ve_tos/ve_tos.py +710 -0
- veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
- veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +308 -0
- veadk/knowledgebase/__init__.py +17 -0
- veadk/knowledgebase/backends/__init__.py +13 -0
- veadk/knowledgebase/backends/base_backend.py +72 -0
- veadk/knowledgebase/backends/in_memory_backend.py +91 -0
- veadk/knowledgebase/backends/opensearch_backend.py +162 -0
- veadk/knowledgebase/backends/redis_backend.py +172 -0
- veadk/knowledgebase/backends/utils.py +92 -0
- veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +608 -0
- veadk/knowledgebase/entry.py +25 -0
- veadk/knowledgebase/knowledgebase.py +307 -0
- veadk/memory/__init__.py +35 -0
- veadk/memory/long_term_memory.py +365 -0
- veadk/memory/long_term_memory_backends/__init__.py +13 -0
- veadk/memory/long_term_memory_backends/base_backend.py +35 -0
- veadk/memory/long_term_memory_backends/in_memory_backend.py +67 -0
- veadk/memory/long_term_memory_backends/mem0_backend.py +155 -0
- veadk/memory/long_term_memory_backends/opensearch_backend.py +124 -0
- veadk/memory/long_term_memory_backends/redis_backend.py +140 -0
- veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +189 -0
- veadk/memory/short_term_memory.py +252 -0
- veadk/memory/short_term_memory_backends/__init__.py +13 -0
- veadk/memory/short_term_memory_backends/base_backend.py +31 -0
- veadk/memory/short_term_memory_backends/mysql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/postgresql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/sqlite_backend.py +55 -0
- veadk/memory/short_term_memory_processor.py +100 -0
- veadk/processors/__init__.py +26 -0
- veadk/processors/base_run_processor.py +120 -0
- veadk/prompts/__init__.py +13 -0
- veadk/prompts/agent_default_prompt.py +30 -0
- veadk/prompts/prompt_evaluator.py +20 -0
- veadk/prompts/prompt_memory_processor.py +55 -0
- veadk/prompts/prompt_optimization.py +150 -0
- veadk/runner.py +732 -0
- veadk/tools/__init__.py +13 -0
- veadk/tools/builtin_tools/__init__.py +13 -0
- veadk/tools/builtin_tools/agent_authorization.py +94 -0
- veadk/tools/builtin_tools/generate_image.py +23 -0
- veadk/tools/builtin_tools/image_edit.py +300 -0
- veadk/tools/builtin_tools/image_generate.py +446 -0
- veadk/tools/builtin_tools/lark.py +67 -0
- veadk/tools/builtin_tools/las.py +24 -0
- veadk/tools/builtin_tools/link_reader.py +66 -0
- veadk/tools/builtin_tools/llm_shield.py +381 -0
- veadk/tools/builtin_tools/load_knowledgebase.py +97 -0
- veadk/tools/builtin_tools/mcp_router.py +29 -0
- veadk/tools/builtin_tools/run_code.py +113 -0
- veadk/tools/builtin_tools/tts.py +253 -0
- veadk/tools/builtin_tools/vesearch.py +49 -0
- veadk/tools/builtin_tools/video_generate.py +363 -0
- veadk/tools/builtin_tools/web_scraper.py +76 -0
- veadk/tools/builtin_tools/web_search.py +83 -0
- veadk/tools/demo_tools.py +58 -0
- veadk/tools/load_knowledgebase_tool.py +149 -0
- veadk/tools/sandbox/__init__.py +13 -0
- veadk/tools/sandbox/browser_sandbox.py +37 -0
- veadk/tools/sandbox/code_sandbox.py +40 -0
- veadk/tools/sandbox/computer_sandbox.py +34 -0
- veadk/tracing/__init__.py +13 -0
- veadk/tracing/base_tracer.py +58 -0
- veadk/tracing/telemetry/__init__.py +13 -0
- veadk/tracing/telemetry/attributes/attributes.py +29 -0
- veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +180 -0
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +858 -0
- veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +152 -0
- veadk/tracing/telemetry/attributes/extractors/types.py +164 -0
- veadk/tracing/telemetry/exporters/__init__.py +13 -0
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +558 -0
- veadk/tracing/telemetry/exporters/base_exporter.py +39 -0
- veadk/tracing/telemetry/exporters/cozeloop_exporter.py +129 -0
- veadk/tracing/telemetry/exporters/inmemory_exporter.py +248 -0
- veadk/tracing/telemetry/exporters/tls_exporter.py +139 -0
- veadk/tracing/telemetry/opentelemetry_tracer.py +320 -0
- veadk/tracing/telemetry/telemetry.py +411 -0
- veadk/types.py +47 -0
- veadk/utils/__init__.py +13 -0
- veadk/utils/audio_manager.py +95 -0
- veadk/utils/auth.py +294 -0
- veadk/utils/logger.py +59 -0
- veadk/utils/mcp_utils.py +44 -0
- veadk/utils/misc.py +184 -0
- veadk/utils/patches.py +101 -0
- veadk/utils/volcengine_sign.py +205 -0
- veadk/version.py +15 -0
- veadk_python-0.2.27.dist-info/METADATA +373 -0
- veadk_python-0.2.27.dist-info/RECORD +218 -0
- veadk_python-0.2.27.dist-info/WHEEL +5 -0
- veadk_python-0.2.27.dist-info/entry_points.txt +2 -0
- veadk_python-0.2.27.dist-info/licenses/LICENSE +201 -0
- veadk_python-0.2.27.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,381 @@
|
|
|
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
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import requests
|
|
17
|
+
from typing import Optional, List, Dict, Any, Union
|
|
18
|
+
from volcenginesdkllmshield.models.llm_shield_sign import request_sign
|
|
19
|
+
|
|
20
|
+
from google.adk.plugins import BasePlugin
|
|
21
|
+
from google.adk.agents.callback_context import CallbackContext
|
|
22
|
+
from google.adk.tools.tool_context import ToolContext
|
|
23
|
+
from google.adk.models import LlmRequest, LlmResponse
|
|
24
|
+
from google.genai import types
|
|
25
|
+
from google.adk.tools.base_tool import BaseTool
|
|
26
|
+
|
|
27
|
+
from veadk.config import getenv
|
|
28
|
+
from veadk.utils.logger import get_logger
|
|
29
|
+
from veadk.auth.veauth.utils import get_credential_from_vefaas_iam
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LLMShieldPlugin(BasePlugin):
|
|
35
|
+
"""
|
|
36
|
+
LLM Shield Plugin for content moderation and safety.
|
|
37
|
+
|
|
38
|
+
This plugin integrates with Volcano Engine's LLM Shield service to provide
|
|
39
|
+
real-time content moderation for user inputs, model outputs, and tool interactions.
|
|
40
|
+
It helps detect and block potentially harmful content including sensitive information,
|
|
41
|
+
prompt injection attacks, and policy violations.
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
Basic usage with default settings:
|
|
45
|
+
```python
|
|
46
|
+
from veadk.tools.builtin_tools.llm_shield import content_safety
|
|
47
|
+
agent = Agent(
|
|
48
|
+
before_model_callback=content_safety.before_model_callback,
|
|
49
|
+
after_model_callback=content_safety.after_model_callback,
|
|
50
|
+
before_tool_callback=content_safety.before_tool_callback,
|
|
51
|
+
after_tool_callback=content_safety.after_tool_callback,
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, region: str = "cn-beijing", timeout: int = 50) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Initialize the LLM Shield Plugin.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
region (str, optional): The service region. Defaults to "cn-beijing".
|
|
62
|
+
timeout (int, optional): Request timeout in seconds. Defaults to 50.
|
|
63
|
+
"""
|
|
64
|
+
self.name = "LLMShieldPlugin"
|
|
65
|
+
super().__init__(name=self.name)
|
|
66
|
+
|
|
67
|
+
self.appid = getenv("TOOL_LLM_SHIELD_APP_ID")
|
|
68
|
+
self.region = region
|
|
69
|
+
self.timeout = timeout
|
|
70
|
+
|
|
71
|
+
self.category_map = {
|
|
72
|
+
101: "Model Misuse",
|
|
73
|
+
103: "Sensitive Information",
|
|
74
|
+
104: "Prompt Injection",
|
|
75
|
+
106: "General Topic Control",
|
|
76
|
+
107: "Computational Resource Consumption",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def _request_llm_shield(self, message: str, role: str) -> Optional[str]:
|
|
80
|
+
"""
|
|
81
|
+
Make a request to the LLM Shield service for content moderation.
|
|
82
|
+
|
|
83
|
+
This method sends a message to the LLM Shield API for security analysis.
|
|
84
|
+
If the content is deemed risky, it returns a blocking message explaining
|
|
85
|
+
the violation. Otherwise, it returns None to allow the content through.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
message (str): The content to be moderated
|
|
89
|
+
role (str): The role of the message sender ("user" or "assistant")
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Optional[str]: A blocking message if content violates policies,
|
|
93
|
+
None if content is safe or on error
|
|
94
|
+
"""
|
|
95
|
+
if not self.appid:
|
|
96
|
+
logger.error("LLM Shield app ID not configured")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
ak = os.getenv("VOLCENGINE_ACCESS_KEY")
|
|
100
|
+
sk = os.getenv("VOLCENGINE_SECRET_KEY")
|
|
101
|
+
session_token = ""
|
|
102
|
+
if not (ak and sk):
|
|
103
|
+
logger.debug("Get AK/SK from environment variables failed.")
|
|
104
|
+
credential = get_credential_from_vefaas_iam()
|
|
105
|
+
ak = credential.access_key_id
|
|
106
|
+
sk = credential.secret_access_key
|
|
107
|
+
session_token = credential.session_token
|
|
108
|
+
else:
|
|
109
|
+
logger.debug("Successfully get AK/SK from environment variables.")
|
|
110
|
+
|
|
111
|
+
body = {
|
|
112
|
+
"Message": {
|
|
113
|
+
"Role": role,
|
|
114
|
+
"Content": message,
|
|
115
|
+
"ContentType": 1,
|
|
116
|
+
},
|
|
117
|
+
"Scene": self.appid,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
body_json = json.dumps(body).encode("utf-8")
|
|
121
|
+
|
|
122
|
+
header = {"X-Security-Token": session_token}
|
|
123
|
+
url = f"https://{self.region}.sdk.access.llm-shield.omini-shield.com"
|
|
124
|
+
path = "/v2/moderate"
|
|
125
|
+
action = "Moderate"
|
|
126
|
+
version = "2025-08-31"
|
|
127
|
+
|
|
128
|
+
signed_header = request_sign(
|
|
129
|
+
header, ak, sk, self.region, url, path, action, body_json
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
signed_header.update(
|
|
133
|
+
{
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
"X-Top-Service": "llmshield",
|
|
136
|
+
"X-Top-Region": self.region,
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
response = requests.post(
|
|
142
|
+
url + path,
|
|
143
|
+
headers=signed_header,
|
|
144
|
+
data=body_json,
|
|
145
|
+
params={"Action": action, "Version": version},
|
|
146
|
+
timeout=self.timeout,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if response.status_code != 200:
|
|
150
|
+
logger.error(
|
|
151
|
+
f"LLM Shield HTTP error: {response.status_code} - {response.text}"
|
|
152
|
+
)
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
response = response.json()
|
|
156
|
+
except requests.exceptions.Timeout:
|
|
157
|
+
logger.error("LLM Shield request timeout")
|
|
158
|
+
return None
|
|
159
|
+
except requests.exceptions.RequestException as e:
|
|
160
|
+
logger.error(f"LLM Shield network request failed: {e}")
|
|
161
|
+
return None
|
|
162
|
+
except json.JSONDecodeError as e:
|
|
163
|
+
logger.error(f"LLM Shield response JSON decode failed: {e}")
|
|
164
|
+
return None
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(f"LLM Shield request failed: {e}")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
# Process risk detection results
|
|
170
|
+
result = response.get("Result", None)
|
|
171
|
+
if result:
|
|
172
|
+
decision = result.get("Decision", None)
|
|
173
|
+
decision_type = decision.get("DecisionType", None)
|
|
174
|
+
risk_info = result.get("RiskInfo", None)
|
|
175
|
+
if decision_type is not None and int(decision_type) == 2 and risk_info:
|
|
176
|
+
risks = risk_info.get("Risks", [])
|
|
177
|
+
if risks:
|
|
178
|
+
# Extract risk categories for user-friendly error message
|
|
179
|
+
risk_reasons = set()
|
|
180
|
+
for risk in risks:
|
|
181
|
+
category = risk.get("Category", None)
|
|
182
|
+
if category:
|
|
183
|
+
category_name = self.category_map.get(
|
|
184
|
+
int(category), f"Category {category}"
|
|
185
|
+
)
|
|
186
|
+
risk_reasons.add(category_name)
|
|
187
|
+
|
|
188
|
+
# Generate blocking response
|
|
189
|
+
reason_text = (
|
|
190
|
+
", ".join(risk_reasons)
|
|
191
|
+
if risk_reasons
|
|
192
|
+
else "security policy violation"
|
|
193
|
+
)
|
|
194
|
+
response_text = (
|
|
195
|
+
f"Your request has been blocked due to: {reason_text}. "
|
|
196
|
+
f"Please modify your input and try again."
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return response_text
|
|
200
|
+
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
def before_agent_callback(
|
|
204
|
+
self, callback_context: CallbackContext, **kwargs
|
|
205
|
+
) -> None:
|
|
206
|
+
# TODO: Implement agent-level input validation and context analysis
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
def after_agent_callback(self, callback_context: CallbackContext, **kwargs) -> None:
|
|
210
|
+
# TODO: Implement post-agent analysis and context analysis
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
def before_model_callback(
|
|
214
|
+
self, callback_context: CallbackContext, llm_request: LlmRequest, **kwargs
|
|
215
|
+
) -> Optional[LlmResponse]:
|
|
216
|
+
"""
|
|
217
|
+
Moderate user input before sending to the language model.
|
|
218
|
+
|
|
219
|
+
Extracts the last user message from the LLM request and checks it
|
|
220
|
+
against the LLM Shield service. If the content violates safety policies,
|
|
221
|
+
returns a blocking response instead of allowing the request to proceed.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
callback_context (CallbackContext): The callback execution context
|
|
225
|
+
llm_request (LlmRequest): The incoming LLM request to moderate
|
|
226
|
+
**kwargs: Additional keyword arguments
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Optional[LlmResponse]: A blocking response if content is unsafe,
|
|
230
|
+
None if content is safe to proceed
|
|
231
|
+
"""
|
|
232
|
+
# Extract the last user message for moderation
|
|
233
|
+
last_user_message = None
|
|
234
|
+
contents = getattr(llm_request, "contents", [])
|
|
235
|
+
|
|
236
|
+
if contents:
|
|
237
|
+
last_content = contents[-1]
|
|
238
|
+
last_role = getattr(last_content, "role", "")
|
|
239
|
+
last_parts = getattr(last_content, "parts", [])
|
|
240
|
+
|
|
241
|
+
if last_role == "user" and last_parts:
|
|
242
|
+
last_user_message = getattr(last_parts[0], "text", "")
|
|
243
|
+
|
|
244
|
+
# Skip moderation if message is empty
|
|
245
|
+
if not last_user_message:
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
response = self._request_llm_shield(message=last_user_message, role="user")
|
|
249
|
+
if response:
|
|
250
|
+
logger.debug("LLM Shield triggered in before_model_callback.")
|
|
251
|
+
return LlmResponse(
|
|
252
|
+
content=types.Content(
|
|
253
|
+
role="model",
|
|
254
|
+
parts=[types.Part(text=response)],
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
def after_model_callback(
|
|
260
|
+
self, callback_context: CallbackContext, llm_response: LlmResponse, **kwargs
|
|
261
|
+
) -> Optional[LlmResponse]:
|
|
262
|
+
"""
|
|
263
|
+
Moderate model output before returning to the user.
|
|
264
|
+
|
|
265
|
+
Extracts the model's response and checks it against the LLM Shield service.
|
|
266
|
+
If the model's output violates safety policies, returns a blocking response
|
|
267
|
+
instead of the original model output.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
callback_context (CallbackContext): The callback execution context
|
|
271
|
+
llm_response (LlmResponse): The model's response to moderate
|
|
272
|
+
**kwargs: Additional keyword arguments
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Optional[LlmResponse]: A blocking response if content is unsafe,
|
|
276
|
+
None if content is safe to return
|
|
277
|
+
"""
|
|
278
|
+
# Extract the model's response for moderation
|
|
279
|
+
last_model_message = None
|
|
280
|
+
content = getattr(llm_response, "content", [])
|
|
281
|
+
|
|
282
|
+
if content:
|
|
283
|
+
last_role = getattr(content, "role", "")
|
|
284
|
+
last_parts = getattr(content, "parts", [])
|
|
285
|
+
|
|
286
|
+
if last_role == "model" and last_parts:
|
|
287
|
+
last_model_message = getattr(last_parts[0], "text", "")
|
|
288
|
+
|
|
289
|
+
# Skip moderation if message is empty
|
|
290
|
+
if not last_model_message:
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
response = self._request_llm_shield(
|
|
294
|
+
message=last_model_message, role="assistant"
|
|
295
|
+
)
|
|
296
|
+
if response:
|
|
297
|
+
logger.debug("LLM Shield triggered in after_model_callback.")
|
|
298
|
+
return LlmResponse(
|
|
299
|
+
content=types.Content(
|
|
300
|
+
role="model",
|
|
301
|
+
parts=[types.Part(text=response)],
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
def before_tool_callback(
|
|
307
|
+
self, tool: BaseTool, args: Dict[str, Any], tool_context: ToolContext, **kwargs
|
|
308
|
+
) -> Optional[Dict]:
|
|
309
|
+
"""
|
|
310
|
+
Moderate tool arguments before tool execution.
|
|
311
|
+
|
|
312
|
+
Combines all tool arguments into a message and checks it against
|
|
313
|
+
the LLM Shield service. If the arguments contain unsafe content,
|
|
314
|
+
returns a blocking result instead of allowing tool execution.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
tool (BaseTool): The tool to be executed
|
|
318
|
+
args (Dict[str, Any]): The arguments passed to the tool
|
|
319
|
+
tool_context (ToolContext): The tool execution context
|
|
320
|
+
**kwargs: Additional keyword arguments
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Optional[Dict]: A blocking result if arguments are unsafe,
|
|
324
|
+
None if arguments are safe to proceed
|
|
325
|
+
"""
|
|
326
|
+
args_list = []
|
|
327
|
+
|
|
328
|
+
for key, value in args.items():
|
|
329
|
+
args_list.append(f"{key}: {value}")
|
|
330
|
+
|
|
331
|
+
message = "\n".join(args_list)
|
|
332
|
+
response = self._request_llm_shield(message=message, role="user")
|
|
333
|
+
if response:
|
|
334
|
+
logger.debug("LLM Shield triggered in before_tool_callback.")
|
|
335
|
+
return {"result": response}
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
def after_tool_callback(
|
|
339
|
+
self,
|
|
340
|
+
tool: BaseTool,
|
|
341
|
+
args: Dict[str, Any],
|
|
342
|
+
tool_context: CallbackContext,
|
|
343
|
+
tool_response: Union[str, Dict[str, Any], List[Any]],
|
|
344
|
+
**kwargs,
|
|
345
|
+
) -> Optional[Dict]:
|
|
346
|
+
"""
|
|
347
|
+
Moderate tool output after tool execution.
|
|
348
|
+
|
|
349
|
+
Processes the tool's response (string, dict, or list) into a message
|
|
350
|
+
and checks it against the LLM Shield service. If the tool's output
|
|
351
|
+
violates safety policies, returns a blocking result.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
tool (BaseTool): The tool that was executed
|
|
355
|
+
args (Dict[str, Any]): The arguments that were passed to the tool
|
|
356
|
+
tool_context (CallbackContext): The tool execution context
|
|
357
|
+
tool_response (Union[str, Dict[str, Any], List[Any]]): The tool's response
|
|
358
|
+
**kwargs: Additional keyword arguments
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Optional[Dict]: A blocking result if tool output is unsafe,
|
|
362
|
+
None if output is safe to return
|
|
363
|
+
"""
|
|
364
|
+
message = ""
|
|
365
|
+
if isinstance(tool_response, str):
|
|
366
|
+
message = tool_response
|
|
367
|
+
elif isinstance(tool_response, dict):
|
|
368
|
+
for key, value in tool_response.items():
|
|
369
|
+
message += f"{value}\n"
|
|
370
|
+
elif isinstance(tool_response, list):
|
|
371
|
+
for item in tool_response:
|
|
372
|
+
message += f"{item}\n"
|
|
373
|
+
|
|
374
|
+
response = self._request_llm_shield(message=message, role="assistant")
|
|
375
|
+
if response:
|
|
376
|
+
logger.debug("LLM Shield triggered in after_tool_callback.")
|
|
377
|
+
return {"result": response}
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
content_safety = LLMShieldPlugin()
|
|
@@ -0,0 +1,97 @@
|
|
|
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 __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from google.adk.models.llm_request import LlmRequest
|
|
18
|
+
from google.adk.tools.function_tool import FunctionTool
|
|
19
|
+
from google.adk.tools.tool_context import ToolContext
|
|
20
|
+
from google.genai import types
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
from typing_extensions import override
|
|
23
|
+
|
|
24
|
+
from veadk.knowledgebase import KnowledgeBase
|
|
25
|
+
from veadk.knowledgebase.entry import KnowledgebaseEntry
|
|
26
|
+
from veadk.utils.logger import get_logger
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LoadKnowledgebaseResponse(BaseModel):
|
|
32
|
+
knowledges: list[KnowledgebaseEntry] = Field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LoadKnowledgebaseTool(FunctionTool):
|
|
36
|
+
"""A tool that loads the common knowledgebase"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, knowledgebase: KnowledgeBase):
|
|
39
|
+
super().__init__(self.load_knowledgebase)
|
|
40
|
+
|
|
41
|
+
self.knowledgebase = knowledgebase
|
|
42
|
+
|
|
43
|
+
if not self.custom_metadata:
|
|
44
|
+
self.custom_metadata = {}
|
|
45
|
+
self.custom_metadata["backend"] = knowledgebase.backend
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
def _get_declaration(self) -> types.FunctionDeclaration | None:
|
|
49
|
+
return types.FunctionDeclaration(
|
|
50
|
+
name=self.name,
|
|
51
|
+
description=self.description,
|
|
52
|
+
parameters=types.Schema(
|
|
53
|
+
type=types.Type.OBJECT,
|
|
54
|
+
properties={
|
|
55
|
+
"query": types.Schema(
|
|
56
|
+
type=types.Type.STRING,
|
|
57
|
+
)
|
|
58
|
+
},
|
|
59
|
+
required=["query"],
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@override
|
|
64
|
+
async def process_llm_request(
|
|
65
|
+
self,
|
|
66
|
+
*,
|
|
67
|
+
tool_context: ToolContext,
|
|
68
|
+
llm_request: LlmRequest,
|
|
69
|
+
) -> None:
|
|
70
|
+
await super().process_llm_request(
|
|
71
|
+
tool_context=tool_context, llm_request=llm_request
|
|
72
|
+
)
|
|
73
|
+
# Tell the model about the knowledgebase.
|
|
74
|
+
llm_request.append_instructions(
|
|
75
|
+
[
|
|
76
|
+
f"""
|
|
77
|
+
You have a knowledgebase (knowledegebase name is `{self.knowledgebase.name}`, knowledgebase description is `{self.knowledgebase.description}`). You can use it to answer questions. If any questions need
|
|
78
|
+
you to look up the knowledgebase, you should call load_knowledgebase function with a query.
|
|
79
|
+
"""
|
|
80
|
+
]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def load_knowledgebase(
|
|
84
|
+
self, query: str, tool_context: ToolContext
|
|
85
|
+
) -> LoadKnowledgebaseResponse:
|
|
86
|
+
"""Loads the knowledgebase for the user.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
query: The query to load the knowledgebase for.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
A list of knowledgebase results.
|
|
93
|
+
"""
|
|
94
|
+
logger.info(f"Search knowledgebase: {self.knowledgebase.name}")
|
|
95
|
+
response = self.knowledgebase.search(query)
|
|
96
|
+
logger.info(f"Loaded {len(response)} knowledgebase entries for query: {query}")
|
|
97
|
+
return LoadKnowledgebaseResponse(knowledges=response)
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
|
16
|
+
|
|
17
|
+
from veadk.config import getenv
|
|
18
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import (
|
|
19
|
+
StreamableHTTPConnectionParams,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
url = getenv("TOOL_MCP_ROUTER_URL")
|
|
23
|
+
api_key = getenv("TOOL_MCP_ROUTER_API_KEY")
|
|
24
|
+
|
|
25
|
+
mcp_router = MCPToolset(
|
|
26
|
+
connection_params=StreamableHTTPConnectionParams(
|
|
27
|
+
url=url, headers={"Authorization": f"Bearer {api_key}"}
|
|
28
|
+
),
|
|
29
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
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 json
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from google.adk.tools import ToolContext
|
|
19
|
+
|
|
20
|
+
from veadk.config import getenv
|
|
21
|
+
from veadk.utils.logger import get_logger
|
|
22
|
+
from veadk.utils.volcengine_sign import ve_request
|
|
23
|
+
from veadk.auth.veauth.utils import get_credential_from_vefaas_iam
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_code(
|
|
29
|
+
code: str, language: str, tool_context: ToolContext, timeout: int = 30
|
|
30
|
+
) -> str:
|
|
31
|
+
"""Run code in a code sandbox and return the output.
|
|
32
|
+
For C++ code, don't execute it directly, compile and execute via Python; write sources and object files to /tmp.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
code (str): The code to run.
|
|
36
|
+
language (str): The programming language of the code. Language must be one of the supported languages: python3.
|
|
37
|
+
timeout (int, optional): The timeout in seconds for the code execution. Defaults to 30.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
str: The output of the code execution.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
tool_id = getenv("AGENTKIT_TOOL_ID")
|
|
44
|
+
|
|
45
|
+
service = getenv(
|
|
46
|
+
"AGENTKIT_TOOL_SERVICE_CODE", "agentkit"
|
|
47
|
+
) # temporary service for code run tool
|
|
48
|
+
region = getenv("AGENTKIT_TOOL_REGION", "cn-beijing")
|
|
49
|
+
host = getenv(
|
|
50
|
+
"AGENTKIT_TOOL_HOST", service + "." + region + ".volces.com"
|
|
51
|
+
) # temporary host for code run tool
|
|
52
|
+
logger.debug(f"tools endpoint: {host}")
|
|
53
|
+
|
|
54
|
+
session_id = tool_context._invocation_context.session.id
|
|
55
|
+
agent_name = tool_context._invocation_context.agent.name
|
|
56
|
+
user_id = tool_context._invocation_context.user_id
|
|
57
|
+
tool_user_session_id = agent_name + "_" + user_id + "_" + session_id
|
|
58
|
+
logger.debug(f"tool_user_session_id: {tool_user_session_id}")
|
|
59
|
+
|
|
60
|
+
logger.debug(
|
|
61
|
+
f"Running code in language: {language}, session_id={session_id}, code={code}, tool_id={tool_id}, host={host}, service={service}, region={region}, timeout={timeout}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
ak = tool_context.state.get("VOLCENGINE_ACCESS_KEY")
|
|
65
|
+
sk = tool_context.state.get("VOLCENGINE_SECRET_KEY")
|
|
66
|
+
header = {}
|
|
67
|
+
|
|
68
|
+
if not (ak and sk):
|
|
69
|
+
logger.debug("Get AK/SK from tool context failed.")
|
|
70
|
+
ak = os.getenv("VOLCENGINE_ACCESS_KEY")
|
|
71
|
+
sk = os.getenv("VOLCENGINE_SECRET_KEY")
|
|
72
|
+
if not (ak and sk):
|
|
73
|
+
logger.debug(
|
|
74
|
+
"Get AK/SK from environment variables failed. Try to use credential from Iam."
|
|
75
|
+
)
|
|
76
|
+
credential = get_credential_from_vefaas_iam()
|
|
77
|
+
ak = credential.access_key_id
|
|
78
|
+
sk = credential.secret_access_key
|
|
79
|
+
header = {"X-Security-Token": credential.session_token}
|
|
80
|
+
else:
|
|
81
|
+
logger.debug("Successfully get AK/SK from environment variables.")
|
|
82
|
+
else:
|
|
83
|
+
logger.debug("Successfully get AK/SK from tool context.")
|
|
84
|
+
|
|
85
|
+
res = ve_request(
|
|
86
|
+
request_body={
|
|
87
|
+
"ToolId": tool_id,
|
|
88
|
+
"UserSessionId": tool_user_session_id,
|
|
89
|
+
"OperationType": "RunCode",
|
|
90
|
+
"OperationPayload": json.dumps(
|
|
91
|
+
{
|
|
92
|
+
"code": code,
|
|
93
|
+
"timeout": timeout,
|
|
94
|
+
"kernel_name": language,
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
action="InvokeTool",
|
|
99
|
+
ak=ak,
|
|
100
|
+
sk=sk,
|
|
101
|
+
service=service,
|
|
102
|
+
version="2025-10-30",
|
|
103
|
+
region=region,
|
|
104
|
+
host=host,
|
|
105
|
+
header=header,
|
|
106
|
+
)
|
|
107
|
+
logger.debug(f"Invoke run code response: {res}")
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
return res["Result"]["Result"]
|
|
111
|
+
except KeyError as e:
|
|
112
|
+
logger.error(f"Error occurred while running code: {e}, response is {res}")
|
|
113
|
+
return res
|