Skip to content

Commit d048c11

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
fix: Fix create_session AttributeError for agents without AdkApp
PiperOrigin-RevId: 899742948
1 parent 1f5af01 commit d048c11

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
@@ -1952,6 +1952,74 @@ def _run_agent(
19521952
os.environ["GOOGLE_CLOUD_LOCATION"] = original_location
19531953

19541954

1955+
def _create_agent_engine_session(
1956+
*,
1957+
agent_engine: types.AgentEngine,
1958+
user_id: str,
1959+
session_state: Optional[dict[str, Any]] = None,
1960+
) -> Any:
1961+
"""Creates a session for an agent engine and returns the session ID.
1962+
1963+
First attempts to use the agent engine's own `create_session` operation
1964+
(available for agents deployed via AdkApp). If the agent engine does not
1965+
have `create_session` registered, falls back to the managed Vertex AI
1966+
Sessions API.
1967+
1968+
Args:
1969+
agent_engine: The AgentEngine instance.
1970+
user_id: The user ID for the session.
1971+
session_state: Optional initial state for the session.
1972+
1973+
Returns:
1974+
The session ID string.
1975+
1976+
Raises:
1977+
RuntimeError: If the session could not be created via either path.
1978+
"""
1979+
try:
1980+
session = agent_engine.create_session( # type: ignore[attr-defined]
1981+
user_id=user_id,
1982+
state=session_state,
1983+
)
1984+
return session["id"]
1985+
except AttributeError as exc:
1986+
# Agent engine does not have create_session registered (e.g. deployed
1987+
# via Console, gcloud, or source code deployment without AdkApp).
1988+
# Fall back to the managed Vertex AI Sessions API.
1989+
logger.info(
1990+
"Agent engine does not have 'create_session' operation registered."
1991+
" Falling back to managed Sessions API."
1992+
)
1993+
if agent_engine.api_resource is None:
1994+
raise RuntimeError(
1995+
"Failed to create session: agent_engine.api_resource is None."
1996+
) from exc
1997+
if agent_engine.api_client is None:
1998+
raise RuntimeError(
1999+
"Failed to create session: agent_engine.api_client is None."
2000+
) from exc
2001+
operation = agent_engine.api_client.sessions.create(
2002+
name=agent_engine.api_resource.name,
2003+
user_id=user_id,
2004+
config=types.CreateAgentEngineSessionConfig(
2005+
session_state=session_state,
2006+
),
2007+
)
2008+
if operation.response and operation.response.name:
2009+
# Session name format:
2010+
# projects/{p}/locations/{l}/reasoningEngines/{re}/sessions/{id}
2011+
return operation.response.name.split("/")[-1]
2012+
elif operation.error:
2013+
raise RuntimeError(
2014+
f"Failed to create session via managed API: {operation.error}"
2015+
) from exc
2016+
else:
2017+
raise RuntimeError(
2018+
"Failed to create session via managed API: "
2019+
"operation returned no response."
2020+
) from exc
2021+
2022+
19552023
def _execute_agent_run_with_retry(
19562024
row: pd.Series,
19572025
contents: Union[genai_types.ContentListUnion, genai_types.ContentListUnionDict],
@@ -1963,9 +2031,10 @@ def _execute_agent_run_with_retry(
19632031
session_inputs = _get_session_inputs(row)
19642032
user_id = session_inputs.user_id
19652033
session_state = session_inputs.state
1966-
session = agent_engine.create_session( # type: ignore[attr-defined]
2034+
session_id = _create_agent_engine_session(
2035+
agent_engine=agent_engine,
19672036
user_id=user_id,
1968-
state=session_state,
2037+
session_state=session_state,
19692038
)
19702039
except KeyError as e:
19712040
return {"error": f"Failed to get all required agent engine inputs: {e}"}
@@ -1976,7 +2045,7 @@ def _execute_agent_run_with_retry(
19762045
responses = []
19772046
for event in agent_engine.stream_query( # type: ignore[attr-defined]
19782047
user_id=user_id,
1979-
session_id=session["id"],
2048+
session_id=session_id,
19802049
message=contents,
19812050
):
19822051
if event and CONTENT in event and PARTS in event[CONTENT]:

0 commit comments

Comments
 (0)