agentensemble 0.4.0__tar.gz → 0.6.0__tar.gz
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.
- agentensemble-0.6.0/CHANGELOG.md +135 -0
- agentensemble-0.6.0/PKG-INFO +455 -0
- agentensemble-0.6.0/README.md +394 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/__init__.py +2 -2
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/agents/__init__.py +1 -1
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/agents/hybrid_agent.py +21 -21
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/agents/rag_agent.py +5 -2
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/agents/react_agent.py +53 -70
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/agents/stategraph_agent.py +4 -2
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/agents/structured_agent.py +13 -27
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/graph/workflow.py +25 -8
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/llm/__init__.py +15 -2
- agentensemble-0.6.0/agentensemble/llm/factory.py +63 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/llm/interface.py +1 -1
- agentensemble-0.4.0/agentensemble/llm/mistral_provider.py → agentensemble-0.6.0/agentensemble/llm/langchain_base.py +58 -78
- agentensemble-0.6.0/agentensemble/llm/mistral_provider.py +77 -0
- agentensemble-0.6.0/agentensemble/llm/openai_provider.py +79 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/memory/sqlite_session.py +23 -6
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/orchestration/debate.py +3 -1
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/orchestration/ensemble.py +3 -4
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/py.typed +0 -1
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/router/router_agent.py +12 -18
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/runner.py +5 -12
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/testing/benchmark.py +2 -3
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/testing/comparison.py +1 -3
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/adapters.py +19 -6
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/protocol.py +1 -3
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/rag.py +99 -30
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/registry.py +1 -1
- agentensemble-0.6.0/agentensemble/tools/scraper.py +207 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/search.py +16 -14
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/validator.py +9 -11
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tracing/hooks.py +22 -6
- agentensemble-0.6.0/agentensemble/utils/__init__.py +21 -0
- agentensemble-0.6.0/agentensemble/utils/llm.py +207 -0
- agentensemble-0.6.0/agentensemble.egg-info/PKG-INFO +455 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble.egg-info/SOURCES.txt +8 -1
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble.egg-info/requires.txt +2 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/pyproject.toml +19 -7
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_agents.py +1 -1
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_comparison.py +3 -3
- agentensemble-0.6.0/tests/test_components.py +444 -0
- agentensemble-0.6.0/tests/test_llm_providers.py +165 -0
- agentensemble-0.6.0/tests/test_react_loop.py +200 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_runner.py +1 -1
- agentensemble-0.6.0/tests/test_utils_structured.py +114 -0
- agentensemble-0.4.0/CHANGELOG.md +0 -73
- agentensemble-0.4.0/PKG-INFO +0 -764
- agentensemble-0.4.0/README.md +0 -705
- agentensemble-0.4.0/agentensemble/tools/scraper.py +0 -106
- agentensemble-0.4.0/agentensemble/utils/__init__.py +0 -10
- agentensemble-0.4.0/agentensemble/utils/llm.py +0 -107
- agentensemble-0.4.0/agentensemble.egg-info/PKG-INFO +0 -764
- {agentensemble-0.4.0 → agentensemble-0.6.0}/LICENSE +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/MANIFEST.in +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/agents/base.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/core/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/core/protocol.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/graph/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/memory/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/memory/session.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/orchestration/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/orchestration/pipeline.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/orchestration/supervisor.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/orchestration/swarm.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/planner/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/planner/planner_agent.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/router/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/testing/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/testing/metrics.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tools/function_tool.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble/tracing/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble.egg-info/dependency_links.txt +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/agentensemble.egg-info/top_level.txt +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/setup.cfg +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_memory.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_orchestration.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_react_session.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_release.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_tools.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.6.0}/tests/test_tracing.py +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to AgentEnsemble will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.6.0] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `ReActAgent` session memory: the user's question and the final answer were never saved, so
|
|
14
|
+
follow-up turns lost context. A turn now stores the question, tool exchanges and answer.
|
|
15
|
+
- `ReActAgent` disabled tools on every turn after the first turn that used one (tool messages
|
|
16
|
+
in the history counted as "already called"). Only the current turn is checked now.
|
|
17
|
+
- Session history windows could start with an orphan `tool` message, which chat APIs reject.
|
|
18
|
+
- `RunConfig(session=...)` had no effect on `ReActAgent`.
|
|
19
|
+
- `SQLiteSession` leaked one open connection per call (`with sqlite3.connect()` does not close).
|
|
20
|
+
- `WorkflowGraph`: a node without a matching outgoing edge re-ran until the 50-step limit
|
|
21
|
+
instead of ending the run. `WorkflowGraph` and `StateGraphAgent` reported every node as
|
|
22
|
+
visited; `nodes_visited` now lists the nodes that ran.
|
|
23
|
+
- `ScraperTool(provider="simple")` returned placeholder text instead of fetching the page.
|
|
24
|
+
- `RAGAgent` always reported `fallback_attempts=0` and re-fetched URLs on every fallback.
|
|
25
|
+
- `RAGTool` and `SearchTool` had no tool description, so models saw "Tool: rag" and often
|
|
26
|
+
skipped them. URL loading in `RAGTool` had no timeout and failures were printed.
|
|
27
|
+
- `HybridAgent` blocked the event loop while running sync tools.
|
|
28
|
+
- `import agentensemble` logged a langchain-community USER_AGENT warning.
|
|
29
|
+
- LangChain tool calls were built with `type="function"` instead of `"tool_call"`.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- `ScraperTool` fetches with httpx (timeout, redirects, `max_chars`, http(s) only) and converts
|
|
33
|
+
HTML to text; `ScraperTool.fetch()` raises `ScraperError`. New `html_to_text()` helper.
|
|
34
|
+
- `RAGTool.index_texts()` indexes local text without network; `RAGTool(description=...,
|
|
35
|
+
fetch_timeout=...)`.
|
|
36
|
+
- `get_default_embeddings()` / `get_openai_embeddings()`: `RAGTool` embeddings follow the same
|
|
37
|
+
OpenAI-first provider selection as chat models.
|
|
38
|
+
- `WorkflowGraph(max_steps=...)` and a `steps` count in its metadata.
|
|
39
|
+
- `estimate_cost` knows the `gpt-4.1` family and matches dated model names by prefix.
|
|
40
|
+
- Four offline-runnable examples (customer-support RAG, ticket triage, order assistant with
|
|
41
|
+
approval, CSV analytics) and a rule-based `LLMProvider` for running examples without a key.
|
|
42
|
+
- CodeQL workflow, pre-commit config, mypy and coverage gate (85%) in CI; `httpx` is now a
|
|
43
|
+
declared dependency.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- Library code logs through `logging` instead of printing.
|
|
47
|
+
- Ruff rules broadened (SIM, C4, RUF, PTH, RET, PIE); mypy runs with `check_untyped_defs`.
|
|
48
|
+
- README rewritten: architecture diagrams, configuration and API tables, references.
|
|
49
|
+
|
|
50
|
+
## [0.5.0] - 2026-10-31
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- **OpenAI support.** New `OpenAILLMProvider` (LangChain `ChatOpenAI`, native tool calling).
|
|
54
|
+
Default model `gpt-4.1-mini`, configurable via `OPENAI_MODEL`; `OPENAI_BASE_URL` for
|
|
55
|
+
OpenAI-compatible endpoints. Temperature is omitted for reasoning models (o-series, gpt-5).
|
|
56
|
+
- `get_default_provider()` / `resolve_provider_name()`: agents now pick OpenAI when
|
|
57
|
+
`OPENAI_API_KEY` is set, otherwise Mistral; `AGENTENSEMBLE_LLM_PROVIDER` forces one.
|
|
58
|
+
- `agentensemble.utils.get_chat_model()` and `get_openai_model()`; `StructuredAgent` uses the
|
|
59
|
+
same OpenAI-or-Mistral selection.
|
|
60
|
+
- `langchain-openai` is now a core dependency.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
- Calling a sync agent API twice (e.g. `agent.run()` then `agent.run()`, or `run()` followed by
|
|
64
|
+
`asyncio.run(agent.arun())`) failed with `RuntimeError: Event loop is closed`. Each sync call
|
|
65
|
+
uses `asyncio.run()`, and the LLM's async HTTP client stayed bound to the first, closed loop.
|
|
66
|
+
Providers now rebuild their chat model when used on a new event loop, and each OpenAI model
|
|
67
|
+
gets its own async HTTP client instead of the process-wide cached one.
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
- Shared LangChain conversion logic moved to `agentensemble.llm.langchain_base`
|
|
71
|
+
(`LangChainChatProvider`); `MistralLLMProvider` keeps its public API.
|
|
72
|
+
|
|
73
|
+
## [0.4.0] - 2026-10-31
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
- `ReActAgent` can now be constructed without `MISTRAL_API_KEY` (matching `HybridAgent` and
|
|
77
|
+
`RouterAgent`); `run()` / `astream()` raise a clear `RuntimeError` until an LLM is configured.
|
|
78
|
+
Previously the constructor raised `ValueError`, which broke 6 tests and any offline usage.
|
|
79
|
+
- `SearchTool`'s DuckDuckGo fallback works again: `langchain-community` now needs the `ddgs`
|
|
80
|
+
package (the `search` extra previously installed the old `duckduckgo-search`).
|
|
81
|
+
- `SearchTool` creates its backend lazily on first use, so it can be registered in a
|
|
82
|
+
`ToolRegistry` without optional dependencies; a missing backend raises an `ImportError` with
|
|
83
|
+
the `pip install 'agentensemble[search]'` hint. Unknown providers still fail fast.
|
|
84
|
+
- Tool schema type inference uses identity checks (`ann is str`), not equality.
|
|
85
|
+
|
|
86
|
+
### Added
|
|
87
|
+
- `MISTRAL_MODEL` environment variable to choose the default model for `MistralLLMProvider`.
|
|
88
|
+
- `__version__` read from installed package metadata (`importlib.metadata`).
|
|
89
|
+
- GitHub Actions CI (ruff lint + format check, pytest on Python 3.10–3.13).
|
|
90
|
+
- New tests: ReActAgent tool-calling loop with a fake LLM, SearchTool behaviour, version, model
|
|
91
|
+
override (64 tests total).
|
|
92
|
+
- SECURITY.md, CODE_OF_CONDUCT.md, issue/PR templates, Dependabot, CITATION.cff.
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
- Requires Python 3.10+ (3.9 is end-of-life); classifiers for 3.10–3.13.
|
|
96
|
+
- Packaging is `pyproject.toml`-only (removed `setup.py` and `requirements.txt`), PEP 639
|
|
97
|
+
`license = "MIT"`, LICENSE and CHANGELOG shipped in the sdist.
|
|
98
|
+
- Dependency floors raised to the versions the code actually needs: `langchain>=1.1`,
|
|
99
|
+
`langchain-core>=1.0`, `langchain-community>=0.3`, `langchain-mistralai>=0.2`. The unused
|
|
100
|
+
direct `langgraph` dependency was dropped (it is still installed transitively by `langchain`).
|
|
101
|
+
- `dev` extra now includes `build` and `twine`; `black` replaced by `ruff format`.
|
|
102
|
+
- Codebase formatted with `ruff format` and modernised type hints (`X | None`).
|
|
103
|
+
|
|
104
|
+
## [0.3.0] - 2026-03-10
|
|
105
|
+
|
|
106
|
+
### Added
|
|
107
|
+
- Observability (`TraceHooks`, token usage, cost estimation), human-in-the-loop
|
|
108
|
+
(`interrupt_before_tools`), architecture diagram.
|
|
109
|
+
|
|
110
|
+
## [0.2.0] - 2026-03-10
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
- `RouterAgent`, `PlannerAgent`, `WorkflowGraph`, tracing.
|
|
114
|
+
|
|
115
|
+
## [0.1.0] - 2025-12-09
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
- Initial release
|
|
119
|
+
- ReActAgent implementation
|
|
120
|
+
- StateGraphAgent implementation
|
|
121
|
+
- RAGAgent with fallback strategies
|
|
122
|
+
- HybridAgent with intelligent routing
|
|
123
|
+
- Ensemble orchestration patterns (Supervisor, Swarm, Pipeline)
|
|
124
|
+
- Tool ecosystem (Search, Scraper, RAG, Validator)
|
|
125
|
+
- Tool registry for centralized management
|
|
126
|
+
- Testing framework (AgentComparison, Benchmark, Metrics)
|
|
127
|
+
- Base agent class and state management
|
|
128
|
+
- Comprehensive documentation
|
|
129
|
+
|
|
130
|
+
### Infrastructure
|
|
131
|
+
- Python 3.9+ support
|
|
132
|
+
- pyproject.toml for modern packaging
|
|
133
|
+
- Development dependencies (pytest, black, ruff, mypy)
|
|
134
|
+
- MIT License
|
|
135
|
+
- Contributing guidelines
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentensemble
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Build, orchestrate, trace, and test multi-agent LLM systems in Python: ReAct, RAG, router, planner, workflow graphs, swarms, debates, and pipelines.
|
|
5
|
+
Author-email: Irfan Ali <irfanali29@hotmail.com>
|
|
6
|
+
Maintainer-email: Irfan Ali <irfanali29@hotmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/irfanalidv/AgentEnsemble
|
|
9
|
+
Project-URL: Documentation, https://github.com/irfanalidv/AgentEnsemble#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/irfanalidv/AgentEnsemble
|
|
11
|
+
Project-URL: Issues, https://github.com/irfanalidv/AgentEnsemble/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/irfanalidv/AgentEnsemble/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: ai-agents,multi-agent,multi-agent-systems,agent-orchestration,orchestration,llm,langchain,langgraph,mistral,openai,rag,react-agent,tool-calling,function-calling,agent-testing,agentic-ai,state-graph,workflow,tracing
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: langchain>=1.1.0
|
|
31
|
+
Requires-Dist: langchain-core>=1.0.0
|
|
32
|
+
Requires-Dist: langchain-community>=0.3.0
|
|
33
|
+
Requires-Dist: langchain-mistralai>=0.2.0
|
|
34
|
+
Requires-Dist: langchain-openai>=1.0.0
|
|
35
|
+
Requires-Dist: httpx>=0.25.0
|
|
36
|
+
Requires-Dist: pydantic>=2.0.0
|
|
37
|
+
Requires-Dist: typing-extensions>=4.5.0
|
|
38
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
43
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
44
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
45
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
46
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
47
|
+
Provides-Extra: observability
|
|
48
|
+
Requires-Dist: langsmith>=0.1.0; extra == "observability"
|
|
49
|
+
Requires-Dist: opentelemetry-api>=1.20.0; extra == "observability"
|
|
50
|
+
Provides-Extra: rag
|
|
51
|
+
Requires-Dist: chromadb>=0.4.0; extra == "rag"
|
|
52
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "rag"
|
|
53
|
+
Requires-Dist: langchain-text-splitters>=0.0.1; extra == "rag"
|
|
54
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == "rag"
|
|
55
|
+
Provides-Extra: search
|
|
56
|
+
Requires-Dist: ddgs>=9.0.0; extra == "search"
|
|
57
|
+
Requires-Dist: google-search-results>=2.4.2; extra == "search"
|
|
58
|
+
Provides-Extra: all
|
|
59
|
+
Requires-Dist: agentensemble[observability,rag,search]; extra == "all"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
<div align="center">
|
|
63
|
+
|
|
64
|
+
# AgentEnsemble
|
|
65
|
+
|
|
66
|
+
**Build, orchestrate, trace, and test multi-agent LLM systems in Python, with OpenAI or Mistral.**
|
|
67
|
+
|
|
68
|
+
[](https://pypi.org/project/agentensemble/)
|
|
69
|
+
[](https://pypi.org/project/agentensemble/)
|
|
70
|
+
[](https://pepy.tech/projects/agentensemble)
|
|
71
|
+
[](https://github.com/irfanalidv/AgentEnsemble/actions/workflows/tests.yml)
|
|
72
|
+
[](https://github.com/irfanalidv/AgentEnsemble/actions/workflows/codeql.yml)
|
|
73
|
+
[](https://github.com/irfanalidv/AgentEnsemble/blob/main/LICENSE)
|
|
74
|
+
[](https://github.com/astral-sh/ruff)
|
|
75
|
+
[](https://mypy-lang.org/)
|
|
76
|
+
[](https://github.com/irfanalidv/AgentEnsemble/blob/main/CONTRIBUTING.md)
|
|
77
|
+
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
## Why AgentEnsemble
|
|
81
|
+
|
|
82
|
+
Most agent projects need the same handful of pieces: a tool-calling loop, a way to combine
|
|
83
|
+
several agents, memory between turns, a pause before risky actions, and visibility into tokens
|
|
84
|
+
and cost. AgentEnsemble gives you those pieces as small, plain-Python classes behind one
|
|
85
|
+
`run()` / `arun()` interface, so you can start with one agent and grow into routers, pipelines,
|
|
86
|
+
swarms and debates without changing how you call them. LangChain is used only inside the LLM
|
|
87
|
+
providers; agents, tools and results are your own objects and plain dicts.
|
|
88
|
+
|
|
89
|
+
## Features
|
|
90
|
+
|
|
91
|
+
| Area | What you get |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| **Agents** | `ReActAgent` (native tool calling), `HybridAgent` (search → RAG → validate), `RAGAgent`, `StructuredAgent` (Pydantic output), `StateGraphAgent`, `RouterAgent`, `PlannerAgent` |
|
|
94
|
+
| **Orchestration** | `Ensemble` with supervisor / swarm (parallel) / pipeline modes, `DebateOrchestrator`, `WorkflowGraph` with conditional edges |
|
|
95
|
+
| **Tools** | `@function_tool` (JSON schema from type hints), `SearchTool` (Serper or DuckDuckGo), `RAGTool` (URLs or local text), `ScraperTool` (httpx or Playwright), `ValidationTool`, `ToolRegistry` |
|
|
96
|
+
| **LLM providers** | OpenAI and Mistral built in, picked from the environment (OpenAI first); any `LLMProvider` subclass works |
|
|
97
|
+
| **Runtime** | `Runner` + `RunConfig`: sessions (`InMemorySession`, `SQLiteSession`), retries, lifecycle hooks, human-in-the-loop interrupts |
|
|
98
|
+
| **Observability** | `TraceHooks` events for runs, LLM calls and tools, token usage, cost estimates, `astream()` events for UIs |
|
|
99
|
+
| **Evaluation** | `AgentComparison`, `Benchmark`, `Metrics` to compare agents on the same tasks |
|
|
100
|
+
| **Quality** | Typed (`py.typed`, mypy-clean), tested with fake LLMs (no network in CI), Python 3.10 to 3.13 |
|
|
101
|
+
|
|
102
|
+
## Install
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install agentensemble # or: uv add agentensemble
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| Extra | Adds | Install |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| `search` | DuckDuckGo (`ddgs`) and Serper backends for `SearchTool` | `pip install "agentensemble[search]"` |
|
|
111
|
+
| `rag` | Chroma, sentence-transformers, text splitters, BeautifulSoup for `RAGTool` | `pip install "agentensemble[rag]"` |
|
|
112
|
+
| `observability` | LangSmith and OpenTelemetry API | `pip install "agentensemble[observability]"` |
|
|
113
|
+
| `all` | All of the above | `pip install "agentensemble[all]"` |
|
|
114
|
+
|
|
115
|
+
## Quickstart
|
|
116
|
+
|
|
117
|
+
Needs `OPENAI_API_KEY` (or `MISTRAL_API_KEY`) in the environment or a `.env` file.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from agentensemble import ReActAgent, function_tool
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@function_tool(description="Look up the shipping status of an order by id")
|
|
124
|
+
def order_status(order_id: str) -> str:
|
|
125
|
+
return {"A100": "shipped", "B200": "processing"}.get(order_id, "not found")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
agent = ReActAgent(tools=[order_status]) # OpenAI if OPENAI_API_KEY is set, else Mistral
|
|
129
|
+
result = agent.run("Has order A100 shipped yet?")
|
|
130
|
+
print(result["result"])
|
|
131
|
+
print(result["metadata"])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Output from a run with `gpt-4.1-mini` (model wording varies):
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
Yes, order A100 has shipped.
|
|
138
|
+
{'iterations': 1, 'tool_calls': 1, 'agent': 'react_agent', 'total_usage': {'input_tokens': 439, 'output_tokens': 24, 'total_tokens': 463}, 'cost_usd': 0.00021400000000000002}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
No key yet? Graph workflows run without an LLM, and every script in
|
|
142
|
+
[`examples/`](https://github.com/irfanalidv/AgentEnsemble/tree/main/examples) marked "offline"
|
|
143
|
+
runs with a built-in rule-based stand-in model:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from agentensemble import WorkflowGraph
|
|
147
|
+
|
|
148
|
+
graph = (
|
|
149
|
+
WorkflowGraph(entry="classify")
|
|
150
|
+
.add_node("classify", lambda s: {"context": {"refund": "refund" in s["query"].lower()}})
|
|
151
|
+
.add_node("refund", lambda s: {"result": "Routed to the refunds team"})
|
|
152
|
+
.add_node("general", lambda s: {"result": "Routed to general support"})
|
|
153
|
+
.add_edge("classify", "refund", lambda s: s["context"]["refund"])
|
|
154
|
+
.add_edge("classify", "general")
|
|
155
|
+
)
|
|
156
|
+
out = graph.run("I want a refund for my order")
|
|
157
|
+
print(out["result"], out["metadata"]["nodes_visited"])
|
|
158
|
+
# Routed to the refunds team ['classify', 'refund']
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## How it works
|
|
162
|
+
|
|
163
|
+
```mermaid
|
|
164
|
+
flowchart LR
|
|
165
|
+
App["Your code"] --> Runner["Runner.run / arun<br/>RunConfig: session, retries,<br/>hooks, interrupts"]
|
|
166
|
+
Runner --> Orchestration["Ensemble · DebateOrchestrator<br/>RouterAgent · PlannerAgent"]
|
|
167
|
+
Runner --> Agent
|
|
168
|
+
Orchestration -->|"agent.arun(query)"| Agent["Agents<br/>ReActAgent · HybridAgent<br/>RAGAgent · StructuredAgent"]
|
|
169
|
+
Agent -->|"LLMMessage list + ToolSchema list"| Provider["LLMProvider<br/>OpenAILLMProvider · MistralLLMProvider<br/>or your own"]
|
|
170
|
+
Provider -->|"LLMResponse: content / tool_calls / usage"| Agent
|
|
171
|
+
Agent -->|"ainvoke_tool(name, args)"| Tools["Tools<br/>@function_tool · SearchTool · RAGTool<br/>ScraperTool · ValidationTool"]
|
|
172
|
+
Agent <-->|"load / save turn"| Session["Session<br/>InMemorySession · SQLiteSession"]
|
|
173
|
+
Agent -->|"TraceEvent"| Trace["TraceHooks<br/>usage · cost · timings"]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
One `ReActAgent` turn, including the optional human approval step:
|
|
177
|
+
|
|
178
|
+
```mermaid
|
|
179
|
+
sequenceDiagram
|
|
180
|
+
participant R as Runner
|
|
181
|
+
participant A as ReActAgent
|
|
182
|
+
participant S as Session
|
|
183
|
+
participant L as LLMProvider
|
|
184
|
+
participant T as Tool
|
|
185
|
+
R->>A: arun(query, run_config, session)
|
|
186
|
+
A->>S: get_messages(limit=20)
|
|
187
|
+
A->>L: agenerate(system + history + query, tools)
|
|
188
|
+
L-->>A: tool_calls
|
|
189
|
+
alt tool listed in interrupt_before_tools
|
|
190
|
+
A-->>R: {"__interrupted__": True, "run_state", "pending_tool"}
|
|
191
|
+
R->>A: arun(..., resume=run_state, resume_value=approved_result)
|
|
192
|
+
else
|
|
193
|
+
A->>T: ainvoke_tool(name, arguments)
|
|
194
|
+
T-->>A: result (errors are returned as text)
|
|
195
|
+
end
|
|
196
|
+
A->>L: agenerate(... + tool results), no tools
|
|
197
|
+
L-->>A: final answer
|
|
198
|
+
A->>S: add_messages(user, tool exchange, answer)
|
|
199
|
+
A-->>R: {"result": ..., "metadata": {iterations, tool_calls, total_usage, cost_usd}}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Design decisions**
|
|
203
|
+
|
|
204
|
+
- **One provider interface.** Agents only see `LLMProvider.generate` / `agenerate` and Pydantic
|
|
205
|
+
message models (`agentensemble/llm/interface.py`). The OpenAI and Mistral providers share
|
|
206
|
+
one LangChain adapter (`llm/langchain_base.py`); adding a provider means subclassing
|
|
207
|
+
`LLMProvider` or `LangChainChatProvider`.
|
|
208
|
+
- **Native tool calling.** Tools are described by JSON schemas (inferred from type hints by
|
|
209
|
+
`@function_tool`) and called through the model's function-calling API, not parsed from text.
|
|
210
|
+
- **Bounded tool use per turn.** After the first round of tool results in a turn, the model is
|
|
211
|
+
asked to answer without tools. This keeps cost and latency predictable; `max_iterations`
|
|
212
|
+
caps the loop as well.
|
|
213
|
+
- **Async first.** Every agent has `arun()`; `run()` wraps it with `asyncio.run()`. Providers
|
|
214
|
+
rebuild their HTTP client when used on a new event loop, so repeated sync calls work.
|
|
215
|
+
- **Plain results.** Agents return `{"result": str, "metadata": dict}`. The human-in-the-loop
|
|
216
|
+
`run_state` is a JSON-serialisable dict, so a paused run can be stored and resumed later.
|
|
217
|
+
- **Usable without keys.** LLM agents can be constructed, inspected and registered without an
|
|
218
|
+
API key; `run()` raises a clear `RuntimeError` until a provider is configured.
|
|
219
|
+
|
|
220
|
+
<details>
|
|
221
|
+
<summary>Module map</summary>
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
agentensemble/
|
|
225
|
+
├── agents/ ReActAgent, HybridAgent, RAGAgent, StateGraphAgent, StructuredAgent, BaseAgent
|
|
226
|
+
├── llm/ LLMProvider, OpenAILLMProvider, MistralLLMProvider, get_default_provider
|
|
227
|
+
├── tools/ @function_tool, SearchTool, RAGTool, ScraperTool, ValidationTool, ToolRegistry
|
|
228
|
+
├── orchestration/ Ensemble, Supervisor/Swarm/PipelineOrchestrator, DebateOrchestrator
|
|
229
|
+
├── router/ RouterAgent (LLM picks an agent via a constrained tool call)
|
|
230
|
+
├── planner/ PlannerAgent (rule-based decomposition; override _decompose for LLM planning)
|
|
231
|
+
├── graph/ WorkflowGraph, Edge
|
|
232
|
+
├── memory/ Session protocol, InMemorySession, SQLiteSession
|
|
233
|
+
├── tracing/ TraceHooks, TraceEvent, estimate_cost
|
|
234
|
+
├── testing/ AgentComparison, Benchmark, Metrics
|
|
235
|
+
├── utils/ get_chat_model, get_default_embeddings, provider helpers
|
|
236
|
+
└── runner.py Runner, RunConfig, RunHooks
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
<p align="center">
|
|
240
|
+
<img src="https://raw.githubusercontent.com/irfanalidv/AgentEnsemble/main/docs/agentensemble-architecture.png" alt="AgentEnsemble components" width="820"/>
|
|
241
|
+
</p>
|
|
242
|
+
|
|
243
|
+
</details>
|
|
244
|
+
|
|
245
|
+
## Use cases and examples
|
|
246
|
+
|
|
247
|
+
Runnable scripts in [`examples/`](https://github.com/irfanalidv/AgentEnsemble/tree/main/examples).
|
|
248
|
+
The first four run offline (no key) and switch to OpenAI or Mistral when a key is set; pass
|
|
249
|
+
`--offline` to force offline mode.
|
|
250
|
+
|
|
251
|
+
| Script | Use case | Needs |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| [`customer_support_rag.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/customer_support_rag.py) | Answer customer questions from a help-center FAQ with `RAGTool.index_texts` and `ReActAgent` | `[rag]` extra; offline |
|
|
254
|
+
| [`support_ticket_triage.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/support_ticket_triage.py) | Route tickets with `WorkflowGraph`; a `ValidationTool` gate escalates bad refund replies | offline |
|
|
255
|
+
| [`order_assistant_approval.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/order_assistant_approval.py) | Order bot with `SQLiteSession` memory, `TraceHooks`, and human approval before refunds | offline |
|
|
256
|
+
| [`sales_csv_analyst.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/sales_csv_analyst.py) | Answer revenue questions over a CSV with deterministic `@function_tool` analytics | offline |
|
|
257
|
+
| [`graph_workflow_example.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/graph_workflow_example.py), [`runner_example.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/runner_example.py), [`research_agent_example.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/research_agent_example.py) | Graphs, `Runner` hooks, planner → router → executor | offline |
|
|
258
|
+
| [`react_llm_example.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/react_llm_example.py), [`swarm_example.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/swarm_example.py), [`debate_example.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/debate_example.py), [`streaming_example.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/streaming_example.py), [`showcase_all.py`](https://github.com/irfanalidv/AgentEnsemble/blob/main/examples/showcase_all.py) | Search agents, parallel swarm, debate, streaming events, full tour | API key (+ `[search]`) |
|
|
259
|
+
|
|
260
|
+
<details>
|
|
261
|
+
<summary>Recipes (need <code>OPENAI_API_KEY</code> or <code>MISTRAL_API_KEY</code>)</summary>
|
|
262
|
+
|
|
263
|
+
**Human approval before a sensitive tool**
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from agentensemble import ReActAgent, RunConfig, Runner, function_tool
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@function_tool(description="Send money to a recipient")
|
|
270
|
+
def execute_payment(amount: float, recipient: str) -> str:
|
|
271
|
+
return f"Paid ${amount:.2f} to {recipient}"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
agent = ReActAgent(tools=[execute_payment])
|
|
275
|
+
config = RunConfig(interrupt_before_tools=["execute_payment"])
|
|
276
|
+
|
|
277
|
+
result = Runner.run(agent, "Pay $50 to Alice", config=config)
|
|
278
|
+
if result.get("__interrupted__"):
|
|
279
|
+
pending = result["pending_tool"] # {'name': 'execute_payment', 'arguments': {...}}
|
|
280
|
+
approved = execute_payment.run(**pending["arguments"]) # after a human says yes
|
|
281
|
+
result = Runner.run(
|
|
282
|
+
agent,
|
|
283
|
+
"Pay $50 to Alice",
|
|
284
|
+
config=config,
|
|
285
|
+
resume=result["run_state"],
|
|
286
|
+
resume_value=approved,
|
|
287
|
+
)
|
|
288
|
+
print(result["result"])
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Multi-turn memory**
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from agentensemble import InMemorySession, ReActAgent, RunConfig, Runner
|
|
295
|
+
|
|
296
|
+
agent = ReActAgent(name="chatbot")
|
|
297
|
+
config = RunConfig(session=InMemorySession("chat-1")) # or SQLiteSession("chat-1", db_path=...)
|
|
298
|
+
Runner.run(agent, "My name is Priya and I live in Lisbon.", config=config)
|
|
299
|
+
print(Runner.run(agent, "Which city do I live in?", config=config)["result"]) # ... Lisbon.
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Route to the right specialist**
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from agentensemble import ReActAgent, RouterAgent
|
|
306
|
+
|
|
307
|
+
router = RouterAgent(
|
|
308
|
+
agents={"billing": ReActAgent(name="billing"), "tech_support": ReActAgent(name="tech_support")}
|
|
309
|
+
)
|
|
310
|
+
out = router.run("I was charged twice for my subscription this month")
|
|
311
|
+
print(out["metadata"]["routed_to"]) # billing
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Structured output**
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from pydantic import BaseModel, Field
|
|
318
|
+
from agentensemble import StructuredAgent
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class ProductReview(BaseModel):
|
|
322
|
+
product: str = Field(description="Product name")
|
|
323
|
+
rating: int = Field(description="1-5 stars")
|
|
324
|
+
summary: str = Field(description="Brief summary")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
agent = StructuredAgent(response_format=ProductReview)
|
|
328
|
+
result = agent.run("Review: Acme X1 headphones - 4 stars, great sound, short battery life")
|
|
329
|
+
print(result["structured_response"])
|
|
330
|
+
# product='Acme X1 headphones' rating=4 summary='Great sound quality but battery life is short.'
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Parallel swarm and debate** (`ReActAgent(tools=[])` answers from the model alone)
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from agentensemble import DebateOrchestrator, ReActAgent, SwarmOrchestrator
|
|
337
|
+
|
|
338
|
+
swarm = SwarmOrchestrator(
|
|
339
|
+
agents={"optimist": ReActAgent(name="optimist"), "skeptic": ReActAgent(name="skeptic")}
|
|
340
|
+
)
|
|
341
|
+
views = swarm.perform("Should a 5-person startup self-host its LLM?")
|
|
342
|
+
|
|
343
|
+
debate = DebateOrchestrator(
|
|
344
|
+
solvers=[ReActAgent(name="s1"), ReActAgent(name="s2")],
|
|
345
|
+
aggregator=ReActAgent(name="judge"),
|
|
346
|
+
rounds=2,
|
|
347
|
+
)
|
|
348
|
+
print(debate.debate("A bat and a ball cost $1.10; the bat costs $1 more. Ball price?")["result"])
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Tracing, usage and cost**
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from agentensemble import ReActAgent, RunConfig, Runner, TraceHooks
|
|
355
|
+
|
|
356
|
+
hooks = TraceHooks(on_event=lambda e: print(e.type.value, e.duration_ms))
|
|
357
|
+
result = Runner.run(ReActAgent(), "Hello!", config=RunConfig(trace_hooks=hooks))
|
|
358
|
+
print(result["metadata"].get("total_usage"), result["metadata"].get("cost_usd"))
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
</details>
|
|
362
|
+
|
|
363
|
+
## Configuration
|
|
364
|
+
|
|
365
|
+
**Environment variables** (a `.env` file in the working directory is loaded automatically; see
|
|
366
|
+
[`.env.example`](https://github.com/irfanalidv/AgentEnsemble/blob/main/.env.example)):
|
|
367
|
+
|
|
368
|
+
| Variable | Purpose | Default |
|
|
369
|
+
| --- | --- | --- |
|
|
370
|
+
| `OPENAI_API_KEY` | Enables OpenAI; preferred when set | none |
|
|
371
|
+
| `OPENAI_MODEL` | Default model for `OpenAILLMProvider`. Reasoning models (`o*`, `gpt-5*`) run at the API's default temperature | `gpt-4.1-mini` |
|
|
372
|
+
| `OPENAI_BASE_URL` | OpenAI-compatible endpoint (vLLM, LM Studio, proxies) | OpenAI |
|
|
373
|
+
| `MISTRAL_API_KEY` | Enables Mistral; used when no OpenAI key is set | none |
|
|
374
|
+
| `MISTRAL_MODEL` | Default model for `MistralLLMProvider` | `mistral-large-latest` |
|
|
375
|
+
| `AGENTENSEMBLE_LLM_PROVIDER` | Force `openai` or `mistral` | auto |
|
|
376
|
+
| `SERPER_API_KEY` | `SearchTool()` uses Serper when set, else DuckDuckGo | none |
|
|
377
|
+
|
|
378
|
+
**Main options**
|
|
379
|
+
|
|
380
|
+
| Where | Option | Default | Meaning |
|
|
381
|
+
| --- | --- | --- | --- |
|
|
382
|
+
| `ReActAgent` / `HybridAgent` | `llm=` | from env | Any `LLMProvider` instance |
|
|
383
|
+
| | `max_iterations=` | 10 / 15 | Upper bound on loop steps |
|
|
384
|
+
| | `session=` | `None` | Session used when none is passed via `RunConfig` |
|
|
385
|
+
| `RunConfig` | `session`, `hooks`, `trace_hooks` | `None` | Memory, lifecycle callbacks, tracing |
|
|
386
|
+
| | `interrupt_before_tools` | `None` | Tool names that pause the run for approval |
|
|
387
|
+
| | `max_retries`, `retry_on` | `0`, `(Exception,)` | Retry policy for the whole run |
|
|
388
|
+
| `RAGTool` | `embeddings=`, `vector_store=` | env provider, in-memory | Bring your own embeddings or store |
|
|
389
|
+
| | `chunk_size`, `chunk_overlap`, `fetch_timeout` | 1000, 200, 30 s | Splitting and URL loading |
|
|
390
|
+
| `ScraperTool` | `provider`, `timeout`, `max_chars` | `"simple"`, 15 s, 20000 | httpx or Playwright fetch |
|
|
391
|
+
| `WorkflowGraph` | `entry`, `exit_nodes`, `max_steps` | `"start"`, `["end"]`, 50 | Graph execution limits |
|
|
392
|
+
|
|
393
|
+
## API overview
|
|
394
|
+
|
|
395
|
+
| Object | Kind | Summary |
|
|
396
|
+
| --- | --- | --- |
|
|
397
|
+
| `ReActAgent(tools, llm, session, max_iterations)` | agent | Tool-calling loop; `run`, `arun`, `astream` |
|
|
398
|
+
| `HybridAgent(tools, llm, early_stopping)` | agent | LLM (or fixed) routing over search / RAG / validate |
|
|
399
|
+
| `RAGAgent(tools, fallback_strategies)` | agent | Queries a `RAGTool`, retries with query variations |
|
|
400
|
+
| `StructuredAgent(response_format, model, tools)` | agent | Returns a Pydantic object in `structured_response` |
|
|
401
|
+
| `StateGraphAgent(nodes)` / `WorkflowGraph(nodes, edges)` | graph | Function nodes with routing and conditional edges |
|
|
402
|
+
| `RouterAgent(agents, llm)` | agent | Picks one agent per query; `route_only()` for `Ensemble` |
|
|
403
|
+
| `PlannerAgent(executor, max_subtasks)` | agent | Splits a task into subtasks and runs them |
|
|
404
|
+
| `Ensemble(agents, conductor, router)` | orchestration | `"supervisor"`, `"swarm"` (parallel) or `"pipeline"` |
|
|
405
|
+
| `DebateOrchestrator(solvers, aggregator, rounds)` | orchestration | Proposals, refinement rounds, aggregator decision |
|
|
406
|
+
| `Runner.run / arun(agent, input, config)` | runtime | Applies `RunConfig`: session, hooks, retries, interrupts |
|
|
407
|
+
| `function_tool(name, description)` | tools | Decorator turning a typed function into a tool |
|
|
408
|
+
| `SearchTool`, `RAGTool`, `ScraperTool`, `ValidationTool` | tools | Built-in tools |
|
|
409
|
+
| `InMemorySession`, `SQLiteSession` | memory | Conversation history stores |
|
|
410
|
+
| `TraceHooks`, `TraceEvent`, `estimate_cost` | tracing | Events, token usage, USD estimates from list prices |
|
|
411
|
+
| `get_default_provider()`, `OpenAILLMProvider`, `MistralLLMProvider` | llm | Provider selection and implementations |
|
|
412
|
+
| `AgentComparison`, `Benchmark`, `Metrics` | evaluation | Run agents on the same cases and compare |
|
|
413
|
+
|
|
414
|
+
## Research and references
|
|
415
|
+
|
|
416
|
+
- **ReAct** (`ReActAgent`): S. Yao, J. Zhao, D. Yu, N. Du, I. Shafran, K. Narasimhan, Y. Cao.
|
|
417
|
+
*ReAct: Synergizing Reasoning and Acting in Language Models.* ICLR 2023. AgentEnsemble runs
|
|
418
|
+
the reason / act / observe loop through native function calling instead of text parsing.
|
|
419
|
+
- **Multi-agent debate** (`DebateOrchestrator`): Y. Du, S. Li, A. Torralba, J. B. Tenenbaum,
|
|
420
|
+
I. Mordatch. *Improving Factuality and Reasoning in Language Models through Multiagent
|
|
421
|
+
Debate.* ICML 2024. Solvers see each other's answers for several rounds before an aggregator
|
|
422
|
+
decides.
|
|
423
|
+
- **Retrieval-augmented generation** (`RAGTool`, `RAGAgent`): P. Lewis et al.
|
|
424
|
+
*Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.* NeurIPS 2020. Here the
|
|
425
|
+
retriever is a LangChain vector store and the retrieved passages are handed to the agent's LLM.
|
|
426
|
+
|
|
427
|
+
## When to use it
|
|
428
|
+
|
|
429
|
+
AgentEnsemble fits when you want readable agent code with a small API, OpenAI or Mistral, and
|
|
430
|
+
built-in sessions, tracing and approvals, without adopting a large framework. If you need
|
|
431
|
+
durable, checkpointed graph execution across processes, a very large integration catalogue, or
|
|
432
|
+
a provider other than OpenAI-compatible endpoints and Mistral out of the box, a larger framework
|
|
433
|
+
such as LangGraph may suit you better; AgentEnsemble agents can still be called from it, since
|
|
434
|
+
they are plain Python objects.
|
|
435
|
+
|
|
436
|
+
## Roadmap
|
|
437
|
+
|
|
438
|
+
- LLM-based planning in `PlannerAgent` (today it is rule-based; `_decompose` can be overridden).
|
|
439
|
+
- Optional multi-round tool use per turn in `ReActAgent`.
|
|
440
|
+
- Token-level streaming in `astream()` (today it streams step events).
|
|
441
|
+
- Async-native `SQLiteSession` and a Redis session.
|
|
442
|
+
- Anthropic and other providers through the same `LLMProvider` interface.
|
|
443
|
+
|
|
444
|
+
## Contributing, security, citation, license
|
|
445
|
+
|
|
446
|
+
- Contributions are welcome: see [CONTRIBUTING.md](https://github.com/irfanalidv/AgentEnsemble/blob/main/CONTRIBUTING.md)
|
|
447
|
+
and the [Code of Conduct](https://github.com/irfanalidv/AgentEnsemble/blob/main/CODE_OF_CONDUCT.md).
|
|
448
|
+
CI runs `ruff check`, `ruff format --check`, `mypy agentensemble` and `pytest --cov` on
|
|
449
|
+
Python 3.10 to 3.13.
|
|
450
|
+
- Report vulnerabilities as described in [SECURITY.md](https://github.com/irfanalidv/AgentEnsemble/blob/main/SECURITY.md).
|
|
451
|
+
- If you use AgentEnsemble in research, please cite it with
|
|
452
|
+
[CITATION.cff](https://github.com/irfanalidv/AgentEnsemble/blob/main/CITATION.cff) (GitHub's
|
|
453
|
+
"Cite this repository" button reads it).
|
|
454
|
+
- Changes are listed in the [CHANGELOG](https://github.com/irfanalidv/AgentEnsemble/blob/main/CHANGELOG.md).
|
|
455
|
+
- MIT licensed: see [LICENSE](https://github.com/irfanalidv/AgentEnsemble/blob/main/LICENSE).
|