veadk-python 0.2.8__py3-none-any.whl → 0.2.10__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 veadk-python might be problematic. Click here for more details.

Files changed (49) hide show
  1. veadk/a2a/remote_ve_agent.py +63 -6
  2. veadk/agent.py +6 -0
  3. veadk/agent_builder.py +2 -3
  4. veadk/cli/cli.py +2 -0
  5. veadk/cli/cli_kb.py +75 -0
  6. veadk/cli/cli_prompt.py +9 -2
  7. veadk/cli/cli_web.py +7 -0
  8. veadk/configs/database_configs.py +9 -0
  9. veadk/consts.py +7 -0
  10. veadk/evaluation/adk_evaluator/adk_evaluator.py +5 -2
  11. veadk/evaluation/base_evaluator.py +36 -25
  12. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +5 -3
  13. veadk/integrations/__init__.py +13 -0
  14. veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +9 -3
  15. veadk/integrations/ve_tls/utils.py +1 -2
  16. veadk/integrations/ve_tls/ve_tls.py +9 -5
  17. veadk/integrations/ve_tos/ve_tos.py +538 -67
  18. veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
  19. veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +293 -0
  20. veadk/knowledgebase/backends/base_backend.py +4 -4
  21. veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +162 -50
  22. veadk/knowledgebase/entry.py +25 -0
  23. veadk/knowledgebase/knowledgebase.py +19 -4
  24. veadk/memory/__init__.py +1 -1
  25. veadk/memory/long_term_memory.py +45 -7
  26. veadk/memory/long_term_memory_backends/mem0_backend.py +144 -0
  27. veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +4 -8
  28. veadk/memory/short_term_memory.py +9 -3
  29. veadk/memory/short_term_memory_backends/postgresql_backend.py +3 -1
  30. veadk/runner.py +34 -26
  31. veadk/tools/builtin_tools/generate_image.py +389 -0
  32. veadk/tools/builtin_tools/image_edit.py +61 -16
  33. veadk/tools/builtin_tools/image_generate.py +56 -15
  34. veadk/tools/builtin_tools/video_generate.py +41 -41
  35. veadk/tools/builtin_tools/web_search.py +10 -3
  36. veadk/tools/load_knowledgebase_tool.py +14 -8
  37. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +6 -1
  38. veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +7 -0
  39. veadk/tracing/telemetry/exporters/apmplus_exporter.py +82 -2
  40. veadk/tracing/telemetry/exporters/inmemory_exporter.py +8 -2
  41. veadk/tracing/telemetry/opentelemetry_tracer.py +8 -2
  42. veadk/tracing/telemetry/telemetry.py +41 -5
  43. veadk/version.py +1 -1
  44. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/METADATA +15 -4
  45. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/RECORD +49 -42
  46. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/WHEEL +0 -0
  47. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/entry_points.txt +0 -0
  48. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/licenses/LICENSE +0 -0
  49. {veadk_python-0.2.8.dist-info → veadk_python-0.2.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from pydantic import BaseModel
16
+
17
+
18
+ class KnowledgebaseEntry(BaseModel):
19
+ """Represents a single entry in the knowledgebase."""
20
+
21
+ # The main content of the knowledgebase entry.
22
+ content: str
23
+
24
+ # Optional metadata associated with the entry.
25
+ metadata: dict | None = None
@@ -18,6 +18,7 @@ from pydantic import BaseModel, Field
18
18
  from typing_extensions import Union
19
19
 
20
20
  from veadk.knowledgebase.backends.base_backend import BaseKnowledgebaseBackend
21
+ from veadk.knowledgebase.entry import KnowledgebaseEntry
21
22
  from veadk.utils.logger import get_logger
22
23
 
23
24
  logger = get_logger(__name__)
@@ -72,8 +73,8 @@ class KnowledgeBase(BaseModel):
72
73
  """Configuration for the backend"""
73
74
 
74
75
  top_k: int = 10
75
- """Number of top similar documents to retrieve during search.
76
-
76
+ """Number of top similar documents to retrieve during search.
77
+
77
78
  Default is 10."""
78
79
 
79
80
  app_name: str = ""
@@ -130,11 +131,25 @@ class KnowledgeBase(BaseModel):
130
131
  """Add knowledge from text to knowledgebase"""
131
132
  return self._backend.add_from_text(text=text, **kwargs)
132
133
 
133
- def search(self, query: str, top_k: int = 0, **kwargs) -> list[str]:
134
+ def search(self, query: str, top_k: int = 0, **kwargs) -> list[KnowledgebaseEntry]:
134
135
  """Search knowledge from knowledgebase"""
135
136
  if top_k == 0:
136
137
  top_k = self.top_k
137
- return self._backend.search(query=query, top_k=top_k, **kwargs)
138
+
139
+ _entries = self._backend.search(query=query, top_k=top_k, **kwargs)
140
+
141
+ entries = []
142
+ for entry in _entries:
143
+ if isinstance(entry, KnowledgebaseEntry):
144
+ entries.append(entry)
145
+ elif isinstance(entry, str):
146
+ entries.append(KnowledgebaseEntry(content=entry))
147
+ else:
148
+ logger.error(
149
+ f"Unsupported entry type from backend search method: {type(entry)} with {entry}. Expected `KnowledgebaseEntry` or `str`. Skip for this entry."
150
+ )
151
+
152
+ return entries
138
153
 
139
154
  def __getattr__(self, name) -> Callable:
140
155
  """In case of knowledgebase have no backends' methods (`delete`, `list_chunks`, etc)
veadk/memory/__init__.py CHANGED
@@ -25,7 +25,7 @@ def __getattr__(name):
25
25
  from veadk.memory.short_term_memory import ShortTermMemory
26
26
 
27
27
  return ShortTermMemory
28
- if name == "LongTeremMemory":
28
+ if name == "LongTermMemory":
29
29
  from veadk.memory.long_term_memory import LongTermMemory
30
30
 
31
31
  return LongTermMemory
@@ -62,6 +62,12 @@ def _get_backend_cls(backend: str) -> type[BaseLongTermMemoryBackend]:
62
62
  )
63
63
 
64
64
  return RedisLTMBackend
65
+ case "mem0":
66
+ from veadk.memory.long_term_memory_backends.mem0_backend import (
67
+ Mem0LTMBackend,
68
+ )
69
+
70
+ return Mem0LTMBackend
65
71
 
66
72
  raise ValueError(f"Unsupported long term memory backend: {backend}")
67
73
 
@@ -72,7 +78,7 @@ def build_long_term_memory_index(app_name: str, user_id: str):
72
78
 
73
79
  class LongTermMemory(BaseMemoryService, BaseModel):
74
80
  backend: Union[
75
- Literal["local", "opensearch", "redis", "viking", "viking_mem"],
81
+ Literal["local", "opensearch", "redis", "viking", "viking_mem", "mem0"],
76
82
  BaseLongTermMemoryBackend,
77
83
  ] = "opensearch"
78
84
  """Long term memory backend type"""
@@ -88,6 +94,12 @@ class LongTermMemory(BaseMemoryService, BaseModel):
88
94
  user_id: str = ""
89
95
 
90
96
  def model_post_init(self, __context: Any) -> None:
97
+ if self.backend == "viking_mem":
98
+ logger.warning(
99
+ "The `viking_mem` backend is deprecated, please use `viking` instead."
100
+ )
101
+ self.backend = "viking"
102
+
91
103
  self._backend = None
92
104
 
93
105
  # Once user define a backend instance, use it directly
@@ -103,6 +115,14 @@ class LongTermMemory(BaseMemoryService, BaseModel):
103
115
  f"Initialized long term memory backend {self.backend} with config. We will ignore `app_name` and `user_id` if provided."
104
116
  )
105
117
  self._backend = _get_backend_cls(self.backend)(**self.backend_config)
118
+ _index = self.backend_config.get("index", None)
119
+ if _index:
120
+ self._index = _index
121
+ logger.info(f"Long term memory index set to {self._index}.")
122
+ else:
123
+ logger.warning(
124
+ "Cannot find index via backend_config, please set `index` parameter."
125
+ )
106
126
  return
107
127
 
108
128
  if self.app_name and self.user_id:
@@ -147,12 +167,6 @@ class LongTermMemory(BaseMemoryService, BaseModel):
147
167
  app_name = session.app_name
148
168
  user_id = session.user_id
149
169
 
150
- if self._index != build_long_term_memory_index(app_name, user_id):
151
- logger.warning(
152
- f"The `app_name` or `user_id` is different from the initialized one, skip add session to memory. Initialized index: {self._index}, current built index: {build_long_term_memory_index(app_name, user_id)}"
153
- )
154
- return
155
-
156
170
  if not self._backend and isinstance(self.backend, str):
157
171
  self._index = build_long_term_memory_index(app_name, user_id)
158
172
  self._backend = _get_backend_cls(self.backend)(
@@ -162,6 +176,13 @@ class LongTermMemory(BaseMemoryService, BaseModel):
162
176
  f"Initialize long term memory backend now, index is {self._index}"
163
177
  )
164
178
 
179
+ if not self._index and self._index != build_long_term_memory_index(
180
+ app_name, user_id
181
+ ):
182
+ logger.warning(
183
+ f"The `app_name` or `user_id` is different from the initialized one, skip add session to memory. Initialized index: {self._index}, current built index: {build_long_term_memory_index(app_name, user_id)}"
184
+ )
185
+ return
165
186
  event_strings = self._filter_and_convert_events(session.events)
166
187
 
167
188
  logger.info(
@@ -182,6 +203,23 @@ class LongTermMemory(BaseMemoryService, BaseModel):
182
203
  @override
183
204
  async def search_memory(self, *, app_name: str, user_id: str, query: str):
184
205
  # prevent model invoke `load_memory` before add session to this memory
206
+ if not self._backend and isinstance(self.backend, str):
207
+ self._index = build_long_term_memory_index(app_name, user_id)
208
+ self._backend = _get_backend_cls(self.backend)(
209
+ index=self._index, **self.backend_config if self.backend_config else {}
210
+ )
211
+ logger.info(
212
+ f"Initialize long term memory backend now, index is {self._index}"
213
+ )
214
+
215
+ if not self._index and self._index != build_long_term_memory_index(
216
+ app_name, user_id
217
+ ):
218
+ logger.warning(
219
+ f"The `app_name` or `user_id` is different from the initialized one. Initialized index: {self._index}, current built index: {build_long_term_memory_index(app_name, user_id)}. Search memory return empty list."
220
+ )
221
+ return SearchMemoryResponse(memories=[])
222
+
185
223
  if not self._backend:
186
224
  logger.error(
187
225
  "Long term memory backend is not initialized, cannot search memory."
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Any
16
+ from typing_extensions import override
17
+ from pydantic import Field
18
+
19
+ from veadk.configs.database_configs import Mem0Config
20
+
21
+
22
+ from veadk.memory.long_term_memory_backends.base_backend import (
23
+ BaseLongTermMemoryBackend,
24
+ )
25
+ from veadk.utils.logger import get_logger
26
+
27
+ logger = get_logger(__name__)
28
+
29
+ try:
30
+ from mem0 import MemoryClient
31
+
32
+ except ImportError:
33
+ logger.error(
34
+ "Failed to import mem0 or dotenv. Please install them with 'pip install mem0 '"
35
+ )
36
+ raise ImportError("Required packages not installed: mem0")
37
+
38
+
39
+ class Mem0LTMBackend(BaseLongTermMemoryBackend):
40
+ """Mem0 long term memory backend implementation"""
41
+
42
+ mem0_config: Mem0Config = Field(default_factory=Mem0Config)
43
+
44
+ def model_post_init(self, __context: Any) -> None:
45
+ """Initialize Mem0 client"""
46
+
47
+ try:
48
+ self._mem0_client = MemoryClient(
49
+ host=self.mem0_config.base_url, # mem0 endpoint
50
+ api_key=self.mem0_config.api_key, # mem0 API key
51
+ )
52
+ logger.info(
53
+ f"Initialized Mem0 client for host: {self.mem0_config.base_url}"
54
+ )
55
+ logger.info(f"Initialized Mem0 client for index: {self.index}")
56
+ except Exception as e:
57
+ logger.error(
58
+ f"Failed to initialize Mem0 client for host {self.mem0_config.base_url} : {str(e)}"
59
+ )
60
+ raise
61
+
62
+ def precheck_index_naming(self):
63
+ """Check if the index name is valid
64
+ For Mem0, there are no specific naming constraints
65
+ """
66
+ pass
67
+
68
+ @override
69
+ def save_memory(self, event_strings: list[str], **kwargs) -> bool:
70
+ """Save memory to Mem0
71
+
72
+ Args:
73
+ event_strings: List of event strings to save
74
+ **kwargs: Additional parameters, including 'user_id' for Mem0
75
+
76
+ Returns:
77
+ bool: True if saved successfully, False otherwise
78
+ """
79
+ user_id = kwargs.get("user_id", "default_user")
80
+
81
+ try:
82
+ logger.info(
83
+ f"Saving {len(event_strings)} events to Mem0 for user: {user_id}"
84
+ )
85
+
86
+ for event_string in event_strings:
87
+ # Save event string to Mem0
88
+ result = self._mem0_client.add(
89
+ [{"role": "user", "content": event_string}],
90
+ user_id=user_id,
91
+ output_format="v1.1",
92
+ async_mode=True,
93
+ )
94
+ logger.debug(f"Saved memory result: {result}")
95
+
96
+ logger.info(f"Successfully saved {len(event_strings)} events to Mem0")
97
+ return True
98
+ except Exception as e:
99
+ logger.error(f"Failed to save memory to Mem0: {str(e)}")
100
+ return False
101
+
102
+ @override
103
+ def search_memory(self, query: str, top_k: int, **kwargs) -> list[str]:
104
+ """Search memory from Mem0
105
+
106
+ Args:
107
+ query: Search query
108
+ top_k: Number of results to return
109
+ **kwargs: Additional parameters, including 'user_id' for Mem0
110
+
111
+ Returns:
112
+ list[str]: List of memory strings
113
+ """
114
+ user_id = kwargs.get("user_id", "default_user")
115
+
116
+ try:
117
+ logger.info(
118
+ f"Searching Mem0 for query: {query}, user: {user_id}, top_k: {top_k}"
119
+ )
120
+
121
+ memories = self._mem0_client.search(
122
+ query, user_id=user_id, output_format="v1.1", top_k=top_k
123
+ )
124
+
125
+ logger.debug(f"return relevant memories: {memories}")
126
+
127
+ memory_list = []
128
+ # 如果 memories 是列表,直接返回
129
+ if isinstance(memories, list):
130
+ for mem in memories:
131
+ if "memory" in mem:
132
+ memory_list.append(mem["memory"])
133
+ return memory_list
134
+
135
+ if memories.get("results", []):
136
+ for mem in memories["results"]:
137
+ if "memory" in mem:
138
+ memory_list.append(mem["memory"])
139
+
140
+ logger.info(f"Found {len(memory_list)} memories matching query: {query}")
141
+ return memory_list
142
+ except Exception as e:
143
+ logger.error(f"Failed to search memory from Mem0: {str(e)}")
144
+ return []
@@ -23,18 +23,14 @@ from typing_extensions import override
23
23
 
24
24
  import veadk.config # noqa E401
25
25
  from veadk.config import getenv
26
+ from veadk.integrations.ve_viking_db_memory.ve_viking_db_memory import (
27
+ VikingDBMemoryClient,
28
+ )
26
29
  from veadk.memory.long_term_memory_backends.base_backend import (
27
30
  BaseLongTermMemoryBackend,
28
31
  )
29
32
  from veadk.utils.logger import get_logger
30
33
 
31
- try:
32
- from mcp_server_vikingdb_memory.common.memory_client import VikingDBMemoryService
33
- except ImportError:
34
- raise ImportError(
35
- "Please install VeADK extensions\npip install veadk-python[extensions]"
36
- )
37
-
38
34
  logger = get_logger(__name__)
39
35
 
40
36
 
@@ -61,7 +57,7 @@ class VikingDBLTMBackend(BaseLongTermMemoryBackend):
61
57
  )
62
58
 
63
59
  def model_post_init(self, __context: Any) -> None:
64
- self._client = VikingDBMemoryService(
60
+ self._client = VikingDBMemoryClient(
65
61
  ak=self.volcengine_access_key,
66
62
  sk=self.volcengine_secret_key,
67
63
  region=self.region,
@@ -19,6 +19,7 @@ from google.adk.sessions import (
19
19
  BaseSessionService,
20
20
  DatabaseSessionService,
21
21
  InMemorySessionService,
22
+ Session,
22
23
  )
23
24
  from pydantic import BaseModel, Field, PrivateAttr
24
25
 
@@ -106,7 +107,7 @@ class ShortTermMemory(BaseModel):
106
107
  app_name: str,
107
108
  user_id: str,
108
109
  session_id: str,
109
- ) -> None:
110
+ ) -> Session | None:
110
111
  if isinstance(self._session_service, DatabaseSessionService):
111
112
  list_sessions_response = await self._session_service.list_sessions(
112
113
  app_name=app_name, user_id=user_id
@@ -122,7 +123,12 @@ class ShortTermMemory(BaseModel):
122
123
  )
123
124
  is None
124
125
  ):
125
- # create a new session for this running
126
- await self._session_service.create_session(
126
+ return await self._session_service.create_session(
127
127
  app_name=app_name, user_id=user_id, session_id=session_id
128
128
  )
129
+ else:
130
+ logger.info(
131
+ f"Session {session_id} already exists with app_name={app_name} user_id={user_id}."
132
+ )
133
+
134
+ return None
@@ -14,6 +14,7 @@
14
14
 
15
15
  from functools import cached_property
16
16
  from typing import Any
17
+ from venv import logger
17
18
 
18
19
  from google.adk.sessions import (
19
20
  BaseSessionService,
@@ -33,7 +34,8 @@ class PostgreSqlSTMBackend(BaseShortTermMemoryBackend):
33
34
  postgresql_config: PostgreSqlConfig = Field(default_factory=PostgreSqlConfig)
34
35
 
35
36
  def model_post_init(self, context: Any) -> None:
36
- self._db_url = f"postgresql+psycopg2://{self.postgresql_config.user}:{self.postgresql_config.password}@{self.postgresql_config.host}:{self.postgresql_config.port}/{self.postgresql_config.database}"
37
+ self._db_url = f"postgresql://{self.postgresql_config.user}:{self.postgresql_config.password}@{self.postgresql_config.host}:{self.postgresql_config.port}/{self.postgresql_config.database}"
38
+ logger.debug(self._db_url)
37
39
 
38
40
  @cached_property
39
41
  @override
veadk/runner.py CHANGED
@@ -12,8 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import asyncio
16
15
  import functools
16
+ import os
17
17
  from types import MethodType
18
18
  from typing import Union
19
19
 
@@ -47,7 +47,7 @@ RunnerMessage = Union[
47
47
  ]
48
48
 
49
49
 
50
- def pre_run_process(self, process_func, new_message, user_id, session_id):
50
+ async def pre_run_process(self, process_func, new_message, user_id, session_id):
51
51
  if new_message.parts:
52
52
  for part in new_message.parts:
53
53
  if (
@@ -55,13 +55,12 @@ def pre_run_process(self, process_func, new_message, user_id, session_id):
55
55
  and part.inline_data.mime_type == "image/png"
56
56
  and self.upload_inline_data_to_tos
57
57
  ):
58
- process_func(
58
+ await process_func(
59
59
  part,
60
60
  self.app_name,
61
61
  user_id,
62
62
  session_id,
63
63
  )
64
- return
65
64
 
66
65
 
67
66
  def post_run_process(self):
@@ -79,7 +78,7 @@ def intercept_new_message(process_func):
79
78
  new_message: types.Content,
80
79
  **kwargs,
81
80
  ):
82
- pre_run_process(self, process_func, new_message, user_id, session_id)
81
+ await pre_run_process(self, process_func, new_message, user_id, session_id)
83
82
 
84
83
  async for event in func(
85
84
  user_id=user_id,
@@ -137,27 +136,21 @@ def _convert_messages(
137
136
  return _messages
138
137
 
139
138
 
140
- def _upload_image_to_tos(
139
+ async def _upload_image_to_tos(
141
140
  part: genai.types.Part, app_name: str, user_id: str, session_id: str
142
141
  ) -> None:
143
142
  try:
144
143
  if part.inline_data and part.inline_data.display_name and part.inline_data.data:
145
144
  from veadk.integrations.ve_tos.ve_tos import VeTOS
146
145
 
146
+ filename = os.path.basename(part.inline_data.display_name)
147
+ object_key = f"{app_name}/{user_id}-{session_id}-{filename}"
147
148
  ve_tos = VeTOS()
148
-
149
- object_key, tos_url = ve_tos.build_tos_url(
150
- user_id=user_id,
151
- app_name=app_name,
152
- session_id=session_id,
153
- data_path=part.inline_data.display_name,
149
+ tos_url = ve_tos.build_tos_signed_url(object_key=object_key)
150
+ await ve_tos.async_upload_bytes(
151
+ object_key=object_key,
152
+ data=part.inline_data.data,
154
153
  )
155
-
156
- upload_task = ve_tos.upload(object_key, part.inline_data.data)
157
-
158
- if upload_task is not None:
159
- asyncio.create_task(upload_task)
160
-
161
154
  part.inline_data.display_name = tos_url
162
155
  except Exception as e:
163
156
  logger.error(f"Upload to TOS failed: {e}")
@@ -226,7 +219,7 @@ class Runner(ADKRunner):
226
219
  )
227
220
 
228
221
  self.run_async = MethodType(
229
- intercept_new_message(_upload_image_to_tos)(self.run_async), self
222
+ intercept_new_message(_upload_image_to_tos)(super().run_async), self
230
223
  )
231
224
 
232
225
  async def run(
@@ -256,15 +249,21 @@ class Runner(ADKRunner):
256
249
  )
257
250
 
258
251
  if self.short_term_memory:
259
- await self.short_term_memory.create_session(
260
- app_name=self.app_name, user_id=self.user_id, session_id=session_id
252
+ session = await self.short_term_memory.create_session(
253
+ app_name=self.app_name, user_id=user_id, session_id=session_id
254
+ )
255
+ assert session, (
256
+ f"Failed to create session with app_name={self.app_name}, user_id={user_id}, session_id={session_id}, "
257
+ )
258
+ logger.debug(
259
+ f"Auto create session: {session.id}, user_id: {session.user_id}, app_name: {self.app_name}"
261
260
  )
262
261
 
263
262
  final_output = ""
264
263
  for converted_message in converted_messages:
265
264
  try:
266
265
  async for event in self.run_async(
267
- user_id=self.user_id,
266
+ user_id=user_id,
268
267
  session_id=session_id,
269
268
  new_message=converted_message,
270
269
  run_config=run_config,
@@ -366,19 +365,28 @@ class Runner(ADKRunner):
366
365
  )
367
366
  return eval_set_path
368
367
 
369
- async def save_session_to_long_term_memory(self, session_id: str) -> None:
368
+ async def save_session_to_long_term_memory(
369
+ self, session_id: str, user_id: str = "", app_name: str = ""
370
+ ) -> None:
370
371
  if not self.long_term_memory:
371
372
  logger.warning("Long-term memory is not enabled. Failed to save session.")
372
373
  return
373
374
 
375
+ if not user_id:
376
+ user_id = self.user_id
377
+
378
+ if not app_name:
379
+ app_name = self.app_name
380
+
374
381
  session = await self.session_service.get_session(
375
- app_name=self.app_name,
376
- user_id=self.user_id,
382
+ app_name=app_name,
383
+ user_id=user_id,
377
384
  session_id=session_id,
378
385
  )
386
+
379
387
  if not session:
380
388
  logger.error(
381
- f"Session {session_id} not found in session service, cannot save to long-term memory."
389
+ f"Session {session_id} (app_name={app_name}, user_id={user_id}) not found in session service, cannot save to long-term memory."
382
390
  )
383
391
  return
384
392