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
@@ -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.
@@ -56,15 +56,21 @@ class DataFormatter:
56
56
  if issubclass(value, BaseModel):
57
57
  extracted_value = {}
58
58
  for name, field in value.model_fields.items():
59
+ annotation = field.annotation
60
+ if hasattr(field, "rebuild_annotation"):
61
+ try:
62
+ annotation = field.rebuild_annotation()
63
+ except Exception:
64
+ annotation = field.annotation
59
65
  extracted_value.update(
60
66
  {
61
67
  name: (
62
68
  (
63
- DataFormatter.sanitize(field.annotation, remain_type=remain_type),
69
+ DataFormatter.sanitize(annotation, remain_type=remain_type),
64
70
  field.description,
65
71
  )
66
72
  if field.description
67
- else (DataFormatter.sanitize(field.annotation, remain_type=remain_type),)
73
+ else (DataFormatter.sanitize(annotation, remain_type=remain_type),)
68
74
  )
69
75
  }
70
76
  )
@@ -236,9 +242,11 @@ class DataFormatter:
236
242
 
237
243
  if "additionalProperties" in input_schema:
238
244
  additional_properties = input_schema["additionalProperties"]
239
- 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:
240
248
  kwargs_format["<*>"] = (Any, "")
241
- else:
249
+ elif isinstance(additional_properties, dict):
242
250
  additional_type = additional_properties.pop("type", Any)
243
251
  additional_properties.pop("title", None)
244
252
  additional_desc = (
@@ -247,6 +255,8 @@ class DataFormatter:
247
255
  else ""
248
256
  )
249
257
  kwargs_format["<*>"] = (additional_type, additional_desc)
258
+ else:
259
+ kwargs_format["<*>"] = (Any, "")
250
260
 
251
261
  return kwargs_format or None
252
262
 
@@ -21,6 +21,101 @@ if TYPE_CHECKING:
21
21
 
22
22
 
23
23
  class DataLocator:
24
+ @staticmethod
25
+ def _locate_path_parts(
26
+ result: Any,
27
+ path_parts: list[str],
28
+ *,
29
+ style: Literal["dot", "slash"],
30
+ default: Any,
31
+ ):
32
+ if not path_parts:
33
+ return result
34
+ path_part = path_parts[0]
35
+ remaining = path_parts[1:]
36
+ if style == "dot":
37
+ if "[" in path_part:
38
+ path_key_and_index = path_part.split("[")
39
+ path_key = path_key_and_index[0]
40
+ path_index = path_key_and_index[1][:-1]
41
+ if isinstance(result, Mapping):
42
+ result = result.get(path_key, default)
43
+ else:
44
+ return default
45
+ if path_index in ("*", ""):
46
+ if not isinstance(result, str) and isinstance(result, Sequence):
47
+ values = []
48
+ for item in result:
49
+ value = DataLocator._locate_path_parts(
50
+ item,
51
+ remaining,
52
+ style=style,
53
+ default=default,
54
+ )
55
+ if value is default:
56
+ return default
57
+ values.append(value)
58
+ return values
59
+ return default
60
+ try:
61
+ index = int(path_index)
62
+ except Exception:
63
+ return default
64
+ if not isinstance(result, str) and isinstance(result, Sequence):
65
+ try:
66
+ return DataLocator._locate_path_parts(
67
+ result[index],
68
+ remaining,
69
+ style=style,
70
+ default=default,
71
+ )
72
+ except Exception:
73
+ return default
74
+ return default
75
+ else:
76
+ if isinstance(result, Mapping):
77
+ return DataLocator._locate_path_parts(
78
+ result.get(path_part, default),
79
+ remaining,
80
+ style=style,
81
+ default=default,
82
+ )
83
+ return default
84
+ else:
85
+ if path_part == "*":
86
+ if not isinstance(result, str) and isinstance(result, Sequence):
87
+ values = []
88
+ for item in result:
89
+ value = DataLocator._locate_path_parts(
90
+ item,
91
+ remaining,
92
+ style=style,
93
+ default=default,
94
+ )
95
+ if value is default:
96
+ return default
97
+ values.append(value)
98
+ return values
99
+ return default
100
+ if isinstance(result, Mapping):
101
+ return DataLocator._locate_path_parts(
102
+ result.get(path_part, default),
103
+ remaining,
104
+ style=style,
105
+ default=default,
106
+ )
107
+ if not isinstance(result, str) and isinstance(result, Sequence):
108
+ try:
109
+ return DataLocator._locate_path_parts(
110
+ result[int(path_part)],
111
+ remaining,
112
+ style=style,
113
+ default=default,
114
+ )
115
+ except Exception:
116
+ return default
117
+ return default
118
+
24
119
  @staticmethod
25
120
  def locate_path_in_dict(
26
121
  original_dict: dict,
@@ -34,42 +129,24 @@ class DataLocator:
34
129
  match style:
35
130
  case "dot":
36
131
  try:
37
- result = original_dict
38
132
  path_parts = path.split(".")
39
- for path_part in path_parts:
40
- if "[" in path_part:
41
- path_key_and_index = path_part.split("[")
42
- path_key = path_key_and_index[0]
43
- path_index = int(path_key_and_index[1][:-1])
44
- if isinstance(result, Mapping):
45
- result = result[path_key]
46
- else:
47
- return default
48
- if not isinstance(result, str) and isinstance(result, Sequence):
49
- result = result[path_index]
50
- else:
51
- return default
52
- else:
53
- if isinstance(result, Mapping):
54
- result = result[path_part]
55
- else:
56
- return default
57
- return result
133
+ return DataLocator._locate_path_parts(
134
+ original_dict,
135
+ path_parts,
136
+ style="dot",
137
+ default=default,
138
+ )
58
139
  except Exception:
59
140
  return default
60
141
  case "slash":
61
- result = original_dict
62
- path_parts = path.split("/")
63
142
  try:
64
- for path_part in path_parts:
65
- if path_part:
66
- if isinstance(result, Mapping):
67
- result = result[path_part]
68
- elif not isinstance(result, str) and isinstance(result, Sequence):
69
- result = result[int(path_part)]
70
- else:
71
- return default
72
- return result
143
+ path_parts = [part for part in path.split("/") if part]
144
+ return DataLocator._locate_path_parts(
145
+ original_dict,
146
+ path_parts,
147
+ style="slash",
148
+ default=default,
149
+ )
73
150
  except Exception:
74
151
  return default
75
152
 
@@ -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