agent-starter-pack 0.2.3__py3-none-any.whl → 0.3.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 agent-starter-pack might be problematic. Click here for more details.
- {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.0.dist-info}/METADATA +8 -4
- {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.0.dist-info}/RECORD +61 -46
- agents/adk_base/README.md +14 -0
- agents/adk_base/app/agent.py +66 -0
- agents/adk_base/notebooks/adk_app_testing.ipynb +305 -0
- agents/adk_base/template/.templateconfig.yaml +21 -0
- agents/adk_base/tests/integration/test_agent.py +58 -0
- agents/agentic_rag/README.md +1 -0
- agents/agentic_rag/app/agent.py +44 -89
- agents/agentic_rag/app/templates.py +0 -25
- agents/agentic_rag/notebooks/adk_app_testing.ipynb +305 -0
- agents/agentic_rag/template/.templateconfig.yaml +3 -1
- agents/agentic_rag/tests/integration/test_agent.py +34 -27
- agents/langgraph_base_react/README.md +1 -1
- agents/langgraph_base_react/template/.templateconfig.yaml +1 -1
- src/base_template/Makefile +9 -0
- src/base_template/README.md +1 -1
- src/base_template/app/__init__.py +3 -0
- src/base_template/app/utils/tracing.py +11 -1
- src/base_template/app/utils/typing.py +54 -4
- src/base_template/deployment/terraform/dev/variables.tf +4 -0
- src/base_template/deployment/terraform/dev/vars/env.tfvars +0 -3
- src/base_template/deployment/terraform/variables.tf +4 -0
- src/base_template/deployment/terraform/vars/env.tfvars +0 -4
- src/base_template/pyproject.toml +5 -3
- src/{deployment_targets/agent_engine → base_template}/tests/unit/test_dummy.py +2 -1
- src/cli/commands/create.py +10 -2
- src/cli/commands/setup_cicd.py +3 -0
- src/cli/utils/gcp.py +1 -1
- src/cli/utils/template.py +27 -25
- src/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +2 -1
- src/deployment_targets/agent_engine/app/agent_engine_app.py +62 -11
- src/deployment_targets/agent_engine/app/utils/gcs.py +1 -1
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +63 -0
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +9 -2
- src/deployment_targets/cloud_run/app/server.py +41 -15
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +60 -3
- src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +57 -24
- src/frontends/live_api_react/frontend/package-lock.json +3 -3
- src/frontends/streamlit_adk/frontend/side_bar.py +214 -0
- src/frontends/streamlit_adk/frontend/streamlit_app.py +314 -0
- src/frontends/streamlit_adk/frontend/style/app_markdown.py +37 -0
- src/frontends/streamlit_adk/frontend/utils/chat_utils.py +84 -0
- src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +110 -0
- src/frontends/streamlit_adk/frontend/utils/message_editing.py +61 -0
- src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +223 -0
- src/frontends/streamlit_adk/frontend/utils/stream_handler.py +311 -0
- src/frontends/streamlit_adk/frontend/utils/title_summary.py +129 -0
- src/resources/locks/uv-adk_base-agent_engine.lock +5335 -0
- src/resources/locks/uv-adk_base-cloud_run.lock +5927 -0
- src/resources/locks/uv-agentic_rag-agent_engine.lock +882 -676
- src/resources/locks/uv-agentic_rag-cloud_run.lock +1014 -835
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +712 -606
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +770 -672
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +602 -529
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +763 -665
- src/resources/locks/uv-live_api-cloud_run.lock +760 -662
- agents/agentic_rag/notebooks/evaluating_langgraph_agent.ipynb +0 -1561
- src/base_template/tests/unit/test_utils/test_tracing_exporter.py +0 -140
- src/deployment_targets/cloud_run/tests/unit/test_server.py +0 -124
- {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.2.3.dist-info → agent_starter_pack-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
description: "
|
|
15
|
+
description: "An agent implementing a base ReAct agent using LangGraph"
|
|
16
16
|
settings:
|
|
17
17
|
requires_data_ingestion: false
|
|
18
18
|
deployment_targets: ["agent_engine", "cloud_run"]
|
src/base_template/Makefile
CHANGED
|
@@ -6,6 +6,14 @@ test:
|
|
|
6
6
|
uv run pytest tests/unit && uv run pytest tests/integration
|
|
7
7
|
|
|
8
8
|
playground:
|
|
9
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
10
|
+
@echo "+-----------------------------------------------------------------------------+"
|
|
11
|
+
@echo "| Starting ADK Web Server via 'adk web' command. |"
|
|
12
|
+
@echo "| |"
|
|
13
|
+
@echo "| Please select the app folder to start interacting with your agent. |"
|
|
14
|
+
@echo "+-----------------------------------------------------------------------------+"
|
|
15
|
+
uv run adk web
|
|
16
|
+
{%- else %}
|
|
9
17
|
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
10
18
|
uv run uvicorn app.server:app --host 0.0.0.0 --port 8000 --reload &
|
|
11
19
|
{%- endif %}
|
|
@@ -14,6 +22,7 @@ playground:
|
|
|
14
22
|
{%- else %}
|
|
15
23
|
{% if cookiecutter.deployment_target == 'agent_engine' %}PYTHONPATH=. {% endif %}uv run streamlit run frontend/streamlit_app.py --browser.serverAddress=localhost --server.enableCORS=false --server.enableXsrfProtection=false
|
|
16
24
|
{%- endif %}
|
|
25
|
+
{%- endif %}
|
|
17
26
|
|
|
18
27
|
backend:
|
|
19
28
|
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
src/base_template/README.md
CHANGED
|
@@ -47,7 +47,7 @@ make install && make playground
|
|
|
47
47
|
| -------------------- | ------------------------------------------------------------------------------------------- |
|
|
48
48
|
| `make install` | Install all required dependencies using uv |
|
|
49
49
|
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
50
|
-
| `make playground` | Launch local development environment with backend and frontend |
|
|
50
|
+
| `make playground` | Launch local development environment with backend and frontend{%- if "adk" in cookiecutter.tags %} - leveraging `adk web` command. {%- endif %}|
|
|
51
51
|
| `make backend` | Start backend server only |
|
|
52
52
|
| `make ui` | Launch Streamlit frontend without local backend |
|
|
53
53
|
{%- elif cookiecutter.deployment_target == 'agent_engine' %}
|
|
@@ -86,8 +86,18 @@ class CloudTraceLoggingSpanExporter(CloudTraceSpanExporter):
|
|
|
86
86
|
print(span_dict)
|
|
87
87
|
|
|
88
88
|
# Log the span data to Google Cloud Logging
|
|
89
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
90
|
+
self.logger.log_struct(
|
|
91
|
+
span_dict,
|
|
92
|
+
labels={
|
|
93
|
+
"type": "agent_telemetry",
|
|
94
|
+
"service_name": "{{cookiecutter.project_name}}",
|
|
95
|
+
},
|
|
96
|
+
severity="INFO",
|
|
97
|
+
)
|
|
98
|
+
{%- else %}
|
|
89
99
|
self.logger.log_struct(span_dict, severity="INFO")
|
|
90
|
-
|
|
100
|
+
{%- endif %}
|
|
91
101
|
# Export spans to Google Cloud Trace using the parent class method
|
|
92
102
|
return super().export(spans)
|
|
93
103
|
|
|
@@ -12,6 +12,29 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
16
|
+
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
17
|
+
import uuid
|
|
18
|
+
from typing import (
|
|
19
|
+
Literal,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from google.adk.events.event import Event
|
|
23
|
+
from google.genai.types import Content
|
|
24
|
+
from pydantic import (
|
|
25
|
+
BaseModel,
|
|
26
|
+
Field,
|
|
27
|
+
)
|
|
28
|
+
{%- else %}
|
|
29
|
+
from typing import (
|
|
30
|
+
Literal,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from pydantic import (
|
|
34
|
+
BaseModel,
|
|
35
|
+
)
|
|
36
|
+
{%- endif %}
|
|
37
|
+
{%- else %}
|
|
15
38
|
import json
|
|
16
39
|
import uuid
|
|
17
40
|
from typing import (
|
|
@@ -31,6 +54,24 @@ from pydantic import (
|
|
|
31
54
|
BaseModel,
|
|
32
55
|
Field,
|
|
33
56
|
)
|
|
57
|
+
{%- endif %}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
61
|
+
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Request(BaseModel):
|
|
65
|
+
"""Represents the input for a chat request with optional configuration."""
|
|
66
|
+
|
|
67
|
+
message: Content
|
|
68
|
+
events: list[Event]
|
|
69
|
+
user_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
70
|
+
session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
71
|
+
|
|
72
|
+
model_config = {"extra": "allow"}
|
|
73
|
+
{%- endif %}
|
|
74
|
+
{%- else %}
|
|
34
75
|
|
|
35
76
|
|
|
36
77
|
class InputChat(BaseModel):
|
|
@@ -42,7 +83,7 @@ class InputChat(BaseModel):
|
|
|
42
83
|
..., description="The chat messages representing the current conversation."
|
|
43
84
|
)
|
|
44
85
|
|
|
45
|
-
|
|
86
|
+
|
|
46
87
|
class Request(BaseModel):
|
|
47
88
|
"""Represents the input for a chat request with optional configuration.
|
|
48
89
|
|
|
@@ -54,16 +95,23 @@ class Request(BaseModel):
|
|
|
54
95
|
input: InputChat
|
|
55
96
|
config: RunnableConfig | None = None
|
|
56
97
|
|
|
57
|
-
{
|
|
98
|
+
{%- endif %}
|
|
99
|
+
|
|
100
|
+
|
|
58
101
|
class Feedback(BaseModel):
|
|
59
102
|
"""Represents feedback for a conversation."""
|
|
60
103
|
|
|
61
104
|
score: int | float
|
|
62
105
|
text: str | None = ""
|
|
106
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
107
|
+
invocation_id: str
|
|
108
|
+
{%- else %}
|
|
63
109
|
run_id: str
|
|
110
|
+
{%- endif %}
|
|
64
111
|
log_type: Literal["feedback"] = "feedback"
|
|
65
112
|
service_name: Literal["{{cookiecutter.project_name}}"] = "{{cookiecutter.project_name}}"
|
|
66
|
-
|
|
113
|
+
user_id: str = ""
|
|
114
|
+
{% if "adk" not in cookiecutter.tags %}
|
|
67
115
|
|
|
68
116
|
def ensure_valid_config(config: RunnableConfig | None) -> RunnableConfig:
|
|
69
117
|
"""Ensures a valid RunnableConfig by setting defaults for missing fields."""
|
|
@@ -99,7 +147,8 @@ def dumps(obj: Any) -> str:
|
|
|
99
147
|
JSON string representation of the object
|
|
100
148
|
"""
|
|
101
149
|
return json.dumps(obj, default=default_serialization)
|
|
102
|
-
{
|
|
150
|
+
{%- if cookiecutter.deployment_target == 'agent_engine' %}
|
|
151
|
+
|
|
103
152
|
|
|
104
153
|
def dumpd(obj: Any) -> Any:
|
|
105
154
|
"""
|
|
@@ -113,4 +162,5 @@ def dumpd(obj: Any) -> Any:
|
|
|
113
162
|
Dict/list representation of the object that can be JSON serialized
|
|
114
163
|
"""
|
|
115
164
|
return json.loads(dumps(obj))
|
|
165
|
+
{%- endif %}
|
|
116
166
|
{% endif %}
|
|
@@ -32,7 +32,11 @@ variable "region" {
|
|
|
32
32
|
variable "telemetry_logs_filter" {
|
|
33
33
|
type = string
|
|
34
34
|
description = "Log Sink filter for capturing telemetry data. Captures logs with the `traceloop.association.properties.log_type` attribute set to `tracing`."
|
|
35
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
36
|
+
default = "labels.service_name=\"{{cookiecutter.project_name}}\" labels.type=\"agent_telemetry\""
|
|
37
|
+
{%- else %}
|
|
35
38
|
default = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
|
|
39
|
+
{%- endif %}
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
variable "feedback_logs_filter" {
|
|
@@ -7,9 +7,6 @@ dev_project_id = "your-dev-project-id"
|
|
|
7
7
|
# The Google Cloud region you will use to deploy the infrastructure
|
|
8
8
|
region = "us-central1"
|
|
9
9
|
|
|
10
|
-
telemetry_logs_filter = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
|
|
11
|
-
feedback_logs_filter = "jsonPayload.log_type=\"feedback\""
|
|
12
|
-
|
|
13
10
|
{%- if cookiecutter.data_ingestion %}
|
|
14
11
|
{%- if cookiecutter.datastore_type == "vertex_ai_search" %}
|
|
15
12
|
# The value can only be one of "global", "us" and "eu".
|
|
@@ -52,7 +52,11 @@ variable "repository_name" {
|
|
|
52
52
|
variable "telemetry_logs_filter" {
|
|
53
53
|
type = string
|
|
54
54
|
description = "Log Sink filter for capturing telemetry data. Captures logs with the `traceloop.association.properties.log_type` attribute set to `tracing`."
|
|
55
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
56
|
+
default = "labels.service_name=\"{{cookiecutter.project_name}}\" labels.type=\"agent_telemetry\""
|
|
57
|
+
{%- else %}
|
|
55
58
|
default = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
|
|
59
|
+
{%- endif %}
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
variable "feedback_logs_filter" {
|
|
@@ -19,10 +19,6 @@ repository_name = "repo-{{cookiecutter.project_name}}"
|
|
|
19
19
|
# The Google Cloud region you will use to deploy the infrastructure
|
|
20
20
|
region = "us-central1"
|
|
21
21
|
|
|
22
|
-
telemetry_logs_filter = "jsonPayload.attributes.\"traceloop.association.properties.log_type\"=\"tracing\" jsonPayload.resource.attributes.\"service.name\"=\"{{cookiecutter.project_name}}\""
|
|
23
|
-
|
|
24
|
-
feedback_logs_filter = "jsonPayload.log_type=\"feedback\""
|
|
25
|
-
|
|
26
22
|
{%- if cookiecutter.data_ingestion %}
|
|
27
23
|
pipeline_cron_schedule = "0 0 * * 0"
|
|
28
24
|
|
src/base_template/pyproject.toml
CHANGED
|
@@ -9,16 +9,18 @@ dependencies = [
|
|
|
9
9
|
{%- for dep in cookiecutter.extra_dependencies %}
|
|
10
10
|
"{{ dep }}",
|
|
11
11
|
{%- endfor %}
|
|
12
|
-
"langchain-core~=0.3.9",
|
|
13
12
|
"opentelemetry-exporter-gcp-trace~=1.9.0",
|
|
13
|
+
{%- if "adk" not in cookiecutter.tags %}
|
|
14
|
+
"langchain-core~=0.3.9",
|
|
14
15
|
"traceloop-sdk~=0.38.7",
|
|
16
|
+
{%- endif %}
|
|
15
17
|
"google-cloud-logging~=3.11.4",
|
|
16
18
|
{%- if cookiecutter.deployment_target == 'cloud_run' %}
|
|
17
|
-
"google-cloud-aiplatform[evaluation]~=1.
|
|
19
|
+
"google-cloud-aiplatform[evaluation]~=1.88.0",
|
|
18
20
|
"fastapi~=0.115.8",
|
|
19
21
|
"uvicorn~=0.34.0"
|
|
20
22
|
{%- elif cookiecutter.deployment_target == 'agent_engine' %}
|
|
21
|
-
"google-cloud-aiplatform[evaluation,
|
|
23
|
+
"google-cloud-aiplatform[evaluation,agent-engines]~=1.88.0"
|
|
22
24
|
{%- endif %}
|
|
23
25
|
]
|
|
24
26
|
{% if cookiecutter.deployment_target == 'cloud_run' %}
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
|
|
15
14
|
"""
|
|
16
15
|
You can add your unit tests here.
|
|
16
|
+
This is where you test your business logic, including agent functionality,
|
|
17
|
+
data processing, and other core components of your application.
|
|
17
18
|
"""
|
|
18
19
|
|
|
19
20
|
|
src/cli/commands/create.py
CHANGED
|
@@ -306,10 +306,10 @@ def create(
|
|
|
306
306
|
f"This agent uses a datastore for grounded responses.\n"
|
|
307
307
|
f"The agent will work without data, but for optimal results:\n"
|
|
308
308
|
f"1. Set up dev environment:\n"
|
|
309
|
-
f" [white italic]
|
|
309
|
+
f" [white italic]export PROJECT_ID={project_id} && cd {cd_path} && make setup-dev-env[/white italic]\n\n"
|
|
310
310
|
f" See deployment/README.md for more info\n"
|
|
311
311
|
f"2. Run the data ingestion pipeline:\n"
|
|
312
|
-
f" [white italic]
|
|
312
|
+
f" [white italic]export PROJECT_ID={project_id} && cd {cd_path} && make data-ingestion[/white italic]\n\n"
|
|
313
313
|
f" See data_ingestion/README.md for more info\n"
|
|
314
314
|
f"[bold white]=================================[/bold white]\n"
|
|
315
315
|
)
|
|
@@ -647,6 +647,14 @@ def replace_region_in_files(
|
|
|
647
647
|
"_DATA_STORE_REGION: us", f"_DATA_STORE_REGION: {data_store_region}"
|
|
648
648
|
)
|
|
649
649
|
modified = True
|
|
650
|
+
elif '"DATA_STORE_REGION", "us"' in content:
|
|
651
|
+
if debug:
|
|
652
|
+
logging.debug(f"Replacing DATA_STORE_REGION in {file_path}")
|
|
653
|
+
content = content.replace(
|
|
654
|
+
'"DATA_STORE_REGION", "us"',
|
|
655
|
+
f'"DATA_STORE_REGION", "{data_store_region}"',
|
|
656
|
+
)
|
|
657
|
+
modified = True
|
|
650
658
|
|
|
651
659
|
if modified:
|
|
652
660
|
file_path.write_text(content)
|
src/cli/commands/setup_cicd.py
CHANGED
|
@@ -815,6 +815,9 @@ def setup_cicd(
|
|
|
815
815
|
|
|
816
816
|
repo_url = f"https://github.com/{github_username}/{config.repository_name}"
|
|
817
817
|
cloud_build_url = f"https://console.cloud.google.com/cloud-build/builds?project={config.cicd_project_id}"
|
|
818
|
+
# Sleep to allow resources to propagate
|
|
819
|
+
console.print("\n⏳ Waiting for resources to propagate...")
|
|
820
|
+
time.sleep(10)
|
|
818
821
|
|
|
819
822
|
# Print final summary
|
|
820
823
|
print_cicd_summary(config, github_username, repo_url, cloud_build_url)
|
src/cli/utils/gcp.py
CHANGED
|
@@ -45,7 +45,7 @@ def get_dummy_request(project_id: str, location: str) -> CountTokensRequest:
|
|
|
45
45
|
"""Creates a simple test request for Gemini."""
|
|
46
46
|
return CountTokensRequest(
|
|
47
47
|
contents=[{"role": "user", "parts": [{"text": "Hi"}]}],
|
|
48
|
-
endpoint=f"projects/{project_id}/locations/{location}/publishers/google/models/gemini-
|
|
48
|
+
endpoint=f"projects/{project_id}/locations/{location}/publishers/google/models/gemini-2.0-flash",
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
src/cli/utils/template.py
CHANGED
|
@@ -29,6 +29,9 @@ from src.cli.utils.version import get_current_version
|
|
|
29
29
|
|
|
30
30
|
from .datastores import DATASTORES
|
|
31
31
|
|
|
32
|
+
ADK_FILES = ["app/__init__.py"]
|
|
33
|
+
NON_ADK_FILES: list[str] = []
|
|
34
|
+
|
|
32
35
|
|
|
33
36
|
@dataclass
|
|
34
37
|
class TemplateConfig:
|
|
@@ -77,12 +80,10 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
|
|
|
77
80
|
deployment_target: Optional deployment target to filter agents
|
|
78
81
|
"""
|
|
79
82
|
# Define priority agents that should appear first
|
|
80
|
-
PRIORITY_AGENTS = [
|
|
81
|
-
"langgraph_base_react" # Add other priority agents here as needed
|
|
82
|
-
]
|
|
83
|
+
PRIORITY_AGENTS = ["adk_base", "agentic_rag", "langgraph_base_react"]
|
|
83
84
|
|
|
84
85
|
agents_list = []
|
|
85
|
-
|
|
86
|
+
priority_agents_dict = dict.fromkeys(PRIORITY_AGENTS) # Track priority agents
|
|
86
87
|
agents_dir = pathlib.Path(__file__).parent.parent.parent.parent / "agents"
|
|
87
88
|
|
|
88
89
|
for agent_dir in agents_dir.iterdir():
|
|
@@ -109,16 +110,21 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
|
|
|
109
110
|
|
|
110
111
|
# Add to priority list or regular list based on agent name
|
|
111
112
|
if agent_name in PRIORITY_AGENTS:
|
|
112
|
-
|
|
113
|
+
priority_agents_dict[agent_name] = agent_info
|
|
113
114
|
else:
|
|
114
115
|
agents_list.append(agent_info)
|
|
115
116
|
except Exception as e:
|
|
116
117
|
logging.warning(f"Could not load agent from {agent_dir}: {e}")
|
|
117
118
|
|
|
118
|
-
#
|
|
119
|
+
# Sort the non-priority agents
|
|
119
120
|
agents_list.sort(key=lambda x: x["name"])
|
|
120
121
|
|
|
121
|
-
#
|
|
122
|
+
# Create priority agents list in the exact order specified
|
|
123
|
+
priority_agents = [
|
|
124
|
+
info for name, info in priority_agents_dict.items() if info is not None
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
# Combine priority agents with regular agents
|
|
122
128
|
combined_agents = priority_agents + agents_list
|
|
123
129
|
|
|
124
130
|
# Convert to numbered dictionary starting from 1
|
|
@@ -497,29 +503,27 @@ def process_template(
|
|
|
497
503
|
extra_deps = template_config.get("settings", {}).get(
|
|
498
504
|
"extra_dependencies", []
|
|
499
505
|
)
|
|
500
|
-
otel_instrumentations = get_otel_instrumentations(dependencies=extra_deps)
|
|
501
|
-
|
|
502
506
|
# Get frontend type from template config
|
|
503
507
|
frontend_type = template_config.get("settings", {}).get(
|
|
504
508
|
"frontend_type", DEFAULT_FRONTEND
|
|
505
509
|
)
|
|
506
|
-
|
|
510
|
+
tags = template_config.get("settings", {}).get("tags", ["None"])
|
|
507
511
|
cookiecutter_config = {
|
|
508
512
|
"project_name": "my-project",
|
|
509
513
|
"agent_name": agent_name,
|
|
510
514
|
"package_version": get_current_version(),
|
|
511
515
|
"agent_description": template_config.get("description", ""),
|
|
516
|
+
"tags": tags,
|
|
512
517
|
"deployment_target": deployment_target or "",
|
|
513
518
|
"frontend_type": frontend_type,
|
|
514
519
|
"extra_dependencies": [extra_deps],
|
|
515
|
-
"otel_instrumentations": otel_instrumentations,
|
|
516
520
|
"data_ingestion": include_data_ingestion, # Use explicit flag for cookiecutter
|
|
517
521
|
"datastore_type": datastore if datastore else "",
|
|
518
522
|
"_copy_without_render": [
|
|
519
523
|
"*.ipynb", # Don't render notebooks
|
|
520
524
|
"*.json", # Don't render JSON files
|
|
521
525
|
"frontend/*", # Don't render frontend directory
|
|
522
|
-
"tests/*", # Don't render tests directory
|
|
526
|
+
# "tests/*", # Don't render tests directory
|
|
523
527
|
"notebooks/*", # Don't render notebooks directory
|
|
524
528
|
".git/*", # Don't render git directory
|
|
525
529
|
"__pycache__/*", # Don't render cache
|
|
@@ -566,6 +570,17 @@ def process_template(
|
|
|
566
570
|
shutil.copytree(output_dir, final_destination, dirs_exist_ok=True)
|
|
567
571
|
logging.debug(f"Project successfully created at {final_destination}")
|
|
568
572
|
|
|
573
|
+
# Delete appropriate files based on ADK tag
|
|
574
|
+
if "adk" in tags:
|
|
575
|
+
files_to_delete = [final_destination / f for f in NON_ADK_FILES]
|
|
576
|
+
else:
|
|
577
|
+
files_to_delete = [final_destination / f for f in ADK_FILES]
|
|
578
|
+
|
|
579
|
+
for file_path in files_to_delete:
|
|
580
|
+
if file_path.exists():
|
|
581
|
+
file_path.unlink()
|
|
582
|
+
logging.debug(f"Deleted {file_path}")
|
|
583
|
+
|
|
569
584
|
# After copying template files, handle the lock file
|
|
570
585
|
if deployment_target:
|
|
571
586
|
# Get the source lock file path
|
|
@@ -715,16 +730,3 @@ def copy_deployment_files(
|
|
|
715
730
|
)
|
|
716
731
|
else:
|
|
717
732
|
logging.warning(f"Deployment target directory not found: {deployment_path}")
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
def get_otel_instrumentations(dependencies: list) -> list[list[str]]:
|
|
721
|
-
"""Returns OpenTelemetry instrumentation statements for enabled dependencies."""
|
|
722
|
-
otel_deps = {
|
|
723
|
-
"langgraph": "Instruments.LANGCHAIN",
|
|
724
|
-
"crewai": "Instruments.CREW",
|
|
725
|
-
}
|
|
726
|
-
imports = []
|
|
727
|
-
for dep in dependencies:
|
|
728
|
-
if any(otel_dep in dep for otel_dep in otel_deps):
|
|
729
|
-
imports.append(otel_deps[next(key for key in otel_deps if key in dep)])
|
|
730
|
-
return [imports]
|
|
@@ -109,7 +109,8 @@ def ingest_data(
|
|
|
109
109
|
operation = schema_client.update_schema(
|
|
110
110
|
request=discoveryengine.UpdateSchemaRequest(
|
|
111
111
|
schema=new_schema, allow_missing=True
|
|
112
|
-
)
|
|
112
|
+
),
|
|
113
|
+
timeout=1800,
|
|
113
114
|
)
|
|
114
115
|
logging.info(f"Waiting for schema update operation: {operation.operation.name}")
|
|
115
116
|
operation.result()
|
|
@@ -12,6 +12,53 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
# mypy: disable-error-code="attr-defined"
|
|
16
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
17
|
+
import datetime
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
from collections.abc import Mapping, Sequence
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import google.auth
|
|
24
|
+
import vertexai
|
|
25
|
+
from google.cloud import logging as google_cloud_logging
|
|
26
|
+
from opentelemetry import trace
|
|
27
|
+
from opentelemetry.sdk.trace import TracerProvider, export
|
|
28
|
+
from vertexai import agent_engines
|
|
29
|
+
from vertexai.preview.reasoning_engines import AdkApp
|
|
30
|
+
|
|
31
|
+
from app.agent import root_agent
|
|
32
|
+
from app.utils.gcs import create_bucket_if_not_exists
|
|
33
|
+
from app.utils.tracing import CloudTraceLoggingSpanExporter
|
|
34
|
+
from app.utils.typing import Feedback
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AgentEngineApp(AdkApp):
|
|
38
|
+
def set_up(self) -> None:
|
|
39
|
+
"""Set up logging and tracing for the agent engine app."""
|
|
40
|
+
super().set_up()
|
|
41
|
+
logging_client = google_cloud_logging.Client()
|
|
42
|
+
self.logger = logging_client.logger(__name__)
|
|
43
|
+
provider = TracerProvider()
|
|
44
|
+
processor = export.BatchSpanProcessor(CloudTraceLoggingSpanExporter())
|
|
45
|
+
provider.add_span_processor(processor)
|
|
46
|
+
trace.set_tracer_provider(provider)
|
|
47
|
+
|
|
48
|
+
def register_feedback(self, feedback: dict[str, Any]) -> None:
|
|
49
|
+
"""Collect and log feedback."""
|
|
50
|
+
feedback_obj = Feedback.model_validate(feedback)
|
|
51
|
+
self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
|
|
52
|
+
|
|
53
|
+
def register_operations(self) -> Mapping[str, Sequence]:
|
|
54
|
+
"""Registers the operations of the Agent.
|
|
55
|
+
|
|
56
|
+
Extends the base operations to include feedback registration functionality.
|
|
57
|
+
"""
|
|
58
|
+
operations = super().register_operations()
|
|
59
|
+
operations[""] = operations[""] + ["register_feedback"]
|
|
60
|
+
return operations
|
|
61
|
+
{%- else %}
|
|
15
62
|
import datetime
|
|
16
63
|
import json
|
|
17
64
|
import logging
|
|
@@ -66,7 +113,6 @@ class AgentEngineApp:
|
|
|
66
113
|
)
|
|
67
114
|
except Exception as e:
|
|
68
115
|
logging.error("Failed to initialize Telemetry: %s", str(e))
|
|
69
|
-
|
|
70
116
|
self.runnable = agent
|
|
71
117
|
|
|
72
118
|
# Add any additional variables here that should be included in the tracing logs
|
|
@@ -107,11 +153,6 @@ class AgentEngineApp:
|
|
|
107
153
|
dumped_chunk = dumpd(chunk)
|
|
108
154
|
yield dumped_chunk
|
|
109
155
|
|
|
110
|
-
def register_feedback(self, feedback: dict[str, Any]) -> None:
|
|
111
|
-
"""Collect and log feedback."""
|
|
112
|
-
feedback_obj = Feedback.model_validate(feedback)
|
|
113
|
-
self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
|
|
114
|
-
|
|
115
156
|
def query(
|
|
116
157
|
self,
|
|
117
158
|
*,
|
|
@@ -124,6 +165,11 @@ class AgentEngineApp:
|
|
|
124
165
|
self.set_tracing_properties(config=config)
|
|
125
166
|
return dumpd(self.runnable.invoke(input=input, config=config, **kwargs))
|
|
126
167
|
|
|
168
|
+
def register_feedback(self, feedback: dict[str, Any]) -> None:
|
|
169
|
+
"""Collect and log feedback."""
|
|
170
|
+
feedback_obj = Feedback.model_validate(feedback)
|
|
171
|
+
self.logger.log_struct(feedback_obj.model_dump(), severity="INFO")
|
|
172
|
+
|
|
127
173
|
def register_operations(self) -> Mapping[str, Sequence]:
|
|
128
174
|
"""Registers the operations of the Agent.
|
|
129
175
|
|
|
@@ -140,6 +186,7 @@ class AgentEngineApp:
|
|
|
140
186
|
"": ["query", "register_feedback"],
|
|
141
187
|
"stream": ["stream_query"],
|
|
142
188
|
}
|
|
189
|
+
{%- endif %}
|
|
143
190
|
|
|
144
191
|
|
|
145
192
|
def deploy_agent_engine_app(
|
|
@@ -162,14 +209,18 @@ def deploy_agent_engine_app(
|
|
|
162
209
|
# Read requirements
|
|
163
210
|
with open(requirements_file) as f:
|
|
164
211
|
requirements = f.read().strip().split("\n")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
212
|
+
{% if "adk" in cookiecutter.tags %}
|
|
213
|
+
agent_engine = AgentEngineApp(
|
|
214
|
+
agent=root_agent, env_vars=env_vars, enable_tracing=True
|
|
215
|
+
)
|
|
216
|
+
{% else %}
|
|
217
|
+
agent_engine = AgentEngineApp(project_id=project, env_vars=env_vars)
|
|
218
|
+
{% endif %}
|
|
168
219
|
# Common configuration for both create and update operations
|
|
169
220
|
agent_config = {
|
|
170
|
-
"agent_engine":
|
|
221
|
+
"agent_engine": agent_engine,
|
|
171
222
|
"display_name": agent_name,
|
|
172
|
-
"description": "
|
|
223
|
+
"description": "{{cookiecutter.agent_description}}",
|
|
173
224
|
"extra_packages": extra_packages,
|
|
174
225
|
}
|
|
175
226
|
logging.info(f"Agent config: {agent_config}")
|
|
@@ -15,18 +15,80 @@
|
|
|
15
15
|
import logging
|
|
16
16
|
|
|
17
17
|
import pytest
|
|
18
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
19
|
+
from google.adk.events.event import Event
|
|
18
20
|
|
|
21
|
+
from app.agent import root_agent
|
|
19
22
|
from app.agent_engine_app import AgentEngineApp
|
|
23
|
+
{%- else %}
|
|
24
|
+
|
|
25
|
+
from app.agent_engine_app import AgentEngineApp
|
|
26
|
+
{%- endif %}
|
|
20
27
|
|
|
21
28
|
|
|
22
29
|
@pytest.fixture
|
|
23
30
|
def agent_app() -> AgentEngineApp:
|
|
24
31
|
"""Fixture to create and set up AgentEngineApp instance"""
|
|
32
|
+
{%- if "adk" in cookiecutter.tags %}
|
|
33
|
+
app = AgentEngineApp(agent=root_agent)
|
|
34
|
+
{%- else %}
|
|
25
35
|
app = AgentEngineApp()
|
|
36
|
+
{%- endif %}
|
|
26
37
|
app.set_up()
|
|
27
38
|
return app
|
|
28
39
|
|
|
40
|
+
{% if "adk" in cookiecutter.tags %}
|
|
41
|
+
def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Integration test for the agent stream query functionality.
|
|
44
|
+
Tests that the agent returns valid streaming responses.
|
|
45
|
+
"""
|
|
46
|
+
# Create message and events for the stream_query
|
|
47
|
+
message = "What's the weather in San Francisco?"
|
|
48
|
+
events = list(agent_app.stream_query(message=message, user_id="test"))
|
|
49
|
+
assert len(events) > 0, "Expected at least one chunk in response"
|
|
50
|
+
|
|
51
|
+
# Check for valid content in the response
|
|
52
|
+
has_text_content = False
|
|
53
|
+
for event in events:
|
|
54
|
+
validated_event = Event.model_validate(event)
|
|
55
|
+
content = validated_event.content
|
|
56
|
+
if (
|
|
57
|
+
content is not None
|
|
58
|
+
and content.parts
|
|
59
|
+
and any(part.text for part in content.parts)
|
|
60
|
+
):
|
|
61
|
+
has_text_content = True
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
assert has_text_content, "Expected at least one event with text content"
|
|
29
65
|
|
|
66
|
+
|
|
67
|
+
def test_agent_feedback(agent_app: AgentEngineApp) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Integration test for the agent feedback functionality.
|
|
70
|
+
Tests that feedback can be registered successfully.
|
|
71
|
+
"""
|
|
72
|
+
feedback_data = {
|
|
73
|
+
"score": 5,
|
|
74
|
+
"text": "Great response!",
|
|
75
|
+
"invocation_id": "test-run-123",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Should not raise any exceptions
|
|
79
|
+
agent_app.register_feedback(feedback_data)
|
|
80
|
+
|
|
81
|
+
# Test invalid feedback
|
|
82
|
+
with pytest.raises(ValueError):
|
|
83
|
+
invalid_feedback = {
|
|
84
|
+
"score": "invalid", # Score must be numeric
|
|
85
|
+
"text": "Bad feedback",
|
|
86
|
+
"invocation_id": "test-run-123",
|
|
87
|
+
}
|
|
88
|
+
agent_app.register_feedback(invalid_feedback)
|
|
89
|
+
|
|
90
|
+
logging.info("All assertions passed for agent feedback test")
|
|
91
|
+
{% else %}
|
|
30
92
|
def test_agent_stream_query(agent_app: AgentEngineApp) -> None:
|
|
31
93
|
"""
|
|
32
94
|
Integration test for the agent stream query functionality.
|
|
@@ -118,3 +180,4 @@ def test_agent_feedback(agent_app: AgentEngineApp) -> None:
|
|
|
118
180
|
agent_app.register_feedback(invalid_feedback)
|
|
119
181
|
|
|
120
182
|
logging.info("All assertions passed for agent feedback test")
|
|
183
|
+
{% endif %}
|