agently 4.0.7.1__py3-none-any.whl → 4.0.7.3__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 (35) hide show
  1. agently/_default_init.py +4 -0
  2. agently/_default_settings.yaml +1 -0
  3. agently/base.py +2 -0
  4. agently/builtins/agent_extensions/ChatSessionExtension.py +2 -2
  5. agently/builtins/agent_extensions/SessionExtension.py +300 -0
  6. agently/builtins/agent_extensions/__init__.py +1 -0
  7. agently/builtins/plugins/PromptGenerator/AgentlyPromptGenerator.py +36 -12
  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 +27 -1
  12. agently/builtins/tools/__init__.py +1 -0
  13. agently/core/Agent.py +7 -7
  14. agently/core/ModelRequest.py +0 -4
  15. agently/core/Prompt.py +1 -1
  16. agently/core/Session.py +218 -0
  17. agently/core/__init__.py +1 -0
  18. agently/integrations/chromadb.py +4 -4
  19. agently/types/data/__init__.py +2 -0
  20. agently/types/data/prompt.py +6 -1
  21. agently/types/data/tool.py +9 -0
  22. agently/types/plugins/BuiltInTool.py +22 -0
  23. agently/types/plugins/Session.py +169 -0
  24. agently/types/plugins/__init__.py +21 -0
  25. agently/types/plugins/base.py +1 -1
  26. agently/utils/AGENT_UTILS_GUIDE.md +175 -0
  27. agently/utils/DataFormatter.py +6 -2
  28. agently/utils/FunctionShifter.py +3 -2
  29. agently/utils/TimeInfo.py +22 -0
  30. agently/utils/__init__.py +1 -0
  31. agently-4.0.7.3.dist-info/METADATA +351 -0
  32. {agently-4.0.7.1.dist-info → agently-4.0.7.3.dist-info}/RECORD +34 -26
  33. {agently-4.0.7.1.dist-info → agently-4.0.7.3.dist-info}/WHEEL +1 -1
  34. agently-4.0.7.1.dist-info/METADATA +0 -194
  35. {agently-4.0.7.1.dist-info → agently-4.0.7.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,112 @@
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
+
16
+ import shlex
17
+ import subprocess
18
+ from pathlib import Path
19
+ from typing import Iterable, Sequence
20
+
21
+ from agently.types.plugins import BuiltInTool
22
+
23
+
24
+ class Cmd(BuiltInTool):
25
+ def __init__(
26
+ self,
27
+ *,
28
+ allowed_cmd_prefixes: Sequence[str] | None = None,
29
+ allowed_workdir_roots: Iterable[str | Path] | None = None,
30
+ timeout: int = 20,
31
+ env: dict[str, str] | None = None,
32
+ ):
33
+ self.tool_info_list = [
34
+ {
35
+ "name": "cmd",
36
+ "desc": "Run a shell command with allowlist checks.",
37
+ "kwargs": {
38
+ "cmd": ("str | list[str]", "Command to run"),
39
+ "workdir": ("str | None", "Working directory"),
40
+ "allow_unsafe": ("bool", "Allow command outside allowlist"),
41
+ },
42
+ "func": self.run,
43
+ }
44
+ ]
45
+
46
+ self.allowed_cmd_prefixes = set(
47
+ allowed_cmd_prefixes
48
+ if allowed_cmd_prefixes is not None
49
+ else ["ls", "rg", "cat", "pwd", "whoami", "date", "head", "tail"]
50
+ )
51
+ roots = allowed_workdir_roots if allowed_workdir_roots is not None else [Path.cwd()]
52
+ self.allowed_workdir_roots = [Path(root).resolve() for root in roots]
53
+ self.timeout = timeout
54
+ self.env = env
55
+
56
+ def _normalize_cmd(self, cmd: str | Sequence[str]) -> list[str]:
57
+ if isinstance(cmd, str):
58
+ return shlex.split(cmd)
59
+ return list(cmd)
60
+
61
+ def _is_cmd_allowed(self, args: list[str]) -> bool:
62
+ if not args:
63
+ return False
64
+ cmd = args[0]
65
+ base = Path(cmd).name
66
+ return base in self.allowed_cmd_prefixes
67
+
68
+ def _is_workdir_allowed(self, workdir: str | Path | None) -> bool:
69
+ workdir_path = Path(workdir or Path.cwd()).resolve()
70
+ for root in self.allowed_workdir_roots:
71
+ try:
72
+ workdir_path.relative_to(root)
73
+ return True
74
+ except ValueError:
75
+ continue
76
+ return False
77
+
78
+ async def run(
79
+ self,
80
+ cmd: str | Sequence[str],
81
+ workdir: str | Path | None = None,
82
+ allow_unsafe: bool = False,
83
+ ) -> dict:
84
+ args = self._normalize_cmd(cmd)
85
+ if not self._is_workdir_allowed(workdir):
86
+ return {
87
+ "ok": False,
88
+ "need_approval": True,
89
+ "reason": "workdir_not_allowed",
90
+ "workdir": str(workdir or Path.cwd()),
91
+ }
92
+ if not self._is_cmd_allowed(args) and not allow_unsafe:
93
+ return {
94
+ "ok": False,
95
+ "need_approval": True,
96
+ "reason": "cmd_not_allowed",
97
+ "cmd": args,
98
+ }
99
+ result = subprocess.run(
100
+ args,
101
+ cwd=str(Path(workdir).resolve()) if workdir else None,
102
+ capture_output=True,
103
+ text=True,
104
+ timeout=self.timeout,
105
+ env=self.env,
106
+ )
107
+ return {
108
+ "ok": result.returncode == 0,
109
+ "returncode": result.returncode,
110
+ "stdout": result.stdout,
111
+ "stderr": result.stderr,
112
+ }
@@ -19,7 +19,6 @@ from agently.utils import LazyImport, FunctionShifter
19
19
 
20
20
 
21
21
  class Search:
22
-
23
22
  def __init__(
24
23
  self,
25
24
  *,
@@ -103,6 +102,33 @@ class Search:
103
102
  ] = "us-en",
104
103
  options: dict[str, Any] | None = None,
105
104
  ):
105
+ self.tool_info_list = [
106
+ {
107
+ "name": "search",
108
+ "desc": "Search the web with {query}",
109
+ "kwargs": {"query": [("str", "search query")]},
110
+ "func": self.search,
111
+ },
112
+ {
113
+ "name": "search_news",
114
+ "desc": "Search news with {query}",
115
+ "kwargs": {"query": [("str", "search query")]},
116
+ "func": self.search_news,
117
+ },
118
+ {
119
+ "name": "search_wikipedia",
120
+ "desc": "Search wikipedia with {query}",
121
+ "kwargs": {"query": [("str", "search query")]},
122
+ "func": self.search_wikipedia,
123
+ },
124
+ {
125
+ "name": "search_arxiv",
126
+ "desc": "Search arXiv with {query}",
127
+ "kwargs": {"query": [("str", "search query")]},
128
+ "func": self.search_arxiv,
129
+ },
130
+ ]
131
+
106
132
  LazyImport.import_package("ddgs", version_constraint=">=9.10.0")
107
133
  from ddgs import DDGS
108
134
 
@@ -15,3 +15,4 @@
15
15
 
16
16
  from .Search import Search
17
17
  from .Browse import Browse
18
+ from .Cmd import Cmd
agently/core/Agent.py CHANGED
@@ -16,7 +16,7 @@ import uuid
16
16
  import yaml
17
17
  import json
18
18
 
19
- from typing import Any, TYPE_CHECKING
19
+ from typing import Any, Sequence, TYPE_CHECKING
20
20
 
21
21
  from agently.core import Prompt, ExtensionHandlers, ModelRequest
22
22
  from agently.utils import Settings
@@ -116,15 +116,15 @@ class BaseAgent:
116
116
  self.agent_prompt.set("chat_history", [])
117
117
  return self
118
118
 
119
- def set_chat_history(self, chat_history: "list[dict[str, Any] | ChatMessage]"):
119
+ def set_chat_history(self, chat_history: "Sequence[dict[str, Any] | ChatMessage]"):
120
120
  self.reset_chat_history()
121
- if not isinstance(chat_history, list):
121
+ if not isinstance(chat_history, Sequence):
122
122
  chat_history = [chat_history]
123
123
  self.agent_prompt.set("chat_history", chat_history)
124
124
  return self
125
125
 
126
- def add_chat_history(self, chat_history: "list[dict[str, Any] | ChatMessage] | dict[str, Any] | ChatMessage"):
127
- if not isinstance(chat_history, list):
126
+ def add_chat_history(self, chat_history: "Sequence[dict[str, Any] | ChatMessage] | dict[str, Any] | ChatMessage"):
127
+ if not isinstance(chat_history, Sequence):
128
128
  chat_history = [chat_history]
129
129
  self.agent_prompt.set("chat_history", chat_history)
130
130
  return self
@@ -151,9 +151,9 @@ class BaseAgent:
151
151
  always: bool = False,
152
152
  ):
153
153
  if always:
154
- self.agent_prompt.set("input", prompt, mappings)
154
+ self.agent_prompt.set("system", prompt, mappings)
155
155
  else:
156
- self.request.prompt.set("input", prompt, mappings)
156
+ self.request.prompt.set("system", prompt, mappings)
157
157
  return self
158
158
 
159
159
  def rule(
@@ -61,10 +61,6 @@ class ModelResponseResult:
61
61
  self.async_get_meta = self._response_parser.async_get_meta
62
62
  self.get_text = self._response_parser.get_text
63
63
  self.async_get_text = self._response_parser.async_get_text
64
- # self.get_data = self._response_parser.get_data
65
- # self.async_get_data = self._response_parser.async_get_data
66
- # self.get_data_object = self._response_parser.get_data_object
67
- # self.async_get_data_object = self._response_parser.async_get_data_object
68
64
  self.get_data = FunctionShifter.syncify(self.async_get_data)
69
65
  self.get_data_object = FunctionShifter.syncify(self.async_get_data_object)
70
66
  self.get_generator = self._response_parser.get_generator
agently/core/Prompt.py CHANGED
@@ -161,5 +161,5 @@ class Prompt(RuntimeData):
161
161
  else:
162
162
  super().append(key, value)
163
163
 
164
- def get(self, key: "PromptStandardSlot | None" = None, default: T = None, inherit: bool = True) -> Any | T:
164
+ def get(self, key: "PromptStandardSlot | str | None" = None, default: T = None, inherit: bool = True) -> Any | T:
165
165
  return super().get(key, default=default, inherit=inherit)
@@ -0,0 +1,218 @@
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
+ from typing import TYPE_CHECKING, Literal, cast
18
+
19
+ from agently.utils import Settings
20
+
21
+ if TYPE_CHECKING:
22
+ from agently.core import PluginManager, BaseAgent
23
+ from agently.types.plugins import (
24
+ SessionProtocol,
25
+ SessionMode,
26
+ SessionLimit,
27
+ MemoResizeDecision,
28
+ MemoResizeType,
29
+ MemoResizePolicyHandler,
30
+ MemoResizeHandler,
31
+ AttachmentSummaryHandler,
32
+ MemoUpdateHandler,
33
+ )
34
+ from agently.types.data import ChatMessage, ChatMessageDict, SerializableValue, SerializableData
35
+
36
+
37
+ class Session:
38
+ _impl: "SessionProtocol"
39
+ settings: Settings
40
+ plugin_manager: "PluginManager"
41
+ id: str
42
+ memo: "SerializableData"
43
+ full_chat_history: "list[ChatMessage]"
44
+ current_chat_history: "list[ChatMessage]"
45
+
46
+ def __init__(
47
+ self,
48
+ *,
49
+ policy_handler: "MemoResizePolicyHandler | None" = None,
50
+ resize_handlers: "dict[MemoResizeType, MemoResizeHandler] | None" = None,
51
+ attachment_summary_handler: "AttachmentSummaryHandler | None" = None,
52
+ memo_update_handler: "MemoUpdateHandler | None" = None,
53
+ parent_settings: Settings | None = None,
54
+ agent: "BaseAgent | None" = None,
55
+ plugin_manager: "PluginManager | None" = None,
56
+ ):
57
+ if plugin_manager is None:
58
+ from agently.base import plugin_manager as global_plugin_manager, settings as global_settings
59
+
60
+ plugin_manager = global_plugin_manager
61
+ if parent_settings is None:
62
+ parent_settings = global_settings
63
+
64
+ if parent_settings is None:
65
+ parent_settings = Settings(name="Session-Settings")
66
+
67
+ session_settings = Settings(
68
+ name="Session-Settings",
69
+ parent=parent_settings,
70
+ )
71
+ plugin_name = str(session_settings.get("plugins.Session.activate", "AgentlyMemoSession"))
72
+ SessionPlugin = cast(
73
+ type["SessionProtocol"],
74
+ plugin_manager.get_plugin("Session", plugin_name),
75
+ )
76
+ impl = SessionPlugin(
77
+ policy_handler=policy_handler,
78
+ resize_handlers=resize_handlers,
79
+ attachment_summary_handler=attachment_summary_handler,
80
+ memo_update_handler=memo_update_handler,
81
+ parent_settings=session_settings,
82
+ agent=agent,
83
+ )
84
+ object.__setattr__(self, "_impl", impl)
85
+ object.__setattr__(self, "settings", impl.settings)
86
+ object.__setattr__(self, "plugin_manager", plugin_manager)
87
+
88
+ def configure(
89
+ self,
90
+ *,
91
+ mode: "SessionMode | None" = None,
92
+ limit: "SessionLimit | None" = None,
93
+ every_n_turns: int | None = None,
94
+ ) -> "Session":
95
+ self._impl.configure(
96
+ mode=mode,
97
+ limit=limit,
98
+ every_n_turns=every_n_turns,
99
+ )
100
+ return self
101
+
102
+ def set_limit(
103
+ self,
104
+ *,
105
+ chars: int | None = None,
106
+ messages: int | None = None,
107
+ ) -> "Session":
108
+ self._impl.set_limit(chars=chars, messages=messages)
109
+ return self
110
+
111
+ def use_lite(
112
+ self,
113
+ *,
114
+ chars: int | None = None,
115
+ messages: int | None = None,
116
+ every_n_turns: int | None = None,
117
+ ) -> "Session":
118
+ self._impl.use_lite(chars=chars, messages=messages, every_n_turns=every_n_turns)
119
+ return self
120
+
121
+ def use_memo(
122
+ self,
123
+ *,
124
+ chars: int | None = None,
125
+ messages: int | None = None,
126
+ every_n_turns: int | None = None,
127
+ ) -> "Session":
128
+ self._impl.use_memo(chars=chars, messages=messages, every_n_turns=every_n_turns)
129
+ return self
130
+
131
+ def append_message(self, message: "ChatMessage | ChatMessageDict") -> "Session":
132
+ self._impl.append_message(message)
133
+ return self
134
+
135
+ def set_settings(
136
+ self,
137
+ key: str,
138
+ value: "SerializableValue",
139
+ *,
140
+ auto_load_env: bool = False,
141
+ ) -> Settings:
142
+ return self._impl.set_settings(key, value, auto_load_env=auto_load_env)
143
+
144
+ def set_policy_handler(self, policy_handler: "MemoResizePolicyHandler") -> "Session":
145
+ self._impl.set_policy_handler(policy_handler)
146
+ return self
147
+
148
+ def set_resize_handlers(
149
+ self,
150
+ resize_type: "MemoResizeType",
151
+ resize_handler: "MemoResizeHandler",
152
+ ) -> "Session":
153
+ self._impl.set_resize_handlers(resize_type, resize_handler)
154
+ return self
155
+
156
+ def set_attachment_summary_handler(
157
+ self,
158
+ attachment_summary_handler: "AttachmentSummaryHandler",
159
+ ) -> "Session":
160
+ self._impl.set_attachment_summary_handler(attachment_summary_handler)
161
+ return self
162
+
163
+ def set_memo_update_handler(
164
+ self,
165
+ memo_update_handler: "MemoUpdateHandler",
166
+ ) -> "Session":
167
+ self._impl.set_memo_update_handler(memo_update_handler)
168
+ return self
169
+
170
+ def judge_resize(
171
+ self,
172
+ force: "Literal['lite', 'deep', False, None] | str" = False,
173
+ ) -> "MemoResizeDecision | None":
174
+ return self._impl.judge_resize(force=force)
175
+
176
+ def resize(
177
+ self,
178
+ force: "Literal['lite', 'deep', False, None] | str" = False,
179
+ ) -> "list[ChatMessage]":
180
+ return self._impl.resize(force=force)
181
+
182
+ async def async_judge_resize(
183
+ self,
184
+ force: "Literal['lite', 'deep', False, None] | str" = False,
185
+ ) -> "MemoResizeDecision | None":
186
+ return await self._impl.async_judge_resize(force=force)
187
+
188
+ async def async_resize(
189
+ self,
190
+ force: "Literal['lite', 'deep', False, None] | str" = False,
191
+ ) -> "list[ChatMessage]":
192
+ return await self._impl.async_resize(force=force)
193
+
194
+ def to_json(self) -> str:
195
+ return self._impl.to_json()
196
+
197
+ def to_yaml(self) -> str:
198
+ return self._impl.to_yaml()
199
+
200
+ def load_json(self, value: str) -> "Session":
201
+ self._impl.load_json(value)
202
+ return self
203
+
204
+ def load_yaml(self, value: str) -> "Session":
205
+ self._impl.load_yaml(value)
206
+ return self
207
+
208
+ def __getattr__(self, name: str):
209
+ return getattr(self._impl, name)
210
+
211
+ def __setattr__(self, name: str, value):
212
+ if name in {"_impl", "settings", "plugin_manager"}:
213
+ object.__setattr__(self, name, value)
214
+ return
215
+ if "_impl" in self.__dict__ and hasattr(self._impl, name):
216
+ setattr(self._impl, name, value)
217
+ return
218
+ object.__setattr__(self, name, value)
agently/core/__init__.py CHANGED
@@ -25,6 +25,7 @@ from .TriggerFlow import (
25
25
  TriggerFlowExecution,
26
26
  TriggerFlowChunk,
27
27
  )
28
+ from .Session import Session
28
29
 
29
30
  # from .TriggerFlow_old import (
30
31
  # TriggerFlow,
@@ -38,7 +38,7 @@ if TYPE_CHECKING:
38
38
  )
39
39
  from chromadb.api.collection_configuration import CreateCollectionConfiguration
40
40
  from chromadb.api import ClientAPI
41
- from agently.core import BaseAgent
41
+ from agently.base import Agent
42
42
 
43
43
 
44
44
  class ChromaDataDictOptional(TypedDict, total=False):
@@ -57,7 +57,7 @@ class ChromaData:
57
57
  original_data: ChromaDataDict | list[ChromaDataDict],
58
58
  *,
59
59
  embedding_function: "Callable[[str | list[str]], Embeddings] | None" = None,
60
- agent: "BaseAgent | None" = None,
60
+ agent: "Agent | None" = None,
61
61
  ):
62
62
  self._original_data = original_data if isinstance(original_data, list) else [original_data]
63
63
  if embedding_function:
@@ -157,7 +157,7 @@ class ChromaEmbeddingFunction(EmbeddingFunction):
157
157
  def __init__(
158
158
  self,
159
159
  *,
160
- embedding_agent: "BaseAgent",
160
+ embedding_agent: "Agent",
161
161
  ):
162
162
  def embedding_function_by_agent(texts: list[str]) -> "Embeddings":
163
163
  return embedding_agent.input(texts).start()
@@ -180,7 +180,7 @@ class ChromaCollection:
180
180
  schema: "Schema | None" = None,
181
181
  configuration: "CreateCollectionConfiguration | None" = None,
182
182
  metadata: dict[str, Any] | None = None,
183
- embedding_agent: "BaseAgent | None" = None,
183
+ embedding_agent: "Agent | None" = None,
184
184
  data_loader: "DataLoader[Loadable] | None" = None,
185
185
  get_or_create: bool = False,
186
186
  hnsw_space: Literal["l2", "cosine", "ip"] = "cosine",
@@ -36,6 +36,7 @@ EMPTY = AVOID_COPY()
36
36
  from .serializable import SerializableData, SerializableValue
37
37
  from .prompt import (
38
38
  ChatMessage,
39
+ ChatMessageDict,
39
40
  ChatMessageContent,
40
41
  TextMessageContent,
41
42
  PromptModel,
@@ -71,4 +72,5 @@ from .tool import (
71
72
  ReturnType,
72
73
  MCPConfig,
73
74
  MCPConfigs,
75
+ ToolInfo,
74
76
  )
@@ -52,8 +52,13 @@ ChatMessageContent = TextMessageContent | AttachmentMessageContent
52
52
  ChatMessageContentAdapter = TypeAdapter(Annotated[ChatMessageContent, Field(union_mode="left_to_right")])
53
53
 
54
54
 
55
+ class ChatMessageDict(TypedDict):
56
+ role: Literal["system", "developer", "tool", "user", "assistant"] | str
57
+ content: str | list[dict[str, Any] | ChatMessageContent]
58
+
59
+
55
60
  class ChatMessage(BaseModel):
56
- role: str = "user"
61
+ role: Literal["system", "developer", "tool", "user", "assistant"] | str = "user"
57
62
  content: str | list[dict[str, Any] | ChatMessageContent]
58
63
 
59
64
  model_config = ConfigDict(extra="allow")
@@ -40,3 +40,12 @@ class MCPConfig(TypedDict):
40
40
 
41
41
  class MCPConfigs(TypedDict):
42
42
  mcpServers: dict[str, MCPConfig]
43
+
44
+
45
+ class ToolInfo(TypedDict, total=False):
46
+ name: str
47
+ desc: str
48
+ kwargs: dict[str, Any]
49
+ func: Callable[..., Any]
50
+ returns: ReturnType
51
+ tags: str | list[str]
@@ -0,0 +1,22 @@
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
+
16
+ from typing import Protocol
17
+
18
+ from agently.types.data import ToolInfo
19
+
20
+
21
+ class BuiltInTool(Protocol):
22
+ tool_info_list: list[ToolInfo]