saf-sdk 0.0.1__tar.gz
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.
- saf_sdk-0.0.1/.gitignore +1 -0
- saf_sdk-0.0.1/PKG-INFO +41 -0
- saf_sdk-0.0.1/README.md +29 -0
- saf_sdk-0.0.1/pyproject.toml +85 -0
- saf_sdk-0.0.1/saf_sdk/__init__.py +8 -0
- saf_sdk-0.0.1/saf_sdk/adk.py +331 -0
saf_sdk-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ref/
|
saf_sdk-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: saf-sdk
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: SDK for wrapping third-party agent frameworks (Google ADK, etc.) for LangSmith deployment.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: langgraph>=0.4.10
|
|
7
|
+
Provides-Extra: google-adk
|
|
8
|
+
Requires-Dist: google-adk>=1.0.0; extra == 'google-adk'
|
|
9
|
+
Requires-Dist: uuid-utils>=0.6; extra == 'google-adk'
|
|
10
|
+
Requires-Dist: wrapt>=1.14; extra == 'google-adk'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# saf-sdk
|
|
14
|
+
|
|
15
|
+
> **Note:** `saf-sdk` is a temporary package name. It will be renamed to `deployments-wrap-sdk` later.
|
|
16
|
+
|
|
17
|
+
SDK for wrapping third-party agent frameworks (Google ADK, Strands, etc.) for deployment on LangSmith.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install saf-sdk[google-adk]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from google.adk.agents import Agent
|
|
29
|
+
from google.adk.runners import Runner
|
|
30
|
+
from saf_sdk.adk import LangsmithSessionService, wrap
|
|
31
|
+
|
|
32
|
+
agent = wrap(
|
|
33
|
+
Runner(
|
|
34
|
+
agent=Agent(name="my_agent", model="gemini-2.0-flash", instruction="..."),
|
|
35
|
+
app_name="my_app",
|
|
36
|
+
session_service=LangsmithSessionService(),
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Export `agent` from your graph module and deploy with `langgraph dev` or LangSmith.
|
saf_sdk-0.0.1/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# saf-sdk
|
|
2
|
+
|
|
3
|
+
> **Note:** `saf-sdk` is a temporary package name. It will be renamed to `deployments-wrap-sdk` later.
|
|
4
|
+
|
|
5
|
+
SDK for wrapping third-party agent frameworks (Google ADK, Strands, etc.) for deployment on LangSmith.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install saf-sdk[google-adk]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from google.adk.agents import Agent
|
|
17
|
+
from google.adk.runners import Runner
|
|
18
|
+
from saf_sdk.adk import LangsmithSessionService, wrap
|
|
19
|
+
|
|
20
|
+
agent = wrap(
|
|
21
|
+
Runner(
|
|
22
|
+
agent=Agent(name="my_agent", model="gemini-2.0-flash", instruction="..."),
|
|
23
|
+
app_name="my_app",
|
|
24
|
+
session_service=LangsmithSessionService(),
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Export `agent` from your graph module and deploy with `langgraph dev` or LangSmith.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# NOTE: "saf-sdk" is a temporary name. Will be renamed to "deployments-wrap-sdk" later.
|
|
7
|
+
name = "saf-sdk"
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
description = "SDK for wrapping third-party agent frameworks (Google ADK, etc.) for LangSmith deployment."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"langgraph>=0.4.10",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
google-adk = [
|
|
18
|
+
"google-adk>=1.0.0",
|
|
19
|
+
# wrapt is an undeclared transitive dep of langsmith's configure_google_adk;
|
|
20
|
+
# pinned here so users don't need to install it themselves.
|
|
21
|
+
"wrapt>=1.14",
|
|
22
|
+
"uuid-utils>=0.6",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.version]
|
|
26
|
+
path = "saf_sdk/__init__.py"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build]
|
|
29
|
+
include = ["saf_sdk"]
|
|
30
|
+
exclude = ["tests/", "docs/"]
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.sdist]
|
|
33
|
+
exclude = [
|
|
34
|
+
"tests/",
|
|
35
|
+
"docs/",
|
|
36
|
+
".github/",
|
|
37
|
+
"dist/",
|
|
38
|
+
"dist*/**",
|
|
39
|
+
".venv*/**",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
exclude = ["venv", "build", "dist"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff.lint]
|
|
46
|
+
select = [
|
|
47
|
+
"E",
|
|
48
|
+
"F",
|
|
49
|
+
"I",
|
|
50
|
+
"TID251",
|
|
51
|
+
"TID252",
|
|
52
|
+
"T20",
|
|
53
|
+
"TC",
|
|
54
|
+
"UP",
|
|
55
|
+
"B",
|
|
56
|
+
"SIM",
|
|
57
|
+
"RUF",
|
|
58
|
+
"S101",
|
|
59
|
+
"PLC0415",
|
|
60
|
+
]
|
|
61
|
+
ignore = [
|
|
62
|
+
"E501",
|
|
63
|
+
"B006",
|
|
64
|
+
"B904",
|
|
65
|
+
"SIM102",
|
|
66
|
+
]
|
|
67
|
+
per-file-ignores = {"tests/**" = ["S101", "B017"]}
|
|
68
|
+
|
|
69
|
+
[dependency-groups]
|
|
70
|
+
dev = [
|
|
71
|
+
"ruff>=0.11.12",
|
|
72
|
+
"pytest>=8.3.5",
|
|
73
|
+
"pytest-asyncio>=0.25.0",
|
|
74
|
+
"pytest-watcher>=0.4.3",
|
|
75
|
+
"saf-sdk[google-adk]",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.pytest-watcher]
|
|
79
|
+
now = true
|
|
80
|
+
delay = 3
|
|
81
|
+
patterns = ["*.py"]
|
|
82
|
+
|
|
83
|
+
[tool.pytest.ini_options]
|
|
84
|
+
addopts = "--strict-markers --strict-config --durations=5 -vv"
|
|
85
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""SAF SDK (temporary name — will be renamed to deployments-wrap-sdk).
|
|
2
|
+
|
|
3
|
+
Provides lightweight wrappers for deploying agents from popular frameworks
|
|
4
|
+
(Google ADK, Strands, etc.) to LangSmith without requiring knowledge of
|
|
5
|
+
LangGraph internals.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Google ADK integration for LangSmith Deployments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextvars
|
|
6
|
+
from typing import TYPE_CHECKING, Annotated, Any, Required, TypedDict
|
|
7
|
+
|
|
8
|
+
from langchain_core.messages import (
|
|
9
|
+
AIMessage,
|
|
10
|
+
AIMessageChunk,
|
|
11
|
+
AnyMessage,
|
|
12
|
+
HumanMessage,
|
|
13
|
+
)
|
|
14
|
+
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, LLMResult
|
|
15
|
+
from langchain_core.runnables.config import (
|
|
16
|
+
RunnableConfig,
|
|
17
|
+
get_async_callback_manager_for_config,
|
|
18
|
+
)
|
|
19
|
+
from langgraph.config import get_config
|
|
20
|
+
from langgraph.func import entrypoint
|
|
21
|
+
from langgraph.graph.message import add_messages
|
|
22
|
+
from uuid_utils.compat import uuid7
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from langsmith.integrations.google_adk import configure_google_adk
|
|
26
|
+
from langsmith.utils import tracing_is_enabled
|
|
27
|
+
except ImportError:
|
|
28
|
+
configure_google_adk = None # type: ignore[assignment, misc]
|
|
29
|
+
tracing_is_enabled = None # type: ignore[assignment]
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from google.adk.agents.run_config import RunConfig, StreamingMode
|
|
33
|
+
from google.adk.runners import Runner
|
|
34
|
+
from google.adk.sessions import BaseSessionService, Session
|
|
35
|
+
from google.adk.sessions.base_session_service import ListSessionsResponse
|
|
36
|
+
from google.genai.types import Content, Part
|
|
37
|
+
from langgraph.pregel import Pregel
|
|
38
|
+
|
|
39
|
+
# Runtime import guard — gives a clear error when google-adk is missing.
|
|
40
|
+
try:
|
|
41
|
+
from google.adk.agents.run_config import RunConfig, StreamingMode
|
|
42
|
+
from google.adk.sessions import BaseSessionService, Session
|
|
43
|
+
from google.adk.sessions.base_session_service import (
|
|
44
|
+
ListSessionsResponse,
|
|
45
|
+
)
|
|
46
|
+
from google.genai.types import Content, Part
|
|
47
|
+
except ImportError as _err:
|
|
48
|
+
raise ImportError(
|
|
49
|
+
"google-adk is required for ADK integration. "
|
|
50
|
+
"Install it with: pip install saf-sdk[google-adk]"
|
|
51
|
+
) from _err
|
|
52
|
+
|
|
53
|
+
__all__ = ["ADKInput", "LangsmithSessionService", "SessionData", "wrap"]
|
|
54
|
+
|
|
55
|
+
_DEFAULT_USER_ID = "anonymous"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ADKInput(TypedDict, total=False):
|
|
59
|
+
"""Default input schema for ADK agents wrapped with :func:`wrap`.
|
|
60
|
+
|
|
61
|
+
``messages`` is required; ``state_delta`` is optional and passed directly
|
|
62
|
+
to ``runner.run_async(state_delta=...)`` to update ADK session state.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
messages: Required[Annotated[list[AnyMessage], add_messages]]
|
|
66
|
+
state_delta: dict[str, Any]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Per-task session storage: each asyncio task (i.e. each concurrent invocation)
|
|
70
|
+
# gets its own isolated session so concurrent requests don't clobber each other.
|
|
71
|
+
_task_session: contextvars.ContextVar[Session | None] = contextvars.ContextVar(
|
|
72
|
+
"langsmith_adk_session", default=None
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SessionData(TypedDict, total=False):
|
|
77
|
+
"""Serialized ADK session stored in the LangGraph checkpoint."""
|
|
78
|
+
|
|
79
|
+
id: str
|
|
80
|
+
app_name: str
|
|
81
|
+
user_id: str
|
|
82
|
+
state: dict[str, Any]
|
|
83
|
+
events: list[dict[str, Any]]
|
|
84
|
+
last_update_time: float
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class LangsmithSessionService(BaseSessionService):
|
|
88
|
+
"""ADK SessionService backed by LangSmith's checkpoint system.
|
|
89
|
+
|
|
90
|
+
Use this as the ``session_service`` in your ``Runner``. Session state is
|
|
91
|
+
persisted automatically by the deployment's storage backend and survives
|
|
92
|
+
server restarts.
|
|
93
|
+
|
|
94
|
+
Example::
|
|
95
|
+
|
|
96
|
+
from saf_sdk.adk import LangsmithSessionService, wrap
|
|
97
|
+
|
|
98
|
+
agent = wrap(Runner(
|
|
99
|
+
agent=my_agent,
|
|
100
|
+
app_name="my_app",
|
|
101
|
+
session_service=LangsmithSessionService(),
|
|
102
|
+
))
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
# -- checkpoint bridge (called by wrap()) --------------------------------
|
|
106
|
+
|
|
107
|
+
def load(self, session_data: SessionData | None) -> None:
|
|
108
|
+
"""Restore session for the current invocation from checkpoint state."""
|
|
109
|
+
if session_data:
|
|
110
|
+
_task_session.set(Session.model_validate(session_data))
|
|
111
|
+
else:
|
|
112
|
+
_task_session.set(None)
|
|
113
|
+
|
|
114
|
+
def dump(self) -> SessionData | None:
|
|
115
|
+
"""Serialize the current session to a dict for checkpointing."""
|
|
116
|
+
session = _task_session.get()
|
|
117
|
+
if session is None:
|
|
118
|
+
return None
|
|
119
|
+
return session.model_dump(mode="json")
|
|
120
|
+
|
|
121
|
+
# -- BaseSessionService interface ----------------------------------------
|
|
122
|
+
|
|
123
|
+
async def get_session(
|
|
124
|
+
self,
|
|
125
|
+
*,
|
|
126
|
+
app_name: str,
|
|
127
|
+
user_id: str,
|
|
128
|
+
session_id: str,
|
|
129
|
+
config: Any = None,
|
|
130
|
+
) -> Session | None:
|
|
131
|
+
session = _task_session.get()
|
|
132
|
+
if (
|
|
133
|
+
session is not None
|
|
134
|
+
and session.app_name == app_name
|
|
135
|
+
and session.user_id == user_id
|
|
136
|
+
and session.id == session_id
|
|
137
|
+
):
|
|
138
|
+
return session
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
async def create_session(
|
|
142
|
+
self,
|
|
143
|
+
*,
|
|
144
|
+
app_name: str,
|
|
145
|
+
user_id: str,
|
|
146
|
+
session_id: str | None = None,
|
|
147
|
+
state: dict | None = None,
|
|
148
|
+
) -> Session:
|
|
149
|
+
session = Session(
|
|
150
|
+
id=session_id or "",
|
|
151
|
+
app_name=app_name,
|
|
152
|
+
user_id=user_id,
|
|
153
|
+
state=state or {},
|
|
154
|
+
events=[],
|
|
155
|
+
)
|
|
156
|
+
_task_session.set(session)
|
|
157
|
+
return session
|
|
158
|
+
|
|
159
|
+
async def delete_session(
|
|
160
|
+
self, *, app_name: str, user_id: str, session_id: str
|
|
161
|
+
) -> None:
|
|
162
|
+
session = _task_session.get()
|
|
163
|
+
if (
|
|
164
|
+
session is not None
|
|
165
|
+
and session.app_name == app_name
|
|
166
|
+
and session.user_id == user_id
|
|
167
|
+
and session.id == session_id
|
|
168
|
+
):
|
|
169
|
+
_task_session.set(None)
|
|
170
|
+
|
|
171
|
+
async def list_sessions(
|
|
172
|
+
self, *, app_name: str, user_id: str | None = None
|
|
173
|
+
) -> ListSessionsResponse:
|
|
174
|
+
session = _task_session.get()
|
|
175
|
+
if (
|
|
176
|
+
session is not None
|
|
177
|
+
and session.app_name == app_name
|
|
178
|
+
and (user_id is None or session.user_id == user_id)
|
|
179
|
+
):
|
|
180
|
+
return ListSessionsResponse(sessions=[session])
|
|
181
|
+
return ListSessionsResponse(sessions=[])
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def _stream_runner_events(
|
|
185
|
+
runner: Runner,
|
|
186
|
+
*,
|
|
187
|
+
app_name: str,
|
|
188
|
+
user_id: str,
|
|
189
|
+
session_id: str,
|
|
190
|
+
new_message: Content,
|
|
191
|
+
user_msg_text: str,
|
|
192
|
+
state_delta: dict[str, Any] | None,
|
|
193
|
+
config: RunnableConfig,
|
|
194
|
+
) -> tuple[str, str]:
|
|
195
|
+
"""Run the ADK agent and stream token events through LangGraph's callback system.
|
|
196
|
+
|
|
197
|
+
Bridges ADK's event stream into ``stream_mode="messages"`` so tokens appear
|
|
198
|
+
in useStream / Studio. Returns ``(response_text, msg_id)``.
|
|
199
|
+
"""
|
|
200
|
+
cm = get_async_callback_manager_for_config(config)
|
|
201
|
+
msg_id = str(uuid7())
|
|
202
|
+
llm_runs = await cm.on_chat_model_start(
|
|
203
|
+
serialized={"name": app_name},
|
|
204
|
+
messages=[[HumanMessage(content=user_msg_text)]],
|
|
205
|
+
run_id=uuid7(),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
_run_config = RunConfig(streaming_mode=StreamingMode.SSE)
|
|
209
|
+
|
|
210
|
+
response_parts: list[str] = []
|
|
211
|
+
final_parts: list[str] = []
|
|
212
|
+
async for event in runner.run_async(
|
|
213
|
+
user_id=user_id,
|
|
214
|
+
session_id=session_id,
|
|
215
|
+
new_message=new_message,
|
|
216
|
+
state_delta=state_delta,
|
|
217
|
+
run_config=_run_config,
|
|
218
|
+
):
|
|
219
|
+
if not event.content or not event.content.parts:
|
|
220
|
+
continue
|
|
221
|
+
if event.partial:
|
|
222
|
+
for part in event.content.parts:
|
|
223
|
+
if part.text:
|
|
224
|
+
response_parts.append(part.text)
|
|
225
|
+
chunk = ChatGenerationChunk(
|
|
226
|
+
message=AIMessageChunk(content=part.text, id=msg_id)
|
|
227
|
+
)
|
|
228
|
+
for llm_run in llm_runs:
|
|
229
|
+
await llm_run.on_llm_new_token(part.text, chunk=chunk)
|
|
230
|
+
else:
|
|
231
|
+
for part in event.content.parts:
|
|
232
|
+
if part.text:
|
|
233
|
+
final_parts.append(part.text)
|
|
234
|
+
|
|
235
|
+
response_text = "".join(response_parts) or "".join(final_parts)
|
|
236
|
+
|
|
237
|
+
for llm_run in llm_runs:
|
|
238
|
+
await llm_run.on_llm_end(
|
|
239
|
+
LLMResult(
|
|
240
|
+
generations=[
|
|
241
|
+
[
|
|
242
|
+
ChatGeneration(
|
|
243
|
+
message=AIMessageChunk(content=response_text, id=msg_id)
|
|
244
|
+
)
|
|
245
|
+
]
|
|
246
|
+
]
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return response_text, msg_id
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def wrap(runner: Runner) -> Pregel:
|
|
254
|
+
"""Wrap a Google ADK Runner for deployment on LangSmith.
|
|
255
|
+
|
|
256
|
+
The runner's ``session_service`` must be a ``LangsmithSessionService``.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
runner: A configured ``google.adk.runners.Runner`` instance.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
A LangGraph-compatible agent ready to be exported from your graph module.
|
|
263
|
+
"""
|
|
264
|
+
if tracing_is_enabled is not None and tracing_is_enabled():
|
|
265
|
+
configure_google_adk()
|
|
266
|
+
|
|
267
|
+
if not isinstance(runner.session_service, LangsmithSessionService):
|
|
268
|
+
raise TypeError(
|
|
269
|
+
f"runner.session_service must be a LangsmithSessionService, "
|
|
270
|
+
f"got {type(runner.session_service).__name__}. "
|
|
271
|
+
f"Replace it with: session_service=LangsmithSessionService()"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
app_name = runner.app_name
|
|
275
|
+
output_key: str | None = getattr(runner.agent, "output_key", None)
|
|
276
|
+
|
|
277
|
+
async def _run(
|
|
278
|
+
payload: ADKInput,
|
|
279
|
+
previous: dict | None = None,
|
|
280
|
+
) -> entrypoint.final[dict, dict]:
|
|
281
|
+
config = get_config()
|
|
282
|
+
configurable = config.get("configurable", {})
|
|
283
|
+
session_id = configurable.get("thread_id", "default")
|
|
284
|
+
user_id = configurable.get("langgraph_auth_user_id") or _DEFAULT_USER_ID
|
|
285
|
+
|
|
286
|
+
runner.session_service.load(previous or {})
|
|
287
|
+
|
|
288
|
+
session = await runner.session_service.get_session(
|
|
289
|
+
app_name=app_name, user_id=user_id, session_id=session_id
|
|
290
|
+
)
|
|
291
|
+
if session is None:
|
|
292
|
+
await runner.session_service.create_session(
|
|
293
|
+
app_name=app_name, user_id=user_id, session_id=session_id
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if not payload["messages"]:
|
|
297
|
+
raise ValueError("messages must not be empty")
|
|
298
|
+
user_msg = HumanMessage(content=payload["messages"][-1]["content"]).text
|
|
299
|
+
new_message = Content(role="user", parts=[Part(text=user_msg)])
|
|
300
|
+
|
|
301
|
+
response_text, msg_id = await _stream_runner_events(
|
|
302
|
+
runner,
|
|
303
|
+
app_name=app_name,
|
|
304
|
+
user_id=user_id,
|
|
305
|
+
session_id=session_id,
|
|
306
|
+
new_message=new_message,
|
|
307
|
+
user_msg_text=user_msg,
|
|
308
|
+
state_delta=payload.get("state_delta"),
|
|
309
|
+
config=config,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
session_data = runner.session_service.dump()
|
|
313
|
+
value: dict[str, Any] = {
|
|
314
|
+
"messages": [AIMessage(content=response_text, id=msg_id)],
|
|
315
|
+
"_adk_session": session_data,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if output_key and session_data:
|
|
319
|
+
output_val = session_data.get("state", {}).get(output_key)
|
|
320
|
+
if output_val is not None:
|
|
321
|
+
value[output_key] = output_val
|
|
322
|
+
|
|
323
|
+
return entrypoint.final(
|
|
324
|
+
value=value,
|
|
325
|
+
save=session_data,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
_run.__name__ = app_name
|
|
329
|
+
_run.__annotations__["payload"] = ADKInput
|
|
330
|
+
_run.__annotations__["return"] = entrypoint.final[dict, dict]
|
|
331
|
+
return entrypoint(name=app_name)(_run)
|