agently 4.0.7.1__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 +1 -0
- agently/base.py +2 -0
- 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 +36 -12
- 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 +27 -1
- agently/builtins/tools/__init__.py +1 -0
- agently/core/Agent.py +7 -7
- agently/core/ModelRequest.py +0 -4
- agently/core/Prompt.py +1 -1
- agently/core/Session.py +85 -0
- 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 +6 -2
- 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.1.dist-info → agently-4.0.7.2.dist-info}/RECORD +33 -25
- {agently-4.0.7.1.dist-info → agently-4.0.7.2.dist-info}/WHEEL +1 -1
- agently-4.0.7.1.dist-info/METADATA +0 -194
- {agently-4.0.7.1.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.
|
agently/utils/DataFormatter.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
agently/utils/FunctionShifter.py
CHANGED
|
@@ -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
|
-
|
|
80
|
-
|
|
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
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agently
|
|
3
|
+
Version: 4.0.7.2
|
|
4
|
+
Summary:
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Agently Team
|
|
8
|
+
Author-email: developer@agently.tech
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: greenlet (>=3.2.3,<4.0.0)
|
|
18
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
19
|
+
Requires-Dist: httpx-sse (>=0.4.1,<0.5.0)
|
|
20
|
+
Requires-Dist: json5 (>=0.12.0,<0.13.0)
|
|
21
|
+
Requires-Dist: packaging (>=25.0,<26.0)
|
|
22
|
+
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
|
23
|
+
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
24
|
+
Requires-Dist: stamina (>=25.1.0,<26.0.0)
|
|
25
|
+
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
<img width="640" alt="image" src="https://github.com/user-attachments/assets/c645d031-c8b0-4dba-a515-9d7a4b0a6881" />
|
|
29
|
+
|
|
30
|
+
# Agently 4
|
|
31
|
+
|
|
32
|
+
[English Introduction](https://github.com/AgentEra/Agently/blob/main/README.md) | [中文介绍](https://github.com/AgentEra/Agently/blob/main/README_CN.md)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
> *Speed Up Your GenAI Application Development*
|
|
36
|
+
|
|
37
|
+
[](https://github.com/AgentEra/Agently/blob/main/LICENSE)
|
|
38
|
+
[](https://pypistats.org/packages/agently)
|
|
39
|
+
[](https://star-history.com/#agentera/agently)
|
|
40
|
+
[](https://x.com/AgentlyTech)
|
|
41
|
+
<a href="https://doc.weixin.qq.com/forms/AIoA8gcHAFMAScAhgZQABIlW6tV3l7QQf">
|
|
42
|
+
<img alt="WeChat" src="https://img.shields.io/badge/WeChat%20Group-Apply-brightgreen?logo=wechat&style=flat-square">
|
|
43
|
+
</a>
|
|
44
|
+
|
|
45
|
+
<p>
|
|
46
|
+
<a href="https://github.com/AgentEra/Agently/discussions/categories/general">
|
|
47
|
+
<img alt="Discussions" src="https://img.shields.io/badge/Agently%20General%20Discussions-JOIN-brightgreen.svg?style=for-the-badge" />
|
|
48
|
+
</a>
|
|
49
|
+
<a href="https://github.com/AgentEra/Agently/discussions/categories/contribute-to-agently-4">
|
|
50
|
+
<img alt="Contribute" src="https://img.shields.io/badge/Contribute%20to%20Agently%204%20-Join-blueviolet.svg?style=for-the-badge">
|
|
51
|
+
</a>
|
|
52
|
+
<a href="https://github.com/AgentEra/Agently/issues">
|
|
53
|
+
<img alt="Issues" src="https://img.shields.io/badge/Report%20Issues-Report-red.svg?style=for-the-badge">
|
|
54
|
+
</a>
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
<hr />
|
|
58
|
+
|
|
59
|
+
<p align="center">
|
|
60
|
+
<b><a href = "https://Agently.tech">💥 Official WebSite</a> - Everything about Agently and what's coming next</b>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<hr />
|
|
64
|
+
|
|
65
|
+
## Getting Started
|
|
66
|
+
|
|
67
|
+
Agently is a Python-based framework for building GenAI applications. You can install it via pip and import features using `from agently import Agently`.
|
|
68
|
+
|
|
69
|
+
Install the latest version via pip:
|
|
70
|
+
|
|
71
|
+
```shell
|
|
72
|
+
pip install -U agently
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> ℹ️ If you're looking for Agently v3's code and documents, please visit branch [`v3-final`](https://github.com/AgentEra/Agently/tree/v3-final)
|
|
76
|
+
|
|
77
|
+
Clone the repository and install locally:
|
|
78
|
+
|
|
79
|
+
```shell
|
|
80
|
+
git clone git@github.com:AgentEra/Agently.git
|
|
81
|
+
cd Agently
|
|
82
|
+
pip install -e .
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
- Docs Site: https://Agently.tech/docs
|
|
88
|
+
- Step-by-step tutorials: `examples/step_by_step/`
|
|
89
|
+
- Auto Loop FastAPI (SSE/WS/POST, Docker-ready): `examples/step_by_step/13-auto_loop_fastapi/`
|
|
90
|
+
|
|
91
|
+
## What is Agently?
|
|
92
|
+
|
|
93
|
+
Agently aims to provide an intuitive, efficient, and developer-friendly framework for GenAI application development. By deeply understanding the runtime control needs of model outputs, Agently bridges the gap between large language models and real-world applications.
|
|
94
|
+
|
|
95
|
+
Agently abstracts away the complexities of:
|
|
96
|
+
- Varying model parameters
|
|
97
|
+
- Output formatting
|
|
98
|
+
- Communication between engineering modules and GenAI logic
|
|
99
|
+
|
|
100
|
+
...while giving developers full control over business logic and integration with existing systems.
|
|
101
|
+
|
|
102
|
+
We believe GenAI is not a generational replacement for current systems but a powerful extension. Engineers and tools are key to turning GenAI's possibilities into reality.
|
|
103
|
+
|
|
104
|
+
Our mission is to build the best developer experience (DX) for GenAI application engineers.
|
|
105
|
+
|
|
106
|
+
## From Demo to Production
|
|
107
|
+
|
|
108
|
+
In real teams, the hardest part is rarely “can the model answer?”—it’s whether the system can survive real traffic, real data, and real dependencies while staying testable, observable, and maintainable. Agently is built to pull LLM uncertainty back inside an engineering boundary.
|
|
109
|
+
|
|
110
|
+
- **Contract-first structured outputs (framework-native, provider-agnostic)**: define schemas with `output()`, enforce critical paths with `ensure_keys`, and parse/repair in the framework pipeline (no hard dependency on provider-specific `response_format` / JSON-schema switches). This keeps interfaces stable even when you switch models or inference servers.
|
|
111
|
+
- **Tool planning + traceability without vendor lock-in**: deciding whether to use a tool, selecting a tool, and building kwargs is a built-in planning step in the framework, not something that requires function-calling support. Every run leaves evidence in `extra` (`tool_logs` / tool calls) for debugging and audit.
|
|
112
|
+
- **Workflow orchestration you can maintain**: TriggerFlow translates visual “low-code graphs” (n8n/Dify/Coze style) into readable code with events, branching, joins, loops, and concurrency limits. Combined with Instant-mode partial node capture + signal-driven execution, you can do real-time UX like “companion robot speaks while actions trigger”.
|
|
113
|
+
- **Grounded answers with citations**: KB retrieval results are structured (`id/document/metadata`) and can be turned into enforced citations (e.g. `source_id` + `quote`) so answers are traceable and reviewable.
|
|
114
|
+
|
|
115
|
+
## Core Features Overview
|
|
116
|
+
|
|
117
|
+
These are the production pain points we keep seeing across teams:
|
|
118
|
+
- **“I asked for JSON, got a paragraph.”** Missing keys, format drift, extra prose → broken parsers.
|
|
119
|
+
- **Tools that work… until they don’t.** Failures become hard to reproduce, debug, and audit.
|
|
120
|
+
- **Low-code graphs that outgrow themselves.** More branches, more state, less confidence to change.
|
|
121
|
+
- **RAG without accountability.** You can’t answer: “Which doc supports this claim?”
|
|
122
|
+
|
|
123
|
+
Agently turns them into engineering primitives you can ship with confidence: schema-first outputs (`output()` + `ensure_keys`), Instant-mode structured streaming, framework-native tool planning with traces, TriggerFlow orchestration, and KB grounding with citations.
|
|
124
|
+
|
|
125
|
+
### Structured and Streamed Output Control for LLMs
|
|
126
|
+
|
|
127
|
+
Schema-first outputs are often the difference between a demo and an API: you define what the system must return, and the framework enforces it at runtime.
|
|
128
|
+
|
|
129
|
+
Agently allows you to control and consume model outputs using a developer-centric pattern:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from agently import Agently
|
|
133
|
+
|
|
134
|
+
agent = Agently.create_agent()
|
|
135
|
+
|
|
136
|
+
(
|
|
137
|
+
agent
|
|
138
|
+
.input("What time is it now?", always=True)
|
|
139
|
+
.info({
|
|
140
|
+
"default_timezone": "",
|
|
141
|
+
"tool_list": [{
|
|
142
|
+
"name": "get_current_time",
|
|
143
|
+
"desc": "Get current time by time zone provided",
|
|
144
|
+
"kwargs": {
|
|
145
|
+
"timezone_str": (str, "time zone string in ZoneInfo()"),
|
|
146
|
+
},
|
|
147
|
+
}]
|
|
148
|
+
})
|
|
149
|
+
.output({
|
|
150
|
+
"first_time_response": (str, ),
|
|
151
|
+
"tool_using_judgement": (bool, ),
|
|
152
|
+
"tool_using_command": (
|
|
153
|
+
{
|
|
154
|
+
"name": (str, "Decide which tool to use by tool name:{tool_list.[].name}"),
|
|
155
|
+
"kwargs": (dict, "According {tool_list.[].args} to output kwargs dictionary"),
|
|
156
|
+
},
|
|
157
|
+
"If {tool_using_judgement}==False, just output {}",
|
|
158
|
+
),
|
|
159
|
+
})
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Then, consume the model response:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
response = agent.get_response()
|
|
167
|
+
|
|
168
|
+
# Get raw text
|
|
169
|
+
response_text = response.get_text()
|
|
170
|
+
|
|
171
|
+
# Get parsed structured data
|
|
172
|
+
response_data = response.get_data()
|
|
173
|
+
|
|
174
|
+
# Instant parsing mode (structured streaming)
|
|
175
|
+
instant_response_generator = response.get_generator(type="instant")
|
|
176
|
+
|
|
177
|
+
use_tool = False
|
|
178
|
+
|
|
179
|
+
for instant_message in instant_response_generator:
|
|
180
|
+
if instant_message.path == "first_time_response":
|
|
181
|
+
if instant_message.delta is not None:
|
|
182
|
+
print(instant_message.delta, end="", flush=True)
|
|
183
|
+
elif instant_message.path == "tool_using_judgement":
|
|
184
|
+
use_tool = instant_message.value
|
|
185
|
+
print()
|
|
186
|
+
if use_tool:
|
|
187
|
+
print("[USE TOOL!]")
|
|
188
|
+
else:
|
|
189
|
+
print("[NO NEED TO USE TOOL!]")
|
|
190
|
+
if use_tool:
|
|
191
|
+
if instant_message.path == "tool_using_command.name" and instant_message.is_complete:
|
|
192
|
+
print(f"I want to use: '{ instant_message.value }'")
|
|
193
|
+
elif instant_message.path == "tool_using_command":
|
|
194
|
+
print(f"call: { instant_message.value }")
|
|
195
|
+
print(f"kwargs: { instant_message.value }")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```shell
|
|
199
|
+
I can check the current time for you. Please specify a timezone (e.g., 'America/New_York') so I can provide the accurate time.
|
|
200
|
+
[NO NEED TO USE TOOL!]
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Provider Compatibility (Local / Hosted / Proxy)
|
|
204
|
+
|
|
205
|
+
Agently unifies model configuration via `OpenAICompatible`, so you can switch between providers while keeping the same developer experience. It also supports common “production reality” knobs like `full_url` and custom auth headers.
|
|
206
|
+
|
|
207
|
+
- Minimal example:
|
|
208
|
+
```python
|
|
209
|
+
from agently import Agently
|
|
210
|
+
|
|
211
|
+
Agently.set_settings(
|
|
212
|
+
"OpenAICompatible",
|
|
213
|
+
{
|
|
214
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
215
|
+
"model": "deepseek-chat",
|
|
216
|
+
"auth": "DEEPSEEK_API_KEY",
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- Example configs: `examples/model_configures/`
|
|
222
|
+
- Step-by-step: `examples/step_by_step/01-settings.py`
|
|
223
|
+
|
|
224
|
+
### Output Reliability (ensure_keys + retries)
|
|
225
|
+
|
|
226
|
+
In batch tasks and pipelines, a missing field can crash the whole job. Agently provides `ensure_keys` + retries for structured output so you can enforce required fields (including wildcard paths for list items).
|
|
227
|
+
|
|
228
|
+
- Minimal example:
|
|
229
|
+
```python
|
|
230
|
+
from agently import Agently
|
|
231
|
+
|
|
232
|
+
agent = Agently.create_agent()
|
|
233
|
+
result = (
|
|
234
|
+
agent.input("Give me 3 todos")
|
|
235
|
+
.output({"todos": [("str", "todo item")]})
|
|
236
|
+
.start(ensure_keys=["todos[*]"], max_retries=2, raise_ensure_failure=False)
|
|
237
|
+
)
|
|
238
|
+
print(result)
|
|
239
|
+
```
|
|
240
|
+
```text
|
|
241
|
+
Output (qwen2.5:7b):
|
|
242
|
+
{'todos': ['Schedule morning exercise routine', 'Prepare presentation slides for the meeting', 'Respond to emails from clients']}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- Step-by-step: `examples/step_by_step/03-output_format_control.py`
|
|
246
|
+
|
|
247
|
+
### Streaming UX (delta / instant / typed_delta)
|
|
248
|
+
|
|
249
|
+
Agently streaming is designed for real applications: reduce waiting, expose decisions early, and route structured fields to different UI regions. For example in a “companion robot” HCI scenario, you often want to mix user-facing text with machine/behavior commands, and consume them as soon as they are parsed.
|
|
250
|
+
|
|
251
|
+
- Minimal example:
|
|
252
|
+
```python
|
|
253
|
+
from agently import Agently
|
|
254
|
+
|
|
255
|
+
agent = Agently.create_agent()
|
|
256
|
+
response = (
|
|
257
|
+
agent.input("Act as a companion robot: greet me and propose a small action you can do next.")
|
|
258
|
+
.output(
|
|
259
|
+
{
|
|
260
|
+
"thinking": ("str", "internal planning (not for users)"),
|
|
261
|
+
"say": ("str", "what the user sees/hears"),
|
|
262
|
+
"actions": [("str", "robot action command(s) for your app to execute")],
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
.get_response()
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
say_label_printed = False
|
|
269
|
+
|
|
270
|
+
def execute_action(action: str) -> None:
|
|
271
|
+
# In real apps, route this to your robot controller / UI event bus.
|
|
272
|
+
print(f"\n[action] {action}")
|
|
273
|
+
|
|
274
|
+
for msg in response.get_generator(type="instant"):
|
|
275
|
+
if msg.path == "say" and msg.delta:
|
|
276
|
+
if not say_label_printed:
|
|
277
|
+
print("[say] ", end="")
|
|
278
|
+
say_label_printed = True
|
|
279
|
+
print(msg.delta, end="", flush=True)
|
|
280
|
+
if msg.path.startswith("actions[") and msg.is_complete:
|
|
281
|
+
execute_action(msg.value)
|
|
282
|
+
print()
|
|
283
|
+
```
|
|
284
|
+
```text
|
|
285
|
+
Output (qwen2.5:7b):
|
|
286
|
+
[say] Hello! Nice to meet you. How about we start with some light conversation? Do you have any favorite hobbies or interests that we could talk about?
|
|
287
|
+
[action] initiate_conversation
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- Step-by-step: `examples/step_by_step/06-streaming.py`
|
|
291
|
+
- Reference patterns: `examples/basic/streaming_print.py`
|
|
292
|
+
|
|
293
|
+
### Tools (built-in + custom + traceable)
|
|
294
|
+
|
|
295
|
+
When a project grows from 1 tool to 20 tools, “it worked yesterday” isn’t enough—you need predictable planning and a trail you can audit.
|
|
296
|
+
|
|
297
|
+
Tools let the model call external functions deterministically. Agently supports:
|
|
298
|
+
- built-in `Search` / `Browse`
|
|
299
|
+
- custom tools via decorator
|
|
300
|
+
- tool call tracing from response metadata (`extra`)
|
|
301
|
+
|
|
302
|
+
Unlike workflows that rely on provider-side function calling, Agently can run a framework-native “tool planning” step even on plain chat endpoints, so tool orchestration stays portable across most modern models.
|
|
303
|
+
|
|
304
|
+
- Minimal example:
|
|
305
|
+
```python
|
|
306
|
+
from agently import Agently
|
|
307
|
+
|
|
308
|
+
agent = Agently.create_agent()
|
|
309
|
+
|
|
310
|
+
@agent.tool_func
|
|
311
|
+
def add(*, a: int, b: int) -> int:
|
|
312
|
+
return a + b
|
|
313
|
+
|
|
314
|
+
agent.use_tools(add)
|
|
315
|
+
print(agent.input("Use the add tool to calculate 12 + 34.").start())
|
|
316
|
+
```
|
|
317
|
+
```text
|
|
318
|
+
Output (qwen2.5:7b):
|
|
319
|
+
The sum of 12 and 34 is calculated as follows:
|
|
320
|
+
|
|
321
|
+
12
|
|
322
|
+
+34
|
|
323
|
+
-----
|
|
324
|
+
46
|
|
325
|
+
|
|
326
|
+
Therefore, the result of 12 + 34 is **46**.
|
|
327
|
+
|
|
328
|
+
No external sources were used in this calculation.
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- Step-by-step: `examples/step_by_step/07-tools.py`
|
|
332
|
+
|
|
333
|
+
### Workflow Orchestration (TriggerFlow)
|
|
334
|
+
|
|
335
|
+
TriggerFlow is for the moment your workflow stops being a sketch: you need events, joins, loops, concurrency limits, and long-term maintainability (including migrating from n8n/Dify/Coze-style graphs into code).
|
|
336
|
+
|
|
337
|
+
TriggerFlow is Agently’s event-driven workflow engine, designed for:
|
|
338
|
+
- branching (`when`, `if_condition`, `match`)
|
|
339
|
+
- concurrency limits (`batch`, `for_each`)
|
|
340
|
+
- loops (`emit` + `when`)
|
|
341
|
+
- runtime stream events (`put_into_stream`)
|
|
342
|
+
|
|
343
|
+
- Minimal example:
|
|
344
|
+
```python
|
|
345
|
+
from agently import TriggerFlow
|
|
346
|
+
|
|
347
|
+
flow = TriggerFlow()
|
|
348
|
+
flow.to(lambda d: f"Hello, {d.value}").end()
|
|
349
|
+
print(flow.start("Agently"))
|
|
350
|
+
```
|
|
351
|
+
```text
|
|
352
|
+
Output (qwen2.5:7b):
|
|
353
|
+
Hello, Agently
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- TriggerFlow series: `examples/step_by_step/11-triggerflow-01_basics.py`
|
|
357
|
+
|
|
358
|
+
### Knowledge Base (embeddings + vector DB)
|
|
359
|
+
|
|
360
|
+
In enterprise RAG, the question is rarely “can we retrieve?”—it’s “can we cite and defend the answer?”.
|
|
361
|
+
|
|
362
|
+
Agently integrates KB pipelines (e.g., Chroma) to ground responses with real documents and metadata.
|
|
363
|
+
|
|
364
|
+
- Minimal example:
|
|
365
|
+
```python
|
|
366
|
+
from agently import Agently
|
|
367
|
+
from agently.integrations.chromadb import ChromaCollection
|
|
368
|
+
|
|
369
|
+
embedding = Agently.create_agent()
|
|
370
|
+
embedding.set_settings(
|
|
371
|
+
"OpenAICompatible",
|
|
372
|
+
{
|
|
373
|
+
"model": "qwen3-embedding:0.6b",
|
|
374
|
+
"base_url": "http://127.0.0.1:11434/v1/",
|
|
375
|
+
"auth": "nothing",
|
|
376
|
+
"model_type": "embeddings",
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
kb = ChromaCollection(collection_name="demo", embedding_agent=embedding)
|
|
380
|
+
kb.add([{"document": "Agently is a GenAI framework.", "metadata": {"source": "demo"}}])
|
|
381
|
+
print(kb.query("What is Agently?"))
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
- Step-by-step: `examples/step_by_step/09-knowledge_base.py`
|
|
385
|
+
|
|
386
|
+
### Deployment Templates (FastAPI, Docker)
|
|
387
|
+
|
|
388
|
+
For engineering delivery, the repo includes a docker-ready FastAPI project that exposes Auto Loop through:
|
|
389
|
+
- SSE streaming
|
|
390
|
+
- WebSocket
|
|
391
|
+
- POST
|
|
392
|
+
|
|
393
|
+
- Minimal example:
|
|
394
|
+
```shell
|
|
395
|
+
cd examples/step_by_step/13-auto_loop_fastapi
|
|
396
|
+
uvicorn app.main:app --reload
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
- Project: `examples/step_by_step/13-auto_loop_fastapi/`
|
|
400
|
+
|
|
401
|
+
### Learn by Examples (Recommended Path)
|
|
402
|
+
|
|
403
|
+
Start with these step-by-step chapters (runnable code + explanations in docs):
|
|
404
|
+
- Settings → `examples/step_by_step/01-settings.py`
|
|
405
|
+
- Prompt Methods → `examples/step_by_step/02-prompt_methods.py`
|
|
406
|
+
- Output Control → `examples/step_by_step/03-output_format_control.py`
|
|
407
|
+
- Streaming → `examples/step_by_step/06-streaming.py`
|
|
408
|
+
- Tools → `examples/step_by_step/07-tools.py`
|
|
409
|
+
- TriggerFlow → `examples/step_by_step/11-triggerflow-01_basics.py`
|
|
410
|
+
- Auto Loop → `examples/step_by_step/12-auto_loop.py`
|
|
411
|
+
|
|
412
|
+
## Agently Helper (Desktop)
|
|
413
|
+
|
|
414
|
+
Agently Helper is a desktop tool to help you quickly **understand** and **test** Agently capabilities without setting up a full project first:
|
|
415
|
+
- Multi-model management and switching
|
|
416
|
+
- Switching between different prompt styles
|
|
417
|
+
- Structured output
|
|
418
|
+
- Streaming output
|
|
419
|
+
|
|
420
|
+
- Windows: https://1drv.ms/u/c/13d5207d1b13e4d3/IQC9XITZl83hR5vU9Z_t-0oKAd3jtMh_fYRypp7T2k8JhCY?e=I72GVH
|
|
421
|
+
- macOS (Apple Silicon): https://1drv.ms/u/c/13d5207d1b13e4d3/IQBhdxYw9Ev1R6qTWb-esVK2AY8PwCxnBHLNuf06Ic4w7sw?e=unMjaD
|
|
422
|
+
- macOS (Intel): https://1drv.ms/u/c/13d5207d1b13e4d3/IQDqUPSqRq7LR7gpCjK60FOSASl4PBsRZPGtHvBAA63U_js?e=EmwVMA
|
|
423
|
+
- Linux: https://1drv.ms/u/c/13d5207d1b13e4d3/IQDVenHvItjFTqnlv294MPD9AUQDvkAKwvBcNufEXSl1nAs?e=Ti5aJ7
|
|
424
|
+
|
|
425
|
+
## 💬 WeChat Group (Join Us)
|
|
426
|
+
|
|
427
|
+
> [Click Here to Apply](https://doc.weixin.qq.com/forms/AIoA8gcHAFMAScAhgZQABIlW6tV3l7QQf)
|
|
428
|
+
> or Scan the QR Code Below:
|
|
429
|
+
|
|
430
|
+
<p align="center">
|
|
431
|
+
<img width="120" alt="WeChat QR" src="https://github.com/AgentEra/Agently/assets/4413155/7f4bc9bf-a125-4a1e-a0a4-0170b718c1a6">
|
|
432
|
+
</p>
|
|
433
|
+
|