agently 4.0.7__py3-none-any.whl → 4.0.7.2__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.
Files changed (37) hide show
  1. agently/_default_init.py +4 -0
  2. agently/_default_settings.yaml +3 -1
  3. agently/base.py +19 -1
  4. agently/builtins/agent_extensions/ChatSessionExtension.py +2 -2
  5. agently/builtins/agent_extensions/SessionExtension.py +294 -0
  6. agently/builtins/agent_extensions/__init__.py +1 -0
  7. agently/builtins/plugins/PromptGenerator/AgentlyPromptGenerator.py +57 -17
  8. agently/builtins/plugins/Session/AgentlyMemoSession.py +652 -0
  9. agently/builtins/tools/Browse.py +11 -3
  10. agently/builtins/tools/Cmd.py +112 -0
  11. agently/builtins/tools/Search.py +28 -2
  12. agently/builtins/tools/__init__.py +1 -0
  13. agently/core/Agent.py +7 -7
  14. agently/core/ModelRequest.py +6 -5
  15. agently/core/Prompt.py +1 -1
  16. agently/core/Session.py +85 -0
  17. agently/core/TriggerFlow/TriggerFlow.py +1 -1
  18. agently/core/TriggerFlow/process/BaseProcess.py +8 -4
  19. agently/integrations/chromadb.py +4 -4
  20. agently/types/data/__init__.py +2 -0
  21. agently/types/data/prompt.py +6 -1
  22. agently/types/data/tool.py +9 -0
  23. agently/types/plugins/BuiltInTool.py +22 -0
  24. agently/types/plugins/Session.py +159 -0
  25. agently/types/plugins/__init__.py +21 -0
  26. agently/types/plugins/base.py +1 -1
  27. agently/utils/AGENT_UTILS_GUIDE.md +175 -0
  28. agently/utils/DataFormatter.py +14 -4
  29. agently/utils/DataLocator.py +108 -31
  30. agently/utils/FunctionShifter.py +3 -2
  31. agently/utils/TimeInfo.py +22 -0
  32. agently/utils/__init__.py +1 -0
  33. agently-4.0.7.2.dist-info/METADATA +433 -0
  34. {agently-4.0.7.dist-info → agently-4.0.7.2.dist-info}/RECORD +36 -28
  35. {agently-4.0.7.dist-info → agently-4.0.7.2.dist-info}/WHEEL +1 -1
  36. agently-4.0.7.dist-info/METADATA +0 -194
  37. {agently-4.0.7.dist-info → agently-4.0.7.2.dist-info}/licenses/LICENSE +0 -0
agently/_default_init.py CHANGED
@@ -46,6 +46,10 @@ def _load_default_plugins(plugin_manager: "PluginManager"):
46
46
 
47
47
  plugin_manager.register("ToolManager", AgentlyToolManager)
48
48
 
49
+ from agently.builtins.plugins.Session.AgentlyMemoSession import AgentlyMemoSession
50
+
51
+ plugin_manager.register("Session", AgentlyMemoSession)
52
+
49
53
 
50
54
  def _load_default_settings(settings: "Settings"):
51
55
  settings.load("yaml_file", f"{str(Path(__file__).resolve().parent)}/_default_settings.yaml")
@@ -16,6 +16,7 @@
16
16
  storage:
17
17
  db_url: "sqlite+aiosqlite:///localstorage.db"
18
18
  prompt:
19
+ add_current_time: False
19
20
  role_mapping:
20
21
  system: system
21
22
  developer: developer
@@ -43,6 +44,7 @@ runtime:
43
44
  show_model_logs: False
44
45
  show_tool_logs: False
45
46
  show_trigger_flow_logs: False
47
+ httpx_log_level: "WARNING"
46
48
  plugins:
47
49
  ToolManager:
48
- activate: AgentlyToolManager
50
+ activate: AgentlyToolManager
agently/base.py CHANGED
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import logging
15
16
  from typing import Any, Literal, Type, TYPE_CHECKING, TypeVar, Generic, cast
16
17
 
17
18
  from agently.utils import Settings, create_logger, FunctionShifter, DataFormatter
@@ -37,6 +38,10 @@ _hook_default_event_handlers(event_center)
37
38
  async_system_message = event_center.async_system_message
38
39
  system_message = event_center.system_message
39
40
  logger = create_logger()
41
+ httpx_level_name = settings.get("runtime.httpx_log_level", "WARNING")
42
+ httpx_level = getattr(logging, str(httpx_level_name).upper(), logging.WARNING)
43
+ logging.getLogger("httpx").setLevel(httpx_level)
44
+ logging.getLogger("httpcore").setLevel(httpx_level)
40
45
  tool = Tool(plugin_manager, settings)
41
46
  _agently_messenger = event_center.create_messenger("Agently")
42
47
 
@@ -70,11 +75,13 @@ settings.update_mappings(
70
75
  "runtime.show_model_logs": True,
71
76
  "runtime.show_tool_logs": True,
72
77
  "runtime.show_trigger_flow_logs": True,
78
+ "runtime.httpx_log_level": "INFO",
73
79
  },
74
80
  False: {
75
81
  "runtime.show_model_logs": False,
76
82
  "runtime.show_tool_logs": False,
77
83
  "runtime.show_trigger_flow_logs": False,
84
+ "runtime.httpx_log_level": "WARNING",
78
85
  },
79
86
  }
80
87
  }
@@ -85,6 +92,7 @@ settings.update_mappings(
85
92
  # BaseAgent + Extensions = Agent
86
93
  from agently.builtins.agent_extensions import (
87
94
  ToolExtension,
95
+ SessionExtension,
88
96
  KeyWaiterExtension,
89
97
  AutoFuncExtension,
90
98
  ConfigurePromptExtension,
@@ -92,6 +100,7 @@ from agently.builtins.agent_extensions import (
92
100
 
93
101
 
94
102
  class Agent(
103
+ SessionExtension,
95
104
  ToolExtension,
96
105
  KeyWaiterExtension,
97
106
  AutoFuncExtension,
@@ -117,7 +126,16 @@ class AgentlyMain(Generic[A]):
117
126
  self.tool = tool
118
127
  self.AgentType = AgentType
119
128
 
120
- self.set_settings = self.settings.set_settings
129
+ def set_settings(key: str, value: "SerializableValue", *, auto_load_env: bool = False):
130
+ self.settings.set_settings(key, value, auto_load_env=auto_load_env)
131
+ if key in ("runtime.httpx_log_level", "debug"):
132
+ level_name = self.settings.get("runtime.httpx_log_level", "WARNING")
133
+ level = getattr(logging, str(level_name).upper(), logging.WARNING)
134
+ logging.getLogger("httpx").setLevel(level)
135
+ logging.getLogger("httpcore").setLevel(level)
136
+ return self
137
+
138
+ self.set_settings = set_settings
121
139
 
122
140
  def set_debug_console(self, debug_console_status: Literal["ON", "OFF"]):
123
141
  match debug_console_status:
@@ -16,7 +16,7 @@
16
16
  import uuid
17
17
  import yaml
18
18
 
19
- from typing import Any, Literal, TYPE_CHECKING, cast
19
+ from typing import Any, Literal, Sequence, TYPE_CHECKING, cast
20
20
 
21
21
  from agently.core import BaseAgent
22
22
  from agently.types.data import ChatMessage
@@ -208,7 +208,7 @@ class ChatSessionExtension(BaseAgent):
208
208
  self._record_output_mode = mode
209
209
  return self
210
210
 
211
- def add_chat_history(self, chat_history: list[dict[str, Any] | ChatMessage] | dict[str, Any] | ChatMessage):
211
+ def add_chat_history(self, chat_history: Sequence[dict[str, Any] | ChatMessage] | dict[str, Any] | ChatMessage):
212
212
  super().add_chat_history(chat_history)
213
213
  if self._activated_chat_session is not None:
214
214
  self.chat_session_runtime.set(self._activated_chat_session, self.prompt.get("chat_history"))
@@ -0,0 +1,294 @@
1
+ # Copyright 2023-2025 AgentEra(Agently.Tech)
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 __future__ import annotations
16
+
17
+ import inspect
18
+ import json
19
+ import yaml
20
+
21
+ from typing import Any, Sequence, TYPE_CHECKING
22
+
23
+ from agently.core import BaseAgent
24
+ from agently.core.Session import Session
25
+ from agently.utils import DataPathBuilder, FunctionShifter
26
+
27
+ if TYPE_CHECKING:
28
+ from agently.types.data import ChatMessage
29
+ from agently.core import Prompt
30
+ from agently.core.ModelRequest import ModelResponseResult
31
+ from agently.utils import Settings
32
+
33
+
34
+ class SessionExtension(BaseAgent):
35
+ def __init__(self, *args, **kwargs):
36
+ super().__init__(*args, **kwargs)
37
+ self._session: Session | None = None
38
+ self._record_handler = None
39
+
40
+ self.settings.setdefault("session.record.input.paths", [], inherit=True)
41
+ self.settings.setdefault("session.record.input.mode", "all", inherit=True)
42
+ self.settings.setdefault("session.record.output.paths", [], inherit=True)
43
+ self.settings.setdefault("session.record.output.mode", "all", inherit=True)
44
+
45
+ self.extension_handlers.append("request_prefixes", self._session_request_prefix)
46
+ self.extension_handlers.append("finally", self._session_finally)
47
+
48
+ # Sync wrappers for async-first methods
49
+ self.set_chat_history = FunctionShifter.syncify(self.async_set_chat_history)
50
+ self.add_chat_history = FunctionShifter.syncify(self.async_add_chat_history)
51
+ self.reset_chat_history = FunctionShifter.syncify(self.async_reset_chat_history)
52
+
53
+ @property
54
+ def session(self) -> Session | None:
55
+ return self._session
56
+
57
+ def attach_session(self, session: Session | None = None, *, mode: str | None = None):
58
+ if session is None:
59
+ session = Session(parent_settings=self.settings, agent=self, plugin_manager=self.plugin_manager)
60
+ if mode is not None:
61
+ session.configure(mode=mode)
62
+ self._session = session
63
+ return self
64
+
65
+ def enable_quick_session(self, session: Session | None = None, *, load: dict[str, Any] | str | None = None):
66
+ if self._session is None:
67
+ self.attach_session(session=session)
68
+ assert self._session is not None
69
+ if load is not None:
70
+ if isinstance(load, dict):
71
+ self._session.load_json(json.dumps(load, ensure_ascii=True))
72
+ elif isinstance(load, str):
73
+ self._session.load_yaml(load)
74
+ else:
75
+ raise TypeError(f"Invalid session load data type: {type(load)}")
76
+ # Minimal session: no memo, no truncation, only record full chat history.
77
+ self._session.configure(
78
+ mode="lite",
79
+ limit={"chars": 0, "messages": 0},
80
+ every_n_turns=0,
81
+ )
82
+ return self
83
+
84
+ def disable_quick_session(self):
85
+ return self.detach_session()
86
+
87
+ def detach_session(self):
88
+ self._session = None
89
+ return self
90
+
91
+ def set_record_handler(self, handler):
92
+ self._record_handler = handler
93
+ return self
94
+
95
+ def enable_session_lite(
96
+ self,
97
+ *,
98
+ chars: int | None = None,
99
+ messages: int | None = None,
100
+ every_n_turns: int | None = None,
101
+ session: Session | None = None,
102
+ ):
103
+ if self._session is None:
104
+ self.attach_session(session=session)
105
+ assert self._session is not None
106
+ self._session.use_lite(chars=chars, messages=messages, every_n_turns=every_n_turns)
107
+ return self
108
+
109
+ def enable_session_memo(
110
+ self,
111
+ *,
112
+ chars: int | None = None,
113
+ messages: int | None = None,
114
+ every_n_turns: int | None = None,
115
+ session: Session | None = None,
116
+ ):
117
+ if self._session is None:
118
+ self.attach_session(session=session)
119
+ assert self._session is not None
120
+ self._session.use_memo(chars=chars, messages=messages, every_n_turns=every_n_turns)
121
+ return self
122
+
123
+ def _normalize_chat_history(
124
+ self, chat_history: Sequence[dict[str, Any] | ChatMessage] | dict[str, Any] | ChatMessage
125
+ ):
126
+ if not isinstance(chat_history, list):
127
+ return [chat_history]
128
+ return chat_history
129
+
130
+ def _stringify_content(self, content: Any):
131
+ if content is None:
132
+ return None
133
+ if isinstance(content, dict):
134
+ return yaml.safe_dump(content)
135
+ if isinstance(content, (str, list)):
136
+ return content
137
+ return str(content)
138
+
139
+ def _collect_record_input(self, prompt: "Prompt") -> list[dict[str, Any]]:
140
+ record_input_paths = self.settings.get("session.record.input.paths", [])
141
+ record_input_mode = self.settings.get("session.record.input.mode", "all")
142
+
143
+ if isinstance(record_input_paths, str):
144
+ record_input_paths = [record_input_paths]
145
+
146
+ if not isinstance(record_input_paths, list) or len(record_input_paths) == 0:
147
+ user_input = prompt.get("input", None)
148
+ if user_input not in (None, ""):
149
+ content = self._stringify_content(user_input)
150
+ if content not in (None, ""):
151
+ return [{"role": "user", "content": content}]
152
+ try:
153
+ messages = prompt.to_messages()
154
+ except Exception:
155
+ return []
156
+ if messages:
157
+ content = messages[-1].get("content")
158
+ if content not in (None, ""):
159
+ return [{"role": "user", "content": content}]
160
+ return []
161
+
162
+ content: Any = {}
163
+ for entry in record_input_paths:
164
+ if isinstance(entry, str):
165
+ prompt_key, path = entry, None
166
+ elif isinstance(entry, (list, tuple)) and len(entry) >= 2:
167
+ prompt_key, path = entry[0], entry[1]
168
+ else:
169
+ continue
170
+
171
+ if path is None:
172
+ value = prompt.get(str(prompt_key))
173
+ if value is None:
174
+ continue
175
+ if record_input_mode == "first":
176
+ return [{"role": "user", "content": self._stringify_content(value)}]
177
+ if not isinstance(content, dict):
178
+ content = {}
179
+ content[prompt_key] = value
180
+ else:
181
+ prompt_value = prompt.get(str(prompt_key))
182
+ if isinstance(prompt_value, dict):
183
+ path_value = DataPathBuilder.get_value_by_path(prompt_value, str(path))
184
+ if path_value is None:
185
+ continue
186
+ if record_input_mode == "first":
187
+ return [{"role": "user", "content": self._stringify_content(path_value)}]
188
+ if not isinstance(content, dict):
189
+ content = {}
190
+ if prompt_key not in content or not isinstance(content[prompt_key], dict):
191
+ content[prompt_key] = {}
192
+ content[prompt_key][str(path)] = path_value
193
+
194
+ if content in (None, {}, []):
195
+ return []
196
+ return [{"role": "user", "content": self._stringify_content(content)}]
197
+
198
+ def _get_result_text(self, result: "ModelResponseResult"):
199
+ return result.full_result_data.get("text_result")
200
+
201
+ async def _collect_record_output(self, result: "ModelResponseResult") -> list[dict[str, Any]]:
202
+ record_output_paths = self.settings.get("session.record.output.paths", [])
203
+ record_output_mode = self.settings.get("session.record.output.mode", "all")
204
+
205
+ if isinstance(record_output_paths, str):
206
+ record_output_paths = [record_output_paths]
207
+
208
+ if not isinstance(record_output_paths, list) or len(record_output_paths) == 0:
209
+ assistant_text = self._get_result_text(result)
210
+ if assistant_text not in (None, ""):
211
+ return [{"role": "assistant", "content": assistant_text}]
212
+ return []
213
+
214
+ parsed_result = result.full_result_data.get("parsed_result")
215
+ if isinstance(parsed_result, dict):
216
+ content: Any = {}
217
+ for path in record_output_paths:
218
+ if not isinstance(path, str):
219
+ continue
220
+ path_key = DataPathBuilder.convert_slash_to_dot(path) if "/" in path else path
221
+ path_value = DataPathBuilder.get_value_by_path(parsed_result, path_key)
222
+ if path_value is None:
223
+ continue
224
+ if record_output_mode == "first":
225
+ return [{"role": "assistant", "content": self._stringify_content(path_value)}]
226
+ if not isinstance(content, dict):
227
+ content = {}
228
+ content[path_key] = path_value
229
+ if isinstance(content, dict) and content:
230
+ return [{"role": "assistant", "content": self._stringify_content(content)}]
231
+
232
+ assistant_text = self._get_result_text(result)
233
+ if assistant_text not in (None, ""):
234
+ return [{"role": "assistant", "content": assistant_text}]
235
+ return []
236
+
237
+ def _reset_session_history(self):
238
+ assert self._session is not None
239
+ self._session.full_chat_history = []
240
+ self._session.current_chat_history = []
241
+ self._session._turns = 0
242
+ self._session._last_resize_turn = 0
243
+ self._session._memo_cursor = 0
244
+
245
+ async def async_set_chat_history(self, chat_history: Sequence[dict[str, Any] | ChatMessage]):
246
+ if self._session is None:
247
+ return super().set_chat_history(chat_history)
248
+ self._reset_session_history()
249
+ for message in self._normalize_chat_history(chat_history):
250
+ self._session.append_message(message)
251
+ return self
252
+
253
+ async def async_add_chat_history(
254
+ self,
255
+ chat_history: Sequence[dict[str, Any] | ChatMessage] | dict[str, Any] | ChatMessage,
256
+ ):
257
+ if self._session is None:
258
+ return super().add_chat_history(chat_history)
259
+ messages = self._normalize_chat_history(chat_history)
260
+ if not messages:
261
+ return self
262
+ for message in messages:
263
+ self._session.append_message(message)
264
+ await self._session.async_resize()
265
+ return self
266
+
267
+ async def async_reset_chat_history(self):
268
+ if self._session is None:
269
+ return super().reset_chat_history()
270
+ self._reset_session_history()
271
+ return self
272
+
273
+ async def _session_request_prefix(self, prompt: "Prompt", _settings: "Settings"):
274
+ if self._session is None:
275
+ return
276
+ prompt.set("chat_history", self._session.current_chat_history)
277
+
278
+ async def _session_finally(self, result: "ModelResponseResult", _settings: "Settings"):
279
+ if self._session is None:
280
+ return
281
+ if self._record_handler is not None:
282
+ handler_result = self._record_handler(result)
283
+ if inspect.isawaitable(handler_result):
284
+ handler_result = await handler_result
285
+ if handler_result:
286
+ await self.async_add_chat_history(handler_result)
287
+ return
288
+
289
+ prompt = result.prompt
290
+ messages: list[dict[str, Any]] = []
291
+ messages.extend(self._collect_record_input(prompt))
292
+ messages.extend(await self._collect_record_output(result))
293
+ if messages:
294
+ await self.async_add_chat_history(messages)
@@ -14,6 +14,7 @@
14
14
 
15
15
 
16
16
  from .ToolExtension import ToolExtension
17
+ from .SessionExtension import SessionExtension
17
18
  from .KeyWaiterExtension import KeyWaiterExtension
18
19
  from .AutoFuncExtension import AutoFuncExtension
19
20
  from .ConfigurePromptExtension import ConfigurePromptExtension
@@ -35,7 +35,7 @@ from pydantic import (
35
35
 
36
36
  from agently.types.plugins import PromptGenerator
37
37
  from agently.types.data import PromptModel, ChatMessageContent, TextMessageContent
38
- from agently.utils import SettingsNamespace, DataFormatter
38
+ from agently.utils import SettingsNamespace, DataFormatter, TimeInfo
39
39
 
40
40
  if TYPE_CHECKING:
41
41
  from pydantic import BaseModel
@@ -47,7 +47,13 @@ if TYPE_CHECKING:
47
47
  class AgentlyPromptGenerator(PromptGenerator):
48
48
  name = "AgentlyPromptGenerator"
49
49
 
50
- DEFAULT_SETTINGS = {}
50
+ DEFAULT_SETTINGS = {
51
+ "$global": {
52
+ "prompt": {
53
+ "add_current_time": False,
54
+ }
55
+ }
56
+ }
51
57
 
52
58
  def __init__(
53
59
  self,
@@ -310,6 +316,9 @@ class AgentlyPromptGenerator(PromptGenerator):
310
316
  merged_role_mapping.update(role_mapping)
311
317
 
312
318
  prompt_text_list.append(f"{ (merged_role_mapping['user'] if 'user' in merged_role_mapping else 'user') }:")
319
+ if self.settings.get("prompt.add_current_time") is True:
320
+ prompt_text_list.append(f"[current time]: { TimeInfo.get_current_time() }")
321
+ prompt_text_list.append("")
313
322
 
314
323
  # system & developer
315
324
  if prompt_object.system:
@@ -394,6 +403,21 @@ class AgentlyPromptGenerator(PromptGenerator):
394
403
  if isinstance(role_mapping, dict):
395
404
  merged_role_mapping.update(role_mapping)
396
405
 
406
+ add_current_time = self.settings.get("prompt.add_current_time") is True
407
+ current_time_prefix = f"[current time]: { TimeInfo.get_current_time() }\n\n" if add_current_time else ""
408
+
409
+ def _prepend_current_time_text(text: str) -> str:
410
+ return f"{ current_time_prefix }{ text }" if add_current_time else text
411
+
412
+ def _prepend_current_time_rich(content: list[dict[str, Any]]) -> list[dict[str, Any]]:
413
+ if not add_current_time:
414
+ return content
415
+ for item in content:
416
+ if item.get("type") == "text":
417
+ item["text"] = _prepend_current_time_text(str(item.get("text", "")))
418
+ return content
419
+ return [{"type": "text", "text": current_time_prefix}] + content
420
+
397
421
  # system & developer
398
422
  if prompt_object.system:
399
423
  prompt_messages.append(
@@ -507,7 +531,9 @@ class AgentlyPromptGenerator(PromptGenerator):
507
531
  and not prompt_object.attachment
508
532
  ):
509
533
  role = merged_role_mapping["user"] if "user" in merged_role_mapping else "user"
510
- prompt_messages.append({"role": role, "content": DataFormatter.sanitize(prompt_object.input)})
534
+ prompt_messages.append(
535
+ {"role": role, "content": _prepend_current_time_text(DataFormatter.sanitize(prompt_object.input))}
536
+ )
511
537
  # special occasion: only attachment
512
538
  elif (
513
539
  prompt_object.attachment
@@ -521,19 +547,15 @@ class AgentlyPromptGenerator(PromptGenerator):
521
547
  ):
522
548
  role = merged_role_mapping["user"] if "user" in merged_role_mapping else "user"
523
549
  if rich_content:
524
- prompt_messages.append(
525
- {
526
- "role": role,
527
- "content": [content.model_dump() for content in prompt_object.attachment],
528
- }
529
- )
550
+ attachment_content = [content.model_dump() for content in prompt_object.attachment]
551
+ prompt_messages.append({"role": role, "content": _prepend_current_time_rich(attachment_content)})
530
552
  else:
531
553
  for one_content in prompt_object.attachment:
532
554
  if one_content.type == "text" and isinstance(one_content, TextMessageContent):
533
555
  prompt_messages.append(
534
556
  {
535
557
  "role": role,
536
- "content": one_content.text,
558
+ "content": _prepend_current_time_text(one_content.text),
537
559
  }
538
560
  )
539
561
  else:
@@ -550,7 +572,7 @@ class AgentlyPromptGenerator(PromptGenerator):
550
572
  user_message_content.append(
551
573
  {
552
574
  "type": "text",
553
- "text": "\n".join(self._generate_main_prompt(prompt_object)),
575
+ "text": _prepend_current_time_text("\n".join(self._generate_main_prompt(prompt_object))),
554
576
  }
555
577
  )
556
578
  # extend attachment content
@@ -581,7 +603,9 @@ class AgentlyPromptGenerator(PromptGenerator):
581
603
  f"Skipped content: unable to put attachment content into prompt messages when `rich_content` == False\n"
582
604
  f"Content: {str(one_content.model_dump())}"
583
605
  )
584
- prompt_messages.append({"role": role, "content": "\n".join(user_message_content)})
606
+ prompt_messages.append(
607
+ {"role": role, "content": _prepend_current_time_text("\n".join(user_message_content))}
608
+ )
585
609
 
586
610
  return prompt_messages
587
611
 
@@ -589,13 +613,29 @@ class AgentlyPromptGenerator(PromptGenerator):
589
613
  fields = {}
590
614
  validators = {}
591
615
 
592
- def ensure_list_and_cast(v: Any, target_type: type):
616
+ def ensure_list_and_cast(v: Any, target_type: Any):
593
617
  if not isinstance(v, list):
594
618
  v = [v]
595
- return [
596
- (target_type(item) if target_type is not Any and not isinstance(item, target_type) else item)
597
- for item in v
598
- ]
619
+ casted = []
620
+ for item in v:
621
+ if target_type is Any or not isinstance(target_type, type):
622
+ casted.append(item)
623
+ continue
624
+ target_type = cast(type, target_type)
625
+ if isinstance(item, target_type):
626
+ casted.append(item)
627
+ continue
628
+ if isinstance(item, Mapping):
629
+ model_validate = getattr(target_type, "model_validate", None)
630
+ if callable(model_validate):
631
+ casted.append(model_validate(item))
632
+ continue
633
+ parse_obj = getattr(target_type, "parse_obj", None)
634
+ if callable(parse_obj):
635
+ casted.append(parse_obj(item))
636
+ continue
637
+ casted.append(target_type(item))
638
+ return casted
599
639
 
600
640
  if isinstance(schema, Mapping):
601
641
  for field_name, field_type_schema in schema.items():