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,169 @@
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 __future__ import annotations
17
+
18
+ from typing import Any, Literal, Awaitable, Callable, TypeAlias, TYPE_CHECKING, Protocol
19
+ from typing_extensions import TypedDict, NotRequired, Self
20
+
21
+ if TYPE_CHECKING:
22
+ from agently.utils import Settings
23
+ from agently.types.data import SerializableData, SerializableValue, ChatMessage, ChatMessageDict
24
+
25
+ MemoResizeType: TypeAlias = Literal["lite", "deep"] | str
26
+ SessionMode: TypeAlias = Literal["lite", "memo"] | str
27
+ ResizeForce: TypeAlias = Literal["lite", "deep", False, None] | str
28
+
29
+
30
+ class SessionLimit(TypedDict, total=False):
31
+ chars: int
32
+ messages: int
33
+
34
+
35
+ class SessionConfig(TypedDict, total=False):
36
+ mode: SessionMode
37
+ limit: SessionLimit
38
+ every_n_turns: int
39
+
40
+
41
+ class MemoResizeDecision(TypedDict):
42
+ type: MemoResizeType
43
+ reason: NotRequired[str]
44
+ severity: NotRequired[int]
45
+ meta: NotRequired[dict[str, Any]]
46
+
47
+
48
+ MemoResizePolicyResult: TypeAlias = "MemoResizeType | MemoResizeDecision | None"
49
+
50
+ MemoResizePolicyHandler: TypeAlias = (
51
+ "Callable[[list[ChatMessage], list[ChatMessage], Settings], MemoResizePolicyResult | Awaitable[MemoResizePolicyResult]]"
52
+ )
53
+
54
+ MemoResizePolicyAsyncHandler: TypeAlias = (
55
+ "Callable[[list[ChatMessage], list[ChatMessage], Settings], Awaitable[MemoResizePolicyResult]]"
56
+ )
57
+
58
+ MemoResizeHandlerResult: TypeAlias = "tuple[list[ChatMessage], list[ChatMessage], SerializableData]"
59
+
60
+ MemoResizeHandler: TypeAlias = (
61
+ "Callable[[list[ChatMessage], list[ChatMessage], SerializableData, Settings], MemoResizeHandlerResult | Awaitable[MemoResizeHandlerResult]]"
62
+ )
63
+
64
+ MemoResizeAsyncHandler: TypeAlias = (
65
+ "Callable[[list[ChatMessage], list[ChatMessage], SerializableData, Settings], Awaitable[MemoResizeHandlerResult]]"
66
+ )
67
+
68
+ MemoUpdateResult: TypeAlias = "dict[str, Any]"
69
+ MemoUpdateHandler: TypeAlias = (
70
+ "Callable[[dict[str, Any], list[ChatMessage], list[AttachmentSummary], Settings], MemoUpdateResult | Awaitable[MemoUpdateResult]]"
71
+ )
72
+ MemoUpdateAsyncHandler: TypeAlias = (
73
+ "Callable[[dict[str, Any], list[ChatMessage], list[AttachmentSummary], Settings], Awaitable[MemoUpdateResult]]"
74
+ )
75
+
76
+ AttachmentSummary: TypeAlias = "dict[str, Any]"
77
+ AttachmentSummaryHandler: TypeAlias = (
78
+ "Callable[[ChatMessage], list[AttachmentSummary] | Awaitable[list[AttachmentSummary]]]"
79
+ )
80
+
81
+ AttachmentSummaryAsyncHandler: TypeAlias = "Callable[[ChatMessage], Awaitable[list[AttachmentSummary]]]"
82
+
83
+
84
+ class SessionProtocol(Protocol):
85
+ id: str
86
+ settings: "Settings"
87
+ memo: "SerializableData"
88
+ full_chat_history: "list[ChatMessage]"
89
+ current_chat_history: "list[ChatMessage]"
90
+
91
+ def __init__(
92
+ self,
93
+ *,
94
+ policy_handler: MemoResizePolicyHandler | None = None,
95
+ resize_handlers: dict[MemoResizeType, MemoResizeHandler] | None = None,
96
+ attachment_summary_handler: AttachmentSummaryHandler | None = None,
97
+ memo_update_handler: MemoUpdateHandler | None = None,
98
+ parent_settings: "Settings | None" = None,
99
+ agent: Any | None = None,
100
+ ): ...
101
+
102
+ def configure(
103
+ self,
104
+ *,
105
+ mode: SessionMode | None = None,
106
+ limit: SessionLimit | None = None,
107
+ every_n_turns: int | None = None,
108
+ ) -> Self: ...
109
+
110
+ def set_limit(
111
+ self,
112
+ *,
113
+ chars: int | None = None,
114
+ messages: int | None = None,
115
+ ) -> Self: ...
116
+
117
+ def use_lite(
118
+ self,
119
+ *,
120
+ chars: int | None = None,
121
+ messages: int | None = None,
122
+ every_n_turns: int | None = None,
123
+ ) -> Self: ...
124
+
125
+ def use_memo(
126
+ self,
127
+ *,
128
+ chars: int | None = None,
129
+ messages: int | None = None,
130
+ every_n_turns: int | None = None,
131
+ ) -> Self: ...
132
+
133
+ def append_message(self, message: "ChatMessage | ChatMessageDict") -> Self: ...
134
+
135
+ def set_settings(
136
+ self,
137
+ key: str,
138
+ value: "SerializableValue",
139
+ *,
140
+ auto_load_env: bool = False,
141
+ ) -> "Settings": ...
142
+
143
+ def judge_resize(self, force: ResizeForce = False) -> "MemoResizeDecision | None": ...
144
+
145
+ def resize(self, force: ResizeForce = False) -> "list[ChatMessage]": ...
146
+
147
+ def set_policy_handler(self, policy_handler: MemoResizePolicyHandler) -> Self: ...
148
+
149
+ def set_resize_handlers(
150
+ self,
151
+ resize_type: MemoResizeType,
152
+ resize_handler: MemoResizeHandler,
153
+ ) -> Self: ...
154
+
155
+ def set_attachment_summary_handler(self, attachment_summary_handler: AttachmentSummaryHandler) -> Self: ...
156
+
157
+ def set_memo_update_handler(self, memo_update_handler: MemoUpdateHandler) -> Self: ...
158
+
159
+ async def async_judge_resize(self, force: ResizeForce = False) -> "MemoResizeDecision | None": ...
160
+
161
+ async def async_resize(self, force: ResizeForce = False) -> "list[ChatMessage]": ...
162
+
163
+ def to_json(self) -> str: ...
164
+
165
+ def to_yaml(self) -> str: ...
166
+
167
+ def load_json(self, value: str) -> Self: ...
168
+
169
+ def load_yaml(self, value: str) -> Self: ...
@@ -18,3 +18,24 @@ from .PromptGenerator import PromptGenerator
18
18
  from .ModelRequester import ModelRequester
19
19
  from .ResponseParser import ResponseParser
20
20
  from .ToolManager import ToolManager
21
+ from .BuiltInTool import BuiltInTool
22
+ from .Session import (
23
+ SessionProtocol,
24
+ SessionMode,
25
+ SessionLimit,
26
+ SessionConfig,
27
+ MemoResizePolicyHandler,
28
+ MemoResizePolicyAsyncHandler,
29
+ MemoResizePolicyResult,
30
+ MemoResizeHandler,
31
+ MemoResizeAsyncHandler,
32
+ MemoResizeHandlerResult,
33
+ MemoResizeType,
34
+ MemoResizeDecision,
35
+ MemoUpdateHandler,
36
+ MemoUpdateAsyncHandler,
37
+ MemoUpdateResult,
38
+ AttachmentSummaryHandler,
39
+ AttachmentSummaryAsyncHandler,
40
+ AttachmentSummary,
41
+ )
@@ -14,7 +14,7 @@
14
14
 
15
15
  from typing import Any, Literal, Protocol, runtime_checkable
16
16
 
17
- AgentlyPluginType = Literal["PromptGenerator", "ModelRequester", "ResponseParser", "ToolManager"]
17
+ AgentlyPluginType = Literal["PromptGenerator", "ModelRequester", "ResponseParser", "ToolManager", "Session"]
18
18
 
19
19
 
20
20
  @runtime_checkable
@@ -0,0 +1,175 @@
1
+ # Agently Utils Guide (Agent-Readable)
2
+
3
+ Use this as a compact, agent-oriented guide to the utilities in `agently/utils`. It is intentionally brief and practical.
4
+
5
+ ## Quick Map (TL;DR)
6
+ - data shaping: `DataFormatter`, `RuntimeData`, `SerializableRuntimeData`, `Settings`
7
+ - path and JSON helpers: `DataLocator`, `DataPathBuilder`, `StreamingJSONCompleter`, `StreamingJSONParser`
8
+ - async/sync bridging: `FunctionShifter`, `GeneratorConsumer`
9
+ - dynamic deps: `LazyImport`
10
+ - storage: `Storage`, `AsyncStorage`
11
+ - misc: `Logger`, `Messenger`, `PythonSandbox`
12
+ - legacy: `old_RuntimeData` (avoid unless you must keep backward behavior)
13
+
14
+ ## Utilities
15
+
16
+ ### DataFormatter
17
+ Purpose: normalize complex values into safe, serializable, or string forms, and do placeholder substitution.
18
+
19
+ Key methods:
20
+ - `sanitize(value, remain_type=False)`: convert complex objects into JSON-ish values. Handles `datetime`, `RuntimeData`, `pydantic.BaseModel`, and typing constructs like `list[T]`, `Union`, `Literal`.
21
+ - `to_str_key_dict(value, value_format=None, default_key=None, default_value=None)`: ensure dict keys are strings and values optionally sanitized or stringified. If input is not a dict, can wrap with `default_key`.
22
+ - `from_schema_to_kwargs_format(schema)`: convert JSON Schema object fields into Agently kwargs-style `(type, desc)` mapping.
23
+ - `substitute_placeholder(obj, variable_mappings, placeholder_pattern=None)`: recursive replace `${key}` placeholders in strings, dicts, lists, sets, tuples.
24
+
25
+ When to use:
26
+ - Before logging/serialization.
27
+ - When building structured prompts from schemas.
28
+ - When injecting env variables into settings or prompts.
29
+
30
+ ### DataLocator
31
+ Purpose: locate values by path, and extract JSON blocks from mixed text.
32
+
33
+ Key methods:
34
+ - `locate_path_in_dict(dict, path, style="dot"|"slash", default=None)`: safe deep lookup with `a.b[0]` or `/a/b/0` styles.
35
+ - `locate_all_json(text)`: scan text and return all JSON-like blocks.
36
+ - `locate_output_json(text, output_prompt_dict)`: pick the most likely JSON block matching your output schema.
37
+
38
+ When to use:
39
+ - Parsing LLM responses that mix text + JSON.
40
+ - Robust extraction for streaming parsers.
41
+
42
+ ### DataPathBuilder
43
+ Purpose: convert and reason about dot/slash paths, and extract expected parsing paths from a schema-like dict.
44
+
45
+ Key methods:
46
+ - `build_dot_path(keys)`, `build_slash_path(keys)`
47
+ - `convert_dot_to_slash(dot_path)`, `convert_slash_to_dot(slash_path)`
48
+ - `extract_possible_paths(schema, style="dot")`: find all possible paths.
49
+ - `extract_parsing_key_orders(schema, style="dot")`: paths in definition order (used by streaming parser).
50
+ - `get_value_by_path(data, path, style="dot")`: retrieve values, supports `[*]` wildcard expansion.
51
+
52
+ When to use:
53
+ - Streaming JSON parsing with ordered fields.
54
+ - Mapping UI updates to schema paths.
55
+
56
+ ### FunctionShifter
57
+ Purpose: bridge sync/async code and run async work safely from sync contexts.
58
+
59
+ Key methods:
60
+ - `syncify(func)`: wrap an async function so it can be called in sync code. Uses `asyncio.run` or a thread when a loop is running.
61
+ - `asyncify(func)`: wrap a sync function so it can be awaited via `asyncio.to_thread`.
62
+ - `future(func)`: return a `Future` for the function execution; ensures there is a loop.
63
+ - `syncify_async_generator(async_gen)`: consume an async generator from sync code via a background thread.
64
+ - `auto_options_func(func)`: drop extra kwargs that the function does not accept.
65
+
66
+ When to use:
67
+ - Tool functions that may be sync or async.
68
+ - Adapters between streaming generators and sync APIs.
69
+
70
+ ### GeneratorConsumer
71
+ Purpose: fan out a generator or async generator to multiple consumers, replay history, and handle errors.
72
+
73
+ Usage:
74
+ - Wrap a generator, then call `get_async_generator()` for multiple async consumers or `get_generator()` for sync.
75
+ - `get_result()` waits for completion and returns full history.
76
+ - `close()` cancels and notifies listeners.
77
+
78
+ When to use:
79
+ - Broadcast streaming output to multiple subscribers.
80
+ - Merge history + live updates reliably.
81
+
82
+ ### LazyImport
83
+ Purpose: import optional deps and optionally auto-install via pip with version constraints.
84
+
85
+ Key methods:
86
+ - `from_import(from_package, target_modules, auto_install=True, version_constraint=None, install_name=None)`
87
+ - `import_package(package_name, auto_install=True, version_constraint=None, install_name=None)`
88
+
89
+ Notes:
90
+ - This prompts for installation in interactive use; plan for non-interactive runtime accordingly.
91
+
92
+ ### Logger
93
+ Purpose: create a consistent logger with optional uvicorn integration.
94
+
95
+ Key pieces:
96
+ - `create_logger(app_name="Agently", log_level="INFO")` returns `AgentlyLogger` with `raise_error` helper.
97
+
98
+ ### Messenger
99
+ Purpose: convenience wrapper for event center messaging.
100
+
101
+ Key method:
102
+ - `create_messenger(module_name)` delegates to `agently.base.event_center`.
103
+
104
+ ### PythonSandbox
105
+ Purpose: execute small snippets safely with restricted builtins and whitelisted return types.
106
+
107
+ Key behaviors:
108
+ - `run(code)` executes in a sandbox; raises if a return type is not in `allowed_return_types`.
109
+ - `preset_objects` are wrapped to block private attributes and enforce safe return types.
110
+
111
+ When to use:
112
+ - Run short user-defined expressions or filters with safety checks.
113
+
114
+ ### RuntimeData / RuntimeDataNamespace
115
+ Purpose: runtime-scoped hierarchical data with inheritance, dot-path access, and merge-friendly set semantics.
116
+
117
+ Key behaviors:
118
+ - `get(key, default=None, inherit=True)`: inherited view by default.
119
+ - `set` and `__setitem__` merge dict/list/set values rather than replace (unless you use `cover=True` internally).
120
+ - dot-path access: `data["a.b.c"]`.
121
+ - `namespace("path")` returns a namespace view.
122
+ - `dump("json"|"yaml"|"toml")`, `load(...)` support.
123
+
124
+ When to use:
125
+ - Store workflow state, memo, runtime configs.
126
+
127
+ ### SerializableRuntimeData / SerializableRuntimeDataNamespace
128
+ Purpose: same API as `RuntimeData` but value types restricted to JSON-serializable shapes.
129
+
130
+ When to use:
131
+ - Settings and serialized runtime state.
132
+
133
+ ### Settings / SettingsNamespace
134
+ Purpose: settings with mapping shortcuts and env substitution.
135
+
136
+ Key behaviors:
137
+ - `register_path_mappings("short", "actual.path")`: alias keys.
138
+ - `register_kv_mappings("key", "value", actual_settings)`: map a key+value to a settings dict.
139
+ - `set_settings(key, value, auto_load_env=False)`: apply mappings and optionally expand `${ENV.X}`.
140
+
141
+ When to use:
142
+ - Global or per-agent configuration with shortcuts.
143
+
144
+ ### Storage / AsyncStorage
145
+ Purpose: simple SQLModel-based persistence with sync/async APIs.
146
+
147
+ Key behaviors:
148
+ - requires `sqlmodel`, `sqlalchemy`, `aiosqlite` via `LazyImport`.
149
+ - `set(obj|list)` merges into DB.
150
+ - `get(model, where=..., first=False, limit=..., offset=..., order_by=...)`.
151
+ - `create_tables()` calls `SQLModel.metadata.create_all`.
152
+
153
+ When to use:
154
+ - local state persistence for agents or tools.
155
+
156
+ ### StreamingJSONCompleter
157
+ Purpose: complete partial JSON strings by closing open strings, comments, or brackets.
158
+
159
+ Key method:
160
+ - `append(data)` then `complete()` to get best-effort JSON.
161
+
162
+ When to use:
163
+ - streaming LLM output where JSON is partial.
164
+
165
+ ### StreamingJSONParser
166
+ Purpose: parse streaming JSON and emit incremental updates (`delta`) and completion events (`done`).
167
+
168
+ Key behaviors:
169
+ - Uses `DataLocator` + `StreamingJSONCompleter` to find and parse JSON in noisy streams.
170
+ - Tracks schema order via `DataPathBuilder.extract_parsing_key_orders`.
171
+ - `parse_chunk(chunk)` yields `StreamingData` events.
172
+ - `parse_stream(chunk_stream)` yields events and finalizes at end.
173
+
174
+ When to use:
175
+ - UI streaming updates for structured output.
@@ -242,9 +242,11 @@ class DataFormatter:
242
242
 
243
243
  if "additionalProperties" in input_schema:
244
244
  additional_properties = input_schema["additionalProperties"]
245
- if additional_properties is True or additional_properties is None:
245
+ if additional_properties is False:
246
+ pass
247
+ elif additional_properties is True or additional_properties is None:
246
248
  kwargs_format["<*>"] = (Any, "")
247
- else:
249
+ elif isinstance(additional_properties, dict):
248
250
  additional_type = additional_properties.pop("type", Any)
249
251
  additional_properties.pop("title", None)
250
252
  additional_desc = (
@@ -253,6 +255,8 @@ class DataFormatter:
253
255
  else ""
254
256
  )
255
257
  kwargs_format["<*>"] = (additional_type, additional_desc)
258
+ else:
259
+ kwargs_format["<*>"] = (Any, "")
256
260
 
257
261
  return kwargs_format or None
258
262
 
@@ -76,8 +76,9 @@ class FunctionShifter:
76
76
 
77
77
  @wraps(func)
78
78
  async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
79
- assert inspect.isfunction(func)
80
- return await asyncio.to_thread(func, *args, **kwargs)
79
+ if not callable(func):
80
+ raise TypeError(f"Expected a callable, got {type(func)}")
81
+ return await asyncio.to_thread(func, *args, **kwargs) # type: ignore
81
82
 
82
83
  return wrapper
83
84
 
@@ -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 datetime import datetime
17
+
18
+
19
+ class TimeInfo:
20
+ @staticmethod
21
+ def get_current_time():
22
+ return datetime.now().strftime("%Y-%m-%d %H:%M:%S %A")
agently/utils/__init__.py CHANGED
@@ -28,3 +28,4 @@ from .GeneratorConsumer import GeneratorConsumer
28
28
  from .StreamingJSONCompleter import StreamingJSONCompleter
29
29
  from .StreamingJSONParser import StreamingJSONParser
30
30
  from .PythonSandbox import PythonSandbox
31
+ from .TimeInfo import TimeInfo