reasoning-deployment-service 0.3.6__py3-none-any.whl → 0.3.7__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 reasoning-deployment-service might be problematic. Click here for more details.
- reasoning_deployment_service/reasoning_deployment_service.py +208 -8
- reasoning_deployment_service/runner.py +4 -2
- {reasoning_deployment_service-0.3.6.dist-info → reasoning_deployment_service-0.3.7.dist-info}/METADATA +4 -4
- {reasoning_deployment_service-0.3.6.dist-info → reasoning_deployment_service-0.3.7.dist-info}/RECORD +7 -7
- {reasoning_deployment_service-0.3.6.dist-info → reasoning_deployment_service-0.3.7.dist-info}/WHEEL +0 -0
- {reasoning_deployment_service-0.3.6.dist-info → reasoning_deployment_service-0.3.7.dist-info}/entry_points.txt +0 -0
- {reasoning_deployment_service-0.3.6.dist-info → reasoning_deployment_service-0.3.7.dist-info}/top_level.txt +0 -0
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import json, os, subprocess, yaml, sys
|
|
2
2
|
import urllib.parse, vertexai, google.auth
|
|
3
3
|
import requests as _requests
|
|
4
|
-
from typing import Tuple
|
|
4
|
+
from typing import Dict, Optional, Tuple
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from dotenv import load_dotenv
|
|
7
7
|
from vertexai import agent_engines
|
|
8
|
+
from vertexai.agent_engines import AgentEngine
|
|
8
9
|
from google.adk.agents import BaseAgent
|
|
9
10
|
from google.auth.transport.requests import Request as GoogleAuthRequest
|
|
10
11
|
from google.api_core.exceptions import NotFound
|
|
11
12
|
import logging
|
|
12
13
|
from datetime import datetime
|
|
14
|
+
from urllib.parse import urlparse, parse_qs
|
|
13
15
|
|
|
14
16
|
DISCOVERY_ENGINE_URL = "https://discoveryengine.googleapis.com/v1alpha"
|
|
15
17
|
|
|
@@ -101,9 +103,9 @@ class ReasoningEngineDeploymentService:
|
|
|
101
103
|
self._generate_env_agent()
|
|
102
104
|
end_run = True
|
|
103
105
|
|
|
104
|
-
if not os.path.exists("
|
|
106
|
+
if not os.path.exists("agent.yaml"):
|
|
105
107
|
self._generate_example_yaml_config()
|
|
106
|
-
self.warning("Creating
|
|
108
|
+
self.warning("Creating agent.yaml file ... done")
|
|
107
109
|
end_run = True
|
|
108
110
|
|
|
109
111
|
self.warning("Please fill in the required values in the generated files and re-run the deployment.")
|
|
@@ -246,7 +248,7 @@ class ReasoningEngineDeploymentService:
|
|
|
246
248
|
for pattern in new_patterns:
|
|
247
249
|
f.write(f"{pattern}\n")
|
|
248
250
|
|
|
249
|
-
def _generate_example_yaml_config(self, path: str | Path = "
|
|
251
|
+
def _generate_example_yaml_config(self, path: str | Path = "agent.yaml", overwrite: bool = False) -> Path:
|
|
250
252
|
"""
|
|
251
253
|
Create an example YAML config matching the requested schema.
|
|
252
254
|
|
|
@@ -289,11 +291,11 @@ class ReasoningEngineDeploymentService:
|
|
|
289
291
|
|
|
290
292
|
def _load_agent_definition(self):
|
|
291
293
|
try:
|
|
292
|
-
with open("
|
|
294
|
+
with open("agent.yaml", "r") as f:
|
|
293
295
|
config = yaml.safe_load(f)
|
|
294
296
|
except FileNotFoundError:
|
|
295
297
|
self._generate_example_yaml_config()
|
|
296
|
-
self.error("Could not locate a valid
|
|
298
|
+
self.error("Could not locate a valid agent.yaml file. Generating example file in your directory.")
|
|
297
299
|
sys.exit(1)
|
|
298
300
|
|
|
299
301
|
try:
|
|
@@ -320,7 +322,7 @@ class ReasoningEngineDeploymentService:
|
|
|
320
322
|
self._authorization_id = auth.get("oauth_authorization_id", None)
|
|
321
323
|
self._environment_variables = environment_variables or []
|
|
322
324
|
except KeyError as e:
|
|
323
|
-
raise RuntimeError(f"Missing required key in
|
|
325
|
+
raise RuntimeError(f"Missing required key in agent.yaml: {e}")
|
|
324
326
|
|
|
325
327
|
def _load_deployment_environment_variables(self, deployment_environment: str):
|
|
326
328
|
required_vars = ['PROJECT_ID', 'PROJECT_NUMBER', 'PROJECT_LOCATION', 'STAGING_BUCKET', 'AGENT_SPACE_ENGINE']
|
|
@@ -684,4 +686,202 @@ class ReasoningEngineDeploymentService:
|
|
|
684
686
|
self.info("Creating agent space ... ")
|
|
685
687
|
self._deploy_to_agent_space()
|
|
686
688
|
else:
|
|
687
|
-
self._update_in_agent_space()
|
|
689
|
+
self._update_in_agent_space()
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _ensure_vertex_inited(self):
|
|
693
|
+
"""Initialize Vertex AI once and reuse to avoid repeated heavy init calls."""
|
|
694
|
+
if not self._vertex_inited:
|
|
695
|
+
vertexai.init()
|
|
696
|
+
self._vertex_inited = True
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def find_engine_by_name(self, engine_name: str) -> Optional[str]:
|
|
700
|
+
engines = list(agent_engines.list(filter=f'display_name="{engine_name}"'))
|
|
701
|
+
if not engines:
|
|
702
|
+
return None
|
|
703
|
+
if len(engines) > 1:
|
|
704
|
+
raise RuntimeError(f"Multiple engines found with display_name='{engine_name}'. Use unique names/labels.")
|
|
705
|
+
eng = engines[0]
|
|
706
|
+
if not isinstance(eng, AgentEngine) or not hasattr(eng, "resource_name"):
|
|
707
|
+
raise RuntimeError("Unexpected engine object; missing AgentEngine/resource_name.")
|
|
708
|
+
return eng.resource_name
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _get_headers(self) -> dict:
|
|
712
|
+
return {
|
|
713
|
+
"Authorization": f"Bearer {self._access_token()}",
|
|
714
|
+
"Content-Type": "application/json",
|
|
715
|
+
"Accept": "application/json",
|
|
716
|
+
"X-Goog-User-Project": str(self._project_number),
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
def find_authorization_by_id(self, authorization_id: str) -> Optional[str]:
|
|
720
|
+
name = f"projects/{self._project_id}/locations/global/authorizations/{authorization_id}"
|
|
721
|
+
url = f"{DISCOVERY_ENGINE_URL}/{name}"
|
|
722
|
+
r = self._http.get(url, headers=self._get_headers(), timeout=60)
|
|
723
|
+
|
|
724
|
+
if r.status_code == 404:
|
|
725
|
+
return None
|
|
726
|
+
|
|
727
|
+
r.raise_for_status()
|
|
728
|
+
|
|
729
|
+
return r.json().get("name", name)
|
|
730
|
+
|
|
731
|
+
def find_agent_space_agents_by_display(self, display_name: str) -> Optional[dict]:
|
|
732
|
+
base = (
|
|
733
|
+
f"{DISCOVERY_ENGINE_URL}/projects/{self._project_id}/locations/global/"
|
|
734
|
+
f"collections/default_collection/engines/{self._agent_space_engine}/"
|
|
735
|
+
f"assistants/default_assistant/agents"
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
headers = self._get_headers()
|
|
739
|
+
page = None
|
|
740
|
+
matches =[]
|
|
741
|
+
|
|
742
|
+
while True:
|
|
743
|
+
url = base + (f"?pageToken={page}" if page else "")
|
|
744
|
+
r = self._http.get(url, headers=headers, timeout=60)
|
|
745
|
+
r.raise_for_status()
|
|
746
|
+
data = r.json()
|
|
747
|
+
|
|
748
|
+
for a in data.get("agents", []):
|
|
749
|
+
if a.get("displayName") != display_name:
|
|
750
|
+
continue
|
|
751
|
+
full = a.get("name", "")
|
|
752
|
+
matches.append({
|
|
753
|
+
"id": full.split("/")[-1] if full else "",
|
|
754
|
+
"display_name": a.get("displayName", ""),
|
|
755
|
+
"full_name": full,
|
|
756
|
+
"labels": a.get("labels", {}),
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
page = data.get("nextPageToken")
|
|
760
|
+
if not page:
|
|
761
|
+
break
|
|
762
|
+
|
|
763
|
+
if not matches:
|
|
764
|
+
return None
|
|
765
|
+
if len(matches) > 1:
|
|
766
|
+
raise RuntimeError(
|
|
767
|
+
f"Found {len(matches)} agents with displayName='{display_name}'. "
|
|
768
|
+
"Provide an ID or labels (e.g., env/app/agent) to disambiguate."
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
return matches[0]
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def one_github_deployment_to_go(self):
|
|
775
|
+
"""
|
|
776
|
+
CI-friendly deploy:
|
|
777
|
+
- Engine: create or update by display_name.
|
|
778
|
+
- Authorization: create if missing; patch if scopes changed.
|
|
779
|
+
- Agent Space: create if missing; patch if found (by displayName under engine).
|
|
780
|
+
"""
|
|
781
|
+
self.info("Starting GitHub deployment...")
|
|
782
|
+
|
|
783
|
+
# Ensure Vertex SDK calls have context for list/update
|
|
784
|
+
vertexai.init(
|
|
785
|
+
project=self._project_id,
|
|
786
|
+
location=self._project_location,
|
|
787
|
+
staging_bucket=self._staging_bucket,
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
# -----------------------------
|
|
791
|
+
# 1) Reasoning Engine (create or update)
|
|
792
|
+
# -----------------------------
|
|
793
|
+
engine_rn = self.find_engine_by_name(self._reasoning_engine_name)
|
|
794
|
+
if not engine_rn:
|
|
795
|
+
self.info(f"Engine '{self._reasoning_engine_name}' not found. Creating...")
|
|
796
|
+
self.create_reasoning_engine()
|
|
797
|
+
# read back the created id (or re-resolve by name as fallback)
|
|
798
|
+
engine_rn = self._read_engine_deployment_record().get("reasoning_engine_id") or \
|
|
799
|
+
self.find_engine_by_name(self._reasoning_engine_name)
|
|
800
|
+
if not engine_rn:
|
|
801
|
+
self.error("Engine creation did not yield a resource name.")
|
|
802
|
+
raise RuntimeError("Engine creation failed.")
|
|
803
|
+
else:
|
|
804
|
+
self.info(f"Engine '{self._reasoning_engine_name}' exists. Updating...")
|
|
805
|
+
self.update_reasoning_engine(engine_rn)
|
|
806
|
+
|
|
807
|
+
# -----------------------------
|
|
808
|
+
# 2) Authorization (create if missing; update scopes if changed)
|
|
809
|
+
# -----------------------------
|
|
810
|
+
if self._authorization_id:
|
|
811
|
+
want_scopes = set(self._required_scopes or [])
|
|
812
|
+
auth_full_name = self.find_authorization_by_id(self._authorization_id)
|
|
813
|
+
|
|
814
|
+
if not auth_full_name:
|
|
815
|
+
self.info(f"Authorization '{self._authorization_id}' not found. Creating...")
|
|
816
|
+
ok = self._create_authorization()
|
|
817
|
+
if not ok:
|
|
818
|
+
self.error("Authorization creation failed.")
|
|
819
|
+
raise RuntimeError("Authorization creation failed.")
|
|
820
|
+
auth_full_name = self.find_authorization_by_id(self._authorization_id)
|
|
821
|
+
if not auth_full_name:
|
|
822
|
+
self.error("Authorization creation did not resolve to a resource.")
|
|
823
|
+
raise RuntimeError("Authorization creation failed to resolve.")
|
|
824
|
+
else:
|
|
825
|
+
# Compare scopes; patch if different
|
|
826
|
+
r = self._http.get(f"{DISCOVERY_ENGINE_URL}/{auth_full_name}",
|
|
827
|
+
headers=self._get_headers(), timeout=60)
|
|
828
|
+
r.raise_for_status()
|
|
829
|
+
data = r.json() or {}
|
|
830
|
+
existing_uri = (((data.get("serverSideOauth2") or {}).get("authorizationUri")) or "")
|
|
831
|
+
existing_scopes = set()
|
|
832
|
+
if existing_uri:
|
|
833
|
+
parsed = urlparse(existing_uri)
|
|
834
|
+
qs = parse_qs(parsed.query)
|
|
835
|
+
scope_str = (qs.get("scope", [""])[0] or "")
|
|
836
|
+
existing_scopes = set(scope_str.split())
|
|
837
|
+
|
|
838
|
+
if existing_scopes != want_scopes:
|
|
839
|
+
self.info("Authorization scopes changed. Patching authorization...")
|
|
840
|
+
new_auth_uri = self._build_authorization_uri(self._oauth_client_id, list(want_scopes))
|
|
841
|
+
patch_payload = {
|
|
842
|
+
"serverSideOauth2": {
|
|
843
|
+
"clientId": self._oauth_client_id,
|
|
844
|
+
"clientSecret": self._oauth_client_secret,
|
|
845
|
+
"authorizationUri": new_auth_uri,
|
|
846
|
+
"tokenUri": "https://oauth2.googleapis.com/token",
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
pr = self._http.patch(f"{DISCOVERY_ENGINE_URL}/{auth_full_name}",
|
|
850
|
+
headers=self._get_headers(), json=patch_payload, timeout=60)
|
|
851
|
+
pr.raise_for_status()
|
|
852
|
+
self.info("Authorization updated.")
|
|
853
|
+
else:
|
|
854
|
+
self.info("Authorization scopes unchanged; no update needed.")
|
|
855
|
+
else:
|
|
856
|
+
self.info("No authorization_id configured; skipping authorization step.")
|
|
857
|
+
|
|
858
|
+
# -----------------------------
|
|
859
|
+
# 3) Agent Space Agent (create or update by displayName under engine)
|
|
860
|
+
# -----------------------------
|
|
861
|
+
existing_agent = self.find_agent_space_agents_by_display(self._agent_space_name)
|
|
862
|
+
headers, payload = self._get_agent_space_payload(engine_rn)
|
|
863
|
+
|
|
864
|
+
if not existing_agent:
|
|
865
|
+
self.info(f"Agent Space agent '{self._agent_space_name}' not found. Creating...")
|
|
866
|
+
create_url = self._get_agent_space_agent_url_new()
|
|
867
|
+
cr = self._http.post(create_url, headers=headers, json=payload, timeout=90)
|
|
868
|
+
if cr.status_code >= 400:
|
|
869
|
+
# File-only details already handled elsewhere; surface concise error here
|
|
870
|
+
self.error(f"Agent creation failed [{cr.status_code}].")
|
|
871
|
+
cr.raise_for_status()
|
|
872
|
+
agent_name = (cr.json() or {}).get("name")
|
|
873
|
+
if agent_name:
|
|
874
|
+
self._write_engine_deployment({"agent_space_id": agent_name})
|
|
875
|
+
self.info(f"Agent Space agent created: {agent_name}")
|
|
876
|
+
else:
|
|
877
|
+
self.warning("Agent created but response missing name. Verify in console.")
|
|
878
|
+
else:
|
|
879
|
+
self.info(f"Agent Space agent '{self._agent_space_name}' exists. Updating...")
|
|
880
|
+
patch_url = f"{DISCOVERY_ENGINE_URL}/{existing_agent['full_name']}"
|
|
881
|
+
ur = self._http.patch(patch_url, headers=headers, json=payload, timeout=90)
|
|
882
|
+
if ur.status_code >= 400:
|
|
883
|
+
self.error(f"Agent update failed [{ur.status_code}].")
|
|
884
|
+
ur.raise_for_status()
|
|
885
|
+
self.info("Agent Space agent updated.")
|
|
886
|
+
|
|
887
|
+
self.info("GitHub deployment completed successfully.")
|
|
@@ -29,8 +29,8 @@ class Runner:
|
|
|
29
29
|
root_agent = Runner._load_agent(args.agent_path)
|
|
30
30
|
|
|
31
31
|
# --- Require config files ---
|
|
32
|
-
if not Path(".env.agent").exists() or not Path("
|
|
33
|
-
print("Missing .env.agent or
|
|
32
|
+
if not Path(".env.agent").exists() or not Path("agent.yaml").exists():
|
|
33
|
+
print("Missing .env.agent or agent.yaml.")
|
|
34
34
|
print("Options:\n 5) Generate placeholder config\n q) Quit")
|
|
35
35
|
choice = input("Enter choice: ").strip()
|
|
36
36
|
if choice == "5":
|
|
@@ -132,6 +132,8 @@ class Runner:
|
|
|
132
132
|
GUIEditor().run()
|
|
133
133
|
elif mode == "populate_files":
|
|
134
134
|
svc._check_required_files_exist()
|
|
135
|
+
elif mode == "github_deployment":
|
|
136
|
+
svc.one_github_deployment_to_go()
|
|
135
137
|
|
|
136
138
|
@staticmethod
|
|
137
139
|
def _menu(root_agent):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reasoning-deployment-service
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Deployment helper for Vertex AI Reasoning Engines & Agent Spaces
|
|
5
5
|
Author-email: Sergio Estrada <sergio.estrada@accenture.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -67,7 +67,7 @@ q) Quit
|
|
|
67
67
|
- The tool will guide you to create the starter files:
|
|
68
68
|
- `your_agent_import.py` (shim pointing to your agent folder)
|
|
69
69
|
- `.env.agent` (environment and project settings)
|
|
70
|
-
- `
|
|
70
|
+
- `agent.yaml` (agent engine and space definition)
|
|
71
71
|
|
|
72
72
|
Once these are in place, all modes will be fully available
|
|
73
73
|
|
|
@@ -83,7 +83,7 @@ Your project must have the following at minimum:
|
|
|
83
83
|
my_project/
|
|
84
84
|
├── requirements.txt # pinned dependencies
|
|
85
85
|
├── .env.agent # environment and project config
|
|
86
|
-
├──
|
|
86
|
+
├── agent.yaml # engine and agent space definition
|
|
87
87
|
├── my_agent/ # your agent folder
|
|
88
88
|
│ └── agent.py # defines root_agent
|
|
89
89
|
```
|
|
@@ -173,7 +173,7 @@ DEVELOPER=your.name
|
|
|
173
173
|
|
|
174
174
|
---
|
|
175
175
|
|
|
176
|
-
### ✅
|
|
176
|
+
### ✅ agent.yaml
|
|
177
177
|
|
|
178
178
|
Example starter file:
|
|
179
179
|
|
{reasoning_deployment_service-0.3.6.dist-info → reasoning_deployment_service-0.3.7.dist-info}/RECORD
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
reasoning_deployment_service/__init__.py,sha256=xDuKt9gGviQiTV6vXBdkBvygnlAOIrwnUjVaMGZy0L4,670
|
|
2
|
-
reasoning_deployment_service/reasoning_deployment_service.py,sha256=
|
|
3
|
-
reasoning_deployment_service/runner.py,sha256=
|
|
2
|
+
reasoning_deployment_service/reasoning_deployment_service.py,sha256=HZLgyhwgg-MAc8mZkToTg5x9n-4eU7YrC54xZd0JYZc,38297
|
|
3
|
+
reasoning_deployment_service/runner.py,sha256=HH6EfiuC77Mtkqu7CE1VbjKC-D_wL3vMCpaE1VTa32c,5493
|
|
4
4
|
reasoning_deployment_service/cli_editor/__init__.py,sha256=bN8NPkw8riB92pj2lAwJZuEMOQIO_RRuge0ehnJTW1I,118
|
|
5
5
|
reasoning_deployment_service/cli_editor/api_client.py,sha256=Kzx5iYp0MmowggrSmPLE7I2kt1-8xvdGBAgde9a1gCY,33681
|
|
6
6
|
reasoning_deployment_service/cli_editor/cli_runner.py,sha256=1KkHtgAhVZ7VHQj7o76JibLHnr7NMUB-tieDX_KrAcY,18239
|
|
@@ -22,8 +22,8 @@ reasoning_deployment_service/gui_editor/src/ui/authorization_view.py,sha256=BoNc
|
|
|
22
22
|
reasoning_deployment_service/gui_editor/src/ui/reasoning_engine_view.py,sha256=tCvSPEf4dW0NRdAqfs3yT5Pa873gYeLzCMMIt2r2T4o,14644
|
|
23
23
|
reasoning_deployment_service/gui_editor/src/ui/reasoning_engines_view.py,sha256=IRjFlBbY98usAZa0roOonjvWQOsF6NBW4bBg_k8KnKI,7860
|
|
24
24
|
reasoning_deployment_service/gui_editor/src/ui/ui_components.py,sha256=HdQHy-oSZ3GobQ3FNdH7y_w3ANbFiuf2rMoflAmff0A,55366
|
|
25
|
-
reasoning_deployment_service-0.3.
|
|
26
|
-
reasoning_deployment_service-0.3.
|
|
27
|
-
reasoning_deployment_service-0.3.
|
|
28
|
-
reasoning_deployment_service-0.3.
|
|
29
|
-
reasoning_deployment_service-0.3.
|
|
25
|
+
reasoning_deployment_service-0.3.7.dist-info/METADATA,sha256=O64D_CXQKcexIEf1V84DYWa67XaFKTQ6kDwYxA6U4sw,5302
|
|
26
|
+
reasoning_deployment_service-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
+
reasoning_deployment_service-0.3.7.dist-info/entry_points.txt,sha256=onGKjR5ONTtRv3aqEtK863iw9Ty1kLcjfZlsplkRZrA,84
|
|
28
|
+
reasoning_deployment_service-0.3.7.dist-info/top_level.txt,sha256=GKuQS1xHUYLZbatw9DmcYdBxxLhWhhGkV4FmFxgKdp0,29
|
|
29
|
+
reasoning_deployment_service-0.3.7.dist-info/RECORD,,
|
{reasoning_deployment_service-0.3.6.dist-info → reasoning_deployment_service-0.3.7.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|