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.
- agently/_default_init.py +4 -0
- agently/_default_settings.yaml +3 -1
- agently/base.py +19 -1
- agently/builtins/agent_extensions/ChatSessionExtension.py +2 -2
- agently/builtins/agent_extensions/SessionExtension.py +294 -0
- agently/builtins/agent_extensions/__init__.py +1 -0
- agently/builtins/plugins/PromptGenerator/AgentlyPromptGenerator.py +57 -17
- agently/builtins/plugins/Session/AgentlyMemoSession.py +652 -0
- agently/builtins/tools/Browse.py +11 -3
- agently/builtins/tools/Cmd.py +112 -0
- agently/builtins/tools/Search.py +28 -2
- agently/builtins/tools/__init__.py +1 -0
- agently/core/Agent.py +7 -7
- agently/core/ModelRequest.py +6 -5
- agently/core/Prompt.py +1 -1
- agently/core/Session.py +85 -0
- agently/core/TriggerFlow/TriggerFlow.py +1 -1
- agently/core/TriggerFlow/process/BaseProcess.py +8 -4
- agently/integrations/chromadb.py +4 -4
- agently/types/data/__init__.py +2 -0
- agently/types/data/prompt.py +6 -1
- agently/types/data/tool.py +9 -0
- agently/types/plugins/BuiltInTool.py +22 -0
- agently/types/plugins/Session.py +159 -0
- agently/types/plugins/__init__.py +21 -0
- agently/types/plugins/base.py +1 -1
- agently/utils/AGENT_UTILS_GUIDE.md +175 -0
- agently/utils/DataFormatter.py +14 -4
- agently/utils/DataLocator.py +108 -31
- agently/utils/FunctionShifter.py +3 -2
- agently/utils/TimeInfo.py +22 -0
- agently/utils/__init__.py +1 -0
- agently-4.0.7.2.dist-info/METADATA +433 -0
- {agently-4.0.7.dist-info → agently-4.0.7.2.dist-info}/RECORD +36 -28
- {agently-4.0.7.dist-info → agently-4.0.7.2.dist-info}/WHEEL +1 -1
- agently-4.0.7.dist-info/METADATA +0 -194
- {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")
|
agently/_default_settings.yaml
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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:
|
|
616
|
+
def ensure_list_and_cast(v: Any, target_type: Any):
|
|
593
617
|
if not isinstance(v, list):
|
|
594
618
|
v = [v]
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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():
|