Skip to content

Commit 3e85f64

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
fix: Fix create_session AttributeError for agents without AdkApp
PiperOrigin-RevId: 900371738
1 parent 37f72e5 commit 3e85f64

2 files changed

Lines changed: 160 additions & 3 deletions

File tree

tests/unit/vertexai/genai/test_evals.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3144,6 +3144,94 @@ def test_run_inference_with_agent_engine_with_response_column_raises_error(
31443144
"'intermediate_events' or 'response' columns"
31453145
) in str(excinfo.value)
31463146

3147+
@mock.patch.object(_evals_utils, "EvalDatasetLoader")
3148+
@mock.patch("vertexai._genai._evals_common.vertexai.Client")
3149+
def test_run_inference_with_agent_engine_falls_back_to_managed_sessions_api(
3150+
self,
3151+
mock_vertexai_client,
3152+
mock_eval_dataset_loader,
3153+
):
3154+
"""Tests that run_inference falls back to the managed Sessions API
3155+
when the agent engine does not have create_session registered."""
3156+
mock_df = pd.DataFrame(
3157+
{
3158+
"prompt": ["agent prompt"],
3159+
"session_inputs": [
3160+
{
3161+
"user_id": "123",
3162+
"state": {"a": "1"},
3163+
}
3164+
],
3165+
}
3166+
)
3167+
mock_eval_dataset_loader.return_value.load.return_value = mock_df.to_dict(
3168+
orient="records"
3169+
)
3170+
3171+
# Create a mock agent engine WITHOUT create_session (simulates agents
3172+
# deployed via Console, gcloud, or source code deployment).
3173+
mock_agent_engine = mock.Mock(
3174+
spec=["api_client", "api_resource", "stream_query"],
3175+
)
3176+
mock_agent_engine.api_resource.name = (
3177+
"projects/test-project/locations/us-central1/reasoningEngines/123"
3178+
)
3179+
3180+
# Mock the managed Sessions API to return a session.
3181+
mock_session_operation = mock.Mock()
3182+
mock_session_operation.response.name = (
3183+
"projects/test-project/locations/us-central1"
3184+
"/reasoningEngines/123/sessions/managed-session-1"
3185+
)
3186+
mock_agent_engine.api_client.sessions.create.return_value = (
3187+
mock_session_operation
3188+
)
3189+
3190+
stream_query_return_value = [
3191+
{
3192+
"id": "1",
3193+
"content": {"parts": [{"text": "intermediate1"}]},
3194+
"timestamp": 123,
3195+
"author": "model",
3196+
},
3197+
{
3198+
"id": "2",
3199+
"content": {"parts": [{"text": "agent response"}]},
3200+
"timestamp": 124,
3201+
"author": "model",
3202+
},
3203+
]
3204+
mock_agent_engine.stream_query.return_value = iter(stream_query_return_value)
3205+
mock_vertexai_client.return_value.agent_engines.get.return_value = (
3206+
mock_agent_engine
3207+
)
3208+
3209+
inference_result = self.client.evals.run_inference(
3210+
agent="projects/test-project/locations/us-central1/reasoningEngines/123",
3211+
src=mock_df,
3212+
)
3213+
3214+
# Verify the managed Sessions API was called as fallback.
3215+
mock_agent_engine.api_client.sessions.create.assert_called_once_with(
3216+
name="projects/test-project/locations/us-central1/reasoningEngines/123",
3217+
user_id="123",
3218+
config=vertexai_genai_types.CreateAgentEngineSessionConfig(
3219+
session_state={"a": "1"},
3220+
),
3221+
)
3222+
3223+
# Verify stream_query was called with the session ID extracted from
3224+
# the managed session's resource name.
3225+
mock_agent_engine.stream_query.assert_called_once_with(
3226+
user_id="123",
3227+
session_id="managed-session-1",
3228+
message="agent prompt",
3229+
)
3230+
3231+
# Verify the inference results are correct.
3232+
assert inference_result.eval_dataset_df["response"].iloc[0] == "agent response"
3233+
assert inference_result.candidate_name == "agent_engine_0"
3234+
31473235
@mock.patch.object(_evals_utils, "EvalDatasetLoader")
31483236
@mock.patch("vertexai._genai._evals_common.InMemorySessionService") # fmt: skip
31493237
@mock.patch("vertexai._genai._evals_common.Runner")

vertexai/_genai/_evals_common.py

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,74 @@ def _run_agent(
19641964
os.environ["GOOGLE_CLOUD_LOCATION"] = original_location
19651965

19661966

1967+
def _create_agent_engine_session(
1968+
*,
1969+
agent_engine: types.AgentEngine,
1970+
user_id: str,
1971+
session_state: Optional[dict[str, Any]] = None,
1972+
) -> Any:
1973+
"""Creates a session for an agent engine and returns the session ID.
1974+
1975+
First attempts to use the agent engine's own `create_session` operation
1976+
(available for agents deployed via AdkApp). If the agent engine does not
1977+
have `create_session` registered, falls back to the managed Vertex AI
1978+
Sessions API.
1979+
1980+
Args:
1981+
agent_engine: The AgentEngine instance.
1982+
user_id: The user ID for the session.
1983+
session_state: Optional initial state for the session.
1984+
1985+
Returns:
1986+
The session ID string.
1987+
1988+
Raises:
1989+
RuntimeError: If the session could not be created via either path.
1990+
"""
1991+
try:
1992+
session = agent_engine.create_session( # type: ignore[attr-defined]
1993+
user_id=user_id,
1994+
state=session_state,
1995+
)
1996+
return session["id"]
1997+
except AttributeError as exc:
1998+
# Agent engine does not have create_session registered (e.g. deployed
1999+
# via Console, gcloud, or source code deployment without AdkApp).
2000+
# Fall back to the managed Vertex AI Sessions API.
2001+
logger.info(
2002+
"Agent engine does not have 'create_session' operation registered."
2003+
" Falling back to managed Sessions API."
2004+
)
2005+
if agent_engine.api_resource is None:
2006+
raise RuntimeError(
2007+
"Failed to create session: agent_engine.api_resource is None."
2008+
) from exc
2009+
if agent_engine.api_client is None:
2010+
raise RuntimeError(
2011+
"Failed to create session: agent_engine.api_client is None."
2012+
) from exc
2013+
operation = agent_engine.api_client.sessions.create(
2014+
name=agent_engine.api_resource.name,
2015+
user_id=user_id,
2016+
config=types.CreateAgentEngineSessionConfig(
2017+
session_state=session_state,
2018+
),
2019+
)
2020+
if operation.response and operation.response.name:
2021+
# Session name format:
2022+
# projects/{p}/locations/{l}/reasoningEngines/{re}/sessions/{id}
2023+
return operation.response.name.split("/")[-1]
2024+
elif operation.error:
2025+
raise RuntimeError(
2026+
f"Failed to create session via managed API: {operation.error}"
2027+
) from exc
2028+
else:
2029+
raise RuntimeError(
2030+
"Failed to create session via managed API: "
2031+
"operation returned no response."
2032+
) from exc
2033+
2034+
19672035
def _execute_agent_run_with_retry(
19682036
row: pd.Series,
19692037
contents: Union[genai_types.ContentListUnion, genai_types.ContentListUnionDict],
@@ -1975,9 +2043,10 @@ def _execute_agent_run_with_retry(
19752043
session_inputs = _get_session_inputs(row)
19762044
user_id = session_inputs.user_id
19772045
session_state = session_inputs.state
1978-
session = agent_engine.create_session( # type: ignore[attr-defined]
2046+
session_id = _create_agent_engine_session(
2047+
agent_engine=agent_engine,
19792048
user_id=user_id,
1980-
state=session_state,
2049+
session_state=session_state,
19812050
)
19822051
except KeyError as e:
19832052
return {"error": f"Failed to get all required agent engine inputs: {e}"}
@@ -1988,7 +2057,7 @@ def _execute_agent_run_with_retry(
19882057
responses = []
19892058
for event in agent_engine.stream_query( # type: ignore[attr-defined]
19902059
user_id=user_id,
1991-
session_id=session["id"],
2060+
session_id=session_id,
19922061
message=contents,
19932062
):
19942063
if event and CONTENT in event and PARTS in event[CONTENT]:

0 commit comments

Comments
 (0)