reasoning-deployment-service 0.7.2__py3-none-any.whl → 0.8.3__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.

@@ -1,4 +1,5 @@
1
1
  import json, os, subprocess, yaml, sys
2
+ import uuid
2
3
  import urllib.parse, vertexai, google.auth
3
4
  import requests as _requests
4
5
  from typing import Dict, Optional, Tuple
@@ -17,7 +18,6 @@ DISCOVERY_ENGINE_URL = "https://discoveryengine.googleapis.com/v1alpha"
17
18
 
18
19
  class ReasoningEngineDeploymentService:
19
20
  def __init__(self, root_agent: BaseAgent, deployment_environment: str="DEV"):
20
- # Setup logging
21
21
  self._setup_logging()
22
22
 
23
23
  self._check_required_files_exist()
@@ -98,10 +98,16 @@ class ReasoningEngineDeploymentService:
98
98
  def info(self, message: str):
99
99
  self.logger.info(f"[DEPLOYMENT SERVICE: INFO]: {message}")
100
100
 
101
- def _generate_authorization_id(self) -> str:
101
+ def _generate_authorization_id(self) -> Optional[str]:
102
102
  get_deployment_environment = os.getenv('AGENT_DEPLOYMENT_PIPELINE_ID', "LOCAL_RUN")
103
103
 
104
- return f"{get_deployment_environment}-{self._reasoning_engine_name}-{self._agent_space_engine}-auth".lower()
104
+ if self._authorization_override:
105
+ return self._authorization_override.lower()
106
+
107
+ if not self._authorization_override:
108
+ return None
109
+
110
+ return f"{get_deployment_environment}-{self._reasoning_engine_name}-{str(uuid.uuid4())}-auth".lower()
105
111
 
106
112
  def _load_runtime_variables(self):
107
113
  load_dotenv(dotenv_path=".env", override=True)
@@ -112,9 +118,15 @@ class ReasoningEngineDeploymentService:
112
118
  runtime_vars[key] = os.environ[key]
113
119
 
114
120
  runtime_vars.update(self._runtime_variable_definitions or {})
115
- runtime_vars.update({'AUTHORIZATION_ID': self._generate_authorization_id()})
121
+ local_auth_id = self._generate_authorization_id()
122
+
123
+ if self._use_authorization and local_auth_id:
124
+ runtime_vars.update({'AUTHORIZATION_ID': local_auth_id})
125
+ self._authorization_id = local_auth_id
126
+ runtime_vars.update({f"DEPLOYED_PROJECT_NUMBER": self._project_number})
127
+ runtime_vars.update({f"DEPLOYED_PROJECT_ID": self._project_id})
128
+ runtime_vars.update({f"AGENT_DEPLOYMENT_PIPELINE_ID": self._deployed_environment})
116
129
 
117
- self._authorization_id = runtime_vars.get('AUTHORIZATION_ID')
118
130
  self._environment_variables = runtime_vars
119
131
 
120
132
  def _check_required_files_exist(self):
@@ -213,8 +225,6 @@ class ReasoningEngineDeploymentService:
213
225
  DEV_OAUTH_CLIENT_SECRET="""
214
226
 
215
227
  path.write_text(template.strip() + "\n")
216
-
217
- # Also update .gitignore to include logs
218
228
  self._update_gitignore()
219
229
 
220
230
  return path
@@ -256,7 +266,6 @@ class ReasoningEngineDeploymentService:
256
266
 
257
267
  config = {
258
268
  "defaults": {
259
- "deployment_service": "0.7",
260
269
  "reasoning_engine": {
261
270
  "name": "reasoning-engine-dev",
262
271
  "description": "A reasoning engine for development"
@@ -295,11 +304,6 @@ class ReasoningEngineDeploymentService:
295
304
 
296
305
  try:
297
306
  config = config['defaults']
298
- deployment_service_version = config.get('deployment_service')
299
-
300
- if float(deployment_service_version) >= 0.7:
301
- raise RuntimeError(f"Unsupported deployment_service version: {deployment_service_version}. Expected minimum '0.7'")
302
-
303
307
  authorization = config['authorization']
304
308
  gemini_enterprise = config['gemini_enterprise']
305
309
  reasoning_engine = config['reasoning_engine']
@@ -308,13 +312,18 @@ class ReasoningEngineDeploymentService:
308
312
 
309
313
  reasoning_engine_name = reasoning_engine.get('name')
310
314
  reasoning_engine_description = reasoning_engine.get('description')
315
+ reasoning_engine_bypass = reasoning_engine.get('skip_build', False)
311
316
 
312
317
  gemini_enterprise_name = gemini_enterprise.get('name')
313
318
  gemini_enterprise_description = gemini_enterprise.get('description')
314
319
  gemini_enterprise_tool_description = gemini_enterprise.get('tool_description')
315
320
  gemini_enterprise_engine_id = gemini_enterprise.get('target_deployment_engine_id')
321
+ gemini_enterprise_icon_uri = gemini_enterprise.get('icon_uri')
322
+
316
323
 
317
- self._target_deployment_engine_id = gemini_enterprise_engine_id
324
+ self._reasoning_engine_bypass = reasoning_engine_bypass
325
+ self._icon_uri = gemini_enterprise_icon_uri
326
+ self._agent_space_engine = gemini_enterprise_engine_id or os.getenv(f"{self.deployment_env}_AGENT_SPACE_ENGINE")
318
327
  self._required_scopes = authorization.get('scopes', [])
319
328
  self._agent_folder = "agent"
320
329
  self._reasoning_engine_name = reasoning_engine_name
@@ -323,11 +332,12 @@ class ReasoningEngineDeploymentService:
323
332
  self._agent_space_description = gemini_enterprise_description
324
333
  self._agent_space_tool_description = gemini_enterprise_tool_description
325
334
  self._use_authorization = authorization.get('enabled', False)
335
+ self._authorization_override = authorization.get('authorization_id_override', None)
326
336
  except KeyError as e:
327
- raise RuntimeError(f"Missing required key in agent.yaml: {e}")
337
+ raise RuntimeError(f"Missing required key in agent.yaml: {e}. Your agent.yaml file is not valid for this deployment service version.")
328
338
 
329
339
  def _load_deployment_environment_variables(self, deployment_environment: str):
330
- required_vars = ['PROJECT_ID', 'PROJECT_NUMBER', 'PROJECT_LOCATION', 'STAGING_BUCKET', 'AGENT_SPACE_ENGINE']
340
+ required_vars = ['PROJECT_ID', 'PROJECT_NUMBER', 'PROJECT_LOCATION', 'STAGING_BUCKET']
331
341
 
332
342
  for var in required_vars:
333
343
  env_var = f"{deployment_environment}_{var}"
@@ -336,6 +346,9 @@ class ReasoningEngineDeploymentService:
336
346
 
337
347
  setattr(self, f"_{var.lower()}", os.getenv(env_var))
338
348
 
349
+ if not self._agent_space_engine:
350
+ raise RuntimeError(f"Missing AGENT_SPACE_ENGINE for deployment environment {deployment_environment}.")
351
+
339
352
  if self._use_authorization:
340
353
  required_auth_vars = ['OAUTH_CLIENT_ID', 'OAUTH_CLIENT_SECRET']
341
354
 
@@ -346,6 +359,8 @@ class ReasoningEngineDeploymentService:
346
359
 
347
360
  setattr(self, f"_{var.lower()}", os.getenv(env_var))
348
361
 
362
+ self._deployed_environment = os.getenv(f"AGENT_DEPLOYMENT_PIPELINE_ID", "unregistered_environment")
363
+
349
364
  def _check_requirements_file_present(self):
350
365
  if not os.path.exists("requirements.txt"):
351
366
  raise RuntimeError("Missing requirements.txt file")
@@ -422,7 +437,10 @@ class ReasoningEngineDeploymentService:
422
437
  },
423
438
  }
424
439
 
425
- if self._authorization_id:
440
+ if self._icon_uri:
441
+ payload["icon"] = {"uri": self._icon_uri}
442
+
443
+ if self._use_authorization and self._authorization_id:
426
444
  payload["adk_agent_definition"]["authorizations"] = [
427
445
  f"projects/{self._project_number}/locations/global/authorizations/{self._authorization_id}"
428
446
  ]
@@ -525,7 +543,7 @@ class ReasoningEngineDeploymentService:
525
543
  def _create_authorization(self) -> dict:
526
544
  read_authorizations = self._read_engine_deployment_record()
527
545
 
528
- if not self._authorization_id:
546
+ if not self._authorization_id or not self._use_authorization:
529
547
  self.warning("No authorization ID provided; skipping authorization creation.")
530
548
 
531
549
  return
@@ -608,8 +626,10 @@ class ReasoningEngineDeploymentService:
608
626
 
609
627
  return True
610
628
 
611
- def _delete_authorization(self):
612
- if not self._authorization_id:
629
+ def _delete_authorization(self, drop_authorization_for_refresh: Optional[str] = None):
630
+ auth_to_drop = drop_authorization_for_refresh or self._authorization_id
631
+
632
+ if not auth_to_drop:
613
633
  self.warning("No authorization ID provided; skipping deletion.")
614
634
  return
615
635
 
@@ -622,18 +642,21 @@ class ReasoningEngineDeploymentService:
622
642
 
623
643
  url = (
624
644
  f"{discovery_engine_url}/projects/{self._project_id}/locations/global/authorizations"
625
- f"?authorizationId={self._authorization_id}"
645
+ f"?authorizationId={auth_to_drop}"
626
646
  )
627
647
 
628
648
  r = self._http.delete(url, headers=headers, timeout=60)
629
649
 
630
650
  if r.status_code < 400:
651
+ if drop_authorization_for_refresh:
652
+ self.info(f"Authorization {drop_authorization_for_refresh} deleted successfully for refresh.")
653
+ return True
654
+
631
655
  self.info("Authorization deleted successfully.")
632
656
  self._authorization_id = None
633
657
  self._update_in_agent_space()
634
658
  return True
635
659
 
636
- # Log API failure details to file only
637
660
  with open(self.log_filename, 'a') as f:
638
661
  timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3]
639
662
  f.write(f"{timestamp} - ReasoningEngineDeployment - ERROR - Failed to delete authorization with status {r.status_code} {r.reason}\n")
@@ -646,9 +669,8 @@ class ReasoningEngineDeploymentService:
646
669
  except:
647
670
  pass
648
671
 
649
- # Terminal message - simple
672
+
650
673
  self.error("Failed to delete authorization")
651
- # This will also log the record file
652
674
  return False
653
675
 
654
676
  def one_deployment_with_everything_on_it(self, skip_engine_step=False):
@@ -786,6 +808,7 @@ class ReasoningEngineDeploymentService:
786
808
  new_description: Optional[str] = None,
787
809
  new_reasoning_engine: Optional[str] = None,
788
810
  new_authorizations: Optional[list[str]] = None,
811
+ icon_uri: Optional[str] = None,
789
812
  ) -> dict:
790
813
  """
791
814
  Safely patch metadata (displayName, description) and linkage fields
@@ -812,12 +835,15 @@ class ReasoningEngineDeploymentService:
812
835
  }
813
836
  }
814
837
 
838
+ if icon_uri:
839
+ agent_updates_body["icon"] = {"uri": icon_uri}
840
+
815
841
  self.info(agent_updates_body)
816
842
 
817
843
  headers = self._get_headers()
818
844
 
819
845
  update_mask = ["displayName", "description", "adk_agent_definition.tool_settings.tool_description",
820
- "adk_agent_definition.provisioned_reasoning_engine.reasoning_engine", "adk_agent_definition.authorizations"]
846
+ "adk_agent_definition.provisioned_reasoning_engine.reasoning_engine", "adk_agent_definition.authorizations", 'icon.uri']
821
847
  params = {"update_mask": ",".join(update_mask)}
822
848
  resp = self._http.patch(url, headers=headers, params=params, json=agent_updates_body, timeout=60)
823
849
 
@@ -851,6 +877,42 @@ class ReasoningEngineDeploymentService:
851
877
  r = self._http.patch(url, headers=headers, json=payload, timeout=60)
852
878
  r.raise_for_status()
853
879
  return r.json()
880
+
881
+ def detect_scope_change(self, auth_full_name, want_scopes) -> Optional[bool]:
882
+ auth_url = f"{DISCOVERY_ENGINE_URL}/{auth_full_name}"
883
+ hdrs = self._get_headers().copy()
884
+
885
+ if "Authorization" in hdrs:
886
+ hdrs["Authorization"] = "Bearer ***"
887
+
888
+ self.info(f"[AUTH] GET {auth_url} headers={json.dumps(hdrs)}")
889
+ r = self._http.get(auth_url, headers=self._get_headers(), timeout=60)
890
+ self.info(f"[AUTH] GET status={r.status_code} ct={r.headers.get('content-type','')}")
891
+
892
+ try:
893
+ self.info(f"[AUTH] GET body={json.dumps(r.json(), indent=2)[:4000]}")
894
+ except Exception:
895
+ self.info(f"[AUTH] GET text={(r.text or '')[:1000]}")
896
+ r.raise_for_status()
897
+
898
+ data = r.json() or {}
899
+ existing_uri = (((data.get("serverSideOauth2") or {}).get("authorizationUri")) or "")
900
+ self.info(f"[AUTH] existing authorizationUri={existing_uri!r}")
901
+ existing_scopes = set()
902
+
903
+ if existing_uri:
904
+ parsed = urlparse(existing_uri)
905
+ qs = parse_qs(parsed.query)
906
+ scope_str = (qs.get("scope", [""])[0] or "")
907
+ existing_scopes = set(scope_str.split())
908
+
909
+ self.info(
910
+ f"[AUTH] scopes existing={sorted(existing_scopes)} want={sorted(want_scopes)} "
911
+ f"missing={sorted(want_scopes - existing_scopes)} extra={sorted(existing_scopes - want_scopes)}"
912
+ )
913
+
914
+ if existing_scopes != want_scopes:
915
+ return True
854
916
 
855
917
  def one_github_deployment_to_go(self, skip_engine=False):
856
918
  """
@@ -868,6 +930,7 @@ class ReasoningEngineDeploymentService:
868
930
  )
869
931
 
870
932
  self._cicd_deploy = True
933
+ delete_old_authorization = False
871
934
  self.info(f"[INIT] vertexai.init(project={self._project_id}, location={self._project_location}, staging_bucket={self._staging_bucket})")
872
935
  vertexai.init(
873
936
  project=self._project_id,
@@ -879,7 +942,7 @@ class ReasoningEngineDeploymentService:
879
942
  engine_rn = self.find_engine_by_name(self._reasoning_engine_name)
880
943
  self.info(f"[ENGINE] find_engine_by_name -> {engine_rn}")
881
944
 
882
- if not skip_engine:
945
+ if not skip_engine and not self._reasoning_engine_bypass:
883
946
  if not engine_rn:
884
947
  self.info(f"[ENGINE] '{self._reasoning_engine_name}' not found. Creating...")
885
948
  self.create_reasoning_engine()
@@ -896,58 +959,34 @@ class ReasoningEngineDeploymentService:
896
959
 
897
960
  self.info(f"[ENGINE] final engine_rn={engine_rn}")
898
961
 
962
+ if not engine_rn:
963
+ self.error("[ENGINE] Reasoning engine required for Agent Space deployment.")
964
+ raise RuntimeError("Reasoning engine resolution failed.")
965
+
899
966
  auth_full_name = None
900
- if self._authorization_id:
967
+ if self._authorization_id and self._use_authorization:
901
968
  want_scopes = set(self._required_scopes or [])
902
969
  self.info(f"[AUTH] id={self._authorization_id} want_scopes={sorted(want_scopes)}")
903
970
  auth_full_name = self.find_authorization_by_id(self._authorization_id)
904
971
  self.info(f"[AUTH] find_authorization_by_id -> {auth_full_name}")
905
972
 
973
+ if auth_full_name and self.detect_scope_change(auth_full_name, want_scopes):
974
+ self.info(f"[AUTH] Scopes changed; patching authorization {self._authorization_id}...")
975
+ delete_old_authorization = auth_full_name
976
+ self._authorization_id = self._generate_authorization_id()
977
+ auth_full_name = None
978
+
906
979
  if not auth_full_name:
907
980
  self.info(f"[AUTH] '{self._authorization_id}' not found. Creating...")
908
981
  ok = self._create_authorization()
909
982
  self.info(f"[AUTH] _create_authorization -> {ok}")
910
983
  auth_full_name = self.find_authorization_by_id(self._authorization_id)
911
984
  self.info(f"[AUTH] post-create resolve -> {auth_full_name}")
985
+
912
986
  if not ok or not auth_full_name:
913
987
  self.error("[AUTH] Creation failed or did not resolve.")
988
+
914
989
  raise RuntimeError("Authorization creation failed.")
915
- else:
916
- auth_url = f"{DISCOVERY_ENGINE_URL}/{auth_full_name}"
917
- hdrs = self._get_headers().copy()
918
- if "Authorization" in hdrs:
919
- hdrs["Authorization"] = "Bearer ***"
920
- self.info(f"[AUTH] GET {auth_url} headers={json.dumps(hdrs)}")
921
- r = self._http.get(auth_url, headers=self._get_headers(), timeout=60)
922
- self.info(f"[AUTH] GET status={r.status_code} ct={r.headers.get('content-type','')}")
923
- try:
924
- self.info(f"[AUTH] GET body={json.dumps(r.json(), indent=2)[:4000]}")
925
- except Exception:
926
- self.info(f"[AUTH] GET text={(r.text or '')[:1000]}")
927
- r.raise_for_status()
928
-
929
- data = r.json() or {}
930
- existing_uri = (((data.get("serverSideOauth2") or {}).get("authorizationUri")) or "")
931
- self.info(f"[AUTH] existing authorizationUri={existing_uri!r}")
932
-
933
- existing_scopes = set()
934
- if existing_uri:
935
- parsed = urlparse(existing_uri)
936
- qs = parse_qs(parsed.query)
937
- scope_str = (qs.get("scope", [""])[0] or "")
938
- existing_scopes = set(scope_str.split())
939
-
940
- self.info(
941
- f"[AUTH] scopes existing={sorted(existing_scopes)} want={sorted(want_scopes)} "
942
- f"missing={sorted(want_scopes - existing_scopes)} extra={sorted(existing_scopes - want_scopes)}"
943
- )
944
-
945
- if existing_scopes != want_scopes:
946
- self.info("[AUTH] Scopes changed. Patching authorization...")
947
- self.update_authorization_scopes(self._authorization_id, list(want_scopes), self._oauth_client_id)
948
- self.info("[AUTH] Authorization updated.")
949
- else:
950
- self.info("[AUTH] Scopes unchanged; no update needed.")
951
990
  else:
952
991
  self.info("[AUTH] No authorization_id configured; skipping authorization step.")
953
992
 
@@ -980,7 +1019,13 @@ class ReasoningEngineDeploymentService:
980
1019
  new_authorizations=[
981
1020
  f"projects/{self._project_number}/locations/global/authorizations/{self._authorization_id}"
982
1021
  ] if self._authorization_id else None,
1022
+ icon_uri=self._icon_uri,
983
1023
  )
984
1024
  self.info(f"[AGENT] PATCH result -> {json.dumps(patched, indent=2)[:2000]}")
985
1025
 
1026
+
1027
+ if delete_old_authorization:
1028
+ self.info(f"[AUTH] Deleting old authorization {delete_old_authorization}...")
1029
+ self._delete_authorization(delete_old_authorization)
1030
+
986
1031
  self.info("GitHub deployment completed successfully.")
@@ -105,7 +105,7 @@ class Runner:
105
105
  @staticmethod
106
106
  def _check_proper_configuration():
107
107
  """Ensure required environment variables are set in .env.agent."""
108
- required_vars = ['PROJECT_ID', 'PROJECT_NUMBER', 'PROJECT_LOCATION', 'AGENT_SPACE_ENGINE']
108
+ required_vars = ['PROJECT_ID', 'PROJECT_NUMBER', 'PROJECT_LOCATION']
109
109
  load_dotenv(dotenv_path=".env.agent")
110
110
 
111
111
  ok = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reasoning-deployment-service
3
- Version: 0.7.2
3
+ Version: 0.8.3
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
@@ -1,6 +1,6 @@
1
1
  reasoning_deployment_service/__init__.py,sha256=xDuKt9gGviQiTV6vXBdkBvygnlAOIrwnUjVaMGZy0L4,670
2
- reasoning_deployment_service/reasoning_deployment_service.py,sha256=Wotzj8bEuDuV2fPaIGBMQGy8MRd8RSCC1QLwb5TZuew,43543
3
- reasoning_deployment_service/runner.py,sha256=qWN0t66lQ1G4ht48gIHSF2JvedcheHRu8PmUz5TaKTI,5619
2
+ reasoning_deployment_service/reasoning_deployment_service.py,sha256=8CLjzDYFKAwKr6Avd97YlJ1b43T8_Oa3Pqi0Xfim5C8,45298
3
+ reasoning_deployment_service/runner.py,sha256=A88IhRPPAWtqedWPWOVne3Lg16uXhXuqARKGD5ORsWc,5597
4
4
  reasoning_deployment_service/cli_editor/__init__.py,sha256=bN8NPkw8riB92pj2lAwJZuEMOQIO_RRuge0ehnJTW1I,118
5
5
  reasoning_deployment_service/cli_editor/api_client.py,sha256=bcuV0kEHxyNobqJ1k2Iwp73EaFjuOWa4XJ77MRrWQr0,33106
6
6
  reasoning_deployment_service/cli_editor/cli_runner.py,sha256=1KkHtgAhVZ7VHQj7o76JibLHnr7NMUB-tieDX_KrAcY,18239
@@ -21,8 +21,8 @@ reasoning_deployment_service/gui_editor/src/ui/authorization_view.py,sha256=BoNc
21
21
  reasoning_deployment_service/gui_editor/src/ui/reasoning_engine_view.py,sha256=T_kBop74wHv8W7tk9aY17ty44rLu8Dc-vRZdRvhmeH0,13317
22
22
  reasoning_deployment_service/gui_editor/src/ui/reasoning_engines_view.py,sha256=IRjFlBbY98usAZa0roOonjvWQOsF6NBW4bBg_k8KnKI,7860
23
23
  reasoning_deployment_service/gui_editor/src/ui/ui_components.py,sha256=HdQHy-oSZ3GobQ3FNdH7y_w3ANbFiuf2rMoflAmff0A,55366
24
- reasoning_deployment_service-0.7.2.dist-info/METADATA,sha256=kSaZPhGkT191mS50-JWw5NAk85MsBa8weuMeSti_ZAU,5302
25
- reasoning_deployment_service-0.7.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- reasoning_deployment_service-0.7.2.dist-info/entry_points.txt,sha256=onGKjR5ONTtRv3aqEtK863iw9Ty1kLcjfZlsplkRZrA,84
27
- reasoning_deployment_service-0.7.2.dist-info/top_level.txt,sha256=GKuQS1xHUYLZbatw9DmcYdBxxLhWhhGkV4FmFxgKdp0,29
28
- reasoning_deployment_service-0.7.2.dist-info/RECORD,,
24
+ reasoning_deployment_service-0.8.3.dist-info/METADATA,sha256=hNyOtztxXHUY45RW7jQFn_n_apls8D61leAm-88wCJ0,5302
25
+ reasoning_deployment_service-0.8.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ reasoning_deployment_service-0.8.3.dist-info/entry_points.txt,sha256=onGKjR5ONTtRv3aqEtK863iw9Ty1kLcjfZlsplkRZrA,84
27
+ reasoning_deployment_service-0.8.3.dist-info/top_level.txt,sha256=GKuQS1xHUYLZbatw9DmcYdBxxLhWhhGkV4FmFxgKdp0,29
28
+ reasoning_deployment_service-0.8.3.dist-info/RECORD,,