agentensemble 0.4.0__tar.gz → 0.5.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.4.0 → agentensemble-0.5.0}/CHANGELOG.md +23 -0
- {agentensemble-0.4.0/agentensemble.egg-info → agentensemble-0.5.0}/PKG-INFO +25 -10
- {agentensemble-0.4.0 → agentensemble-0.5.0}/README.md +22 -8
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/__init__.py +1 -1
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/agents/hybrid_agent.py +4 -12
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/agents/react_agent.py +6 -12
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/agents/structured_agent.py +2 -14
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/llm/__init__.py +14 -1
- agentensemble-0.5.0/agentensemble/llm/factory.py +63 -0
- agentensemble-0.4.0/agentensemble/llm/mistral_provider.py → agentensemble-0.5.0/agentensemble/llm/langchain_base.py +57 -77
- agentensemble-0.5.0/agentensemble/llm/mistral_provider.py +77 -0
- agentensemble-0.5.0/agentensemble/llm/openai_provider.py +79 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/router/router_agent.py +3 -11
- agentensemble-0.5.0/agentensemble/utils/__init__.py +17 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/utils/llm.py +51 -1
- {agentensemble-0.4.0 → agentensemble-0.5.0/agentensemble.egg-info}/PKG-INFO +25 -10
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble.egg-info/SOURCES.txt +4 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble.egg-info/requires.txt +1 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/pyproject.toml +3 -1
- agentensemble-0.5.0/tests/test_llm_providers.py +165 -0
- agentensemble-0.4.0/agentensemble/utils/__init__.py +0 -10
- {agentensemble-0.4.0 → agentensemble-0.5.0}/LICENSE +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/MANIFEST.in +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/agents/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/agents/base.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/agents/rag_agent.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/agents/stategraph_agent.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/core/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/core/protocol.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/graph/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/graph/workflow.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/llm/interface.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/memory/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/memory/session.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/memory/sqlite_session.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/orchestration/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/orchestration/debate.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/orchestration/ensemble.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/orchestration/pipeline.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/orchestration/supervisor.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/orchestration/swarm.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/planner/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/planner/planner_agent.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/py.typed +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/router/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/runner.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/testing/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/testing/benchmark.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/testing/comparison.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/testing/metrics.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/adapters.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/function_tool.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/protocol.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/rag.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/registry.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/scraper.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/search.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tools/validator.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tracing/__init__.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble/tracing/hooks.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble.egg-info/dependency_links.txt +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/agentensemble.egg-info/top_level.txt +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/setup.cfg +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_agents.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_comparison.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_memory.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_orchestration.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_react_session.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_release.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_runner.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_tools.py +0 -0
- {agentensemble-0.4.0 → agentensemble-0.5.0}/tests/test_tracing.py +0 -0
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **OpenAI support.** New `OpenAILLMProvider` (LangChain `ChatOpenAI`, native tool calling).
|
|
14
|
+
Default model `gpt-4.1-mini`, configurable via `OPENAI_MODEL`; `OPENAI_BASE_URL` for
|
|
15
|
+
OpenAI-compatible endpoints. Temperature is omitted for reasoning models (o-series, gpt-5).
|
|
16
|
+
- `get_default_provider()` / `resolve_provider_name()`: agents now pick OpenAI when
|
|
17
|
+
`OPENAI_API_KEY` is set, otherwise Mistral; `AGENTENSEMBLE_LLM_PROVIDER` forces one.
|
|
18
|
+
- `agentensemble.utils.get_chat_model()` and `get_openai_model()`; `StructuredAgent` uses the
|
|
19
|
+
same OpenAI-or-Mistral selection.
|
|
20
|
+
- `langchain-openai` is now a core dependency.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Calling a sync agent API twice (e.g. `agent.run()` then `agent.run()`, or `run()` followed by
|
|
24
|
+
`asyncio.run(agent.arun())`) failed with `RuntimeError: Event loop is closed`. Each sync call
|
|
25
|
+
uses `asyncio.run()`, and the LLM's async HTTP client stayed bound to the first, closed loop.
|
|
26
|
+
Providers now rebuild their chat model when used on a new event loop, and each OpenAI model
|
|
27
|
+
gets its own async HTTP client instead of the process-wide cached one.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- Shared LangChain conversion logic moved to `agentensemble.llm.langchain_base`
|
|
31
|
+
(`LangChainChatProvider`); `MistralLLMProvider` keeps its public API.
|
|
32
|
+
|
|
10
33
|
## [0.4.0] - 2026-10-31
|
|
11
34
|
|
|
12
35
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentensemble
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Build, orchestrate, trace, and test multi-agent LLM systems in Python: ReAct, RAG, router, planner, workflow graphs, swarms, debates, and pipelines.
|
|
5
5
|
Author-email: Irfan Ali <irfanali29@hotmail.com>
|
|
6
6
|
Maintainer-email: Irfan Ali <irfanali29@hotmail.com>
|
|
@@ -10,7 +10,7 @@ Project-URL: Documentation, https://github.com/irfanalidv/AgentEnsemble#readme
|
|
|
10
10
|
Project-URL: Repository, https://github.com/irfanalidv/AgentEnsemble
|
|
11
11
|
Project-URL: Issues, https://github.com/irfanalidv/AgentEnsemble/issues
|
|
12
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,rag,react-agent,tool-calling,function-calling,agent-testing,agentic-ai,state-graph,workflow,tracing
|
|
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
14
|
Classifier: Development Status :: 4 - Beta
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -31,6 +31,7 @@ Requires-Dist: langchain>=1.1.0
|
|
|
31
31
|
Requires-Dist: langchain-core>=1.0.0
|
|
32
32
|
Requires-Dist: langchain-community>=0.3.0
|
|
33
33
|
Requires-Dist: langchain-mistralai>=0.2.0
|
|
34
|
+
Requires-Dist: langchain-openai>=1.0.0
|
|
34
35
|
Requires-Dist: pydantic>=2.0.0
|
|
35
36
|
Requires-Dist: typing-extensions>=4.5.0
|
|
36
37
|
Requires-Dist: python-dotenv>=1.0.0
|
|
@@ -121,15 +122,29 @@ print(graph.run("What is multi-agent AI?")["result"])
|
|
|
121
122
|
# Summary for: What is multi-agent AI?
|
|
122
123
|
```
|
|
123
124
|
|
|
124
|
-
LLM-powered agents (`ReActAgent`, `HybridAgent`, `RouterAgent`, `StructuredAgent`)
|
|
125
|
-
|
|
125
|
+
LLM-powered agents (`ReActAgent`, `HybridAgent`, `RouterAgent`, `StructuredAgent`) work with
|
|
126
|
+
**OpenAI** or **Mistral**. The provider is picked from your environment: OpenAI if
|
|
127
|
+
`OPENAI_API_KEY` is set, otherwise Mistral if `MISTRAL_API_KEY` is set. You can also pass any
|
|
128
|
+
`LLMProvider` explicitly via `llm=...`:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from agentensemble import ReActAgent
|
|
132
|
+
from agentensemble.llm import OpenAILLMProvider, MistralLLMProvider
|
|
133
|
+
|
|
134
|
+
agent = ReActAgent(tools=[...], llm=OpenAILLMProvider(model="gpt-4.1-mini"))
|
|
135
|
+
agent = ReActAgent(tools=[...], llm=MistralLLMProvider(model="mistral-small-latest"))
|
|
136
|
+
```
|
|
126
137
|
|
|
127
138
|
## Configuration
|
|
128
139
|
|
|
129
140
|
| Variable | Purpose |
|
|
130
141
|
| --- | --- |
|
|
131
|
-
| `
|
|
142
|
+
| `OPENAI_API_KEY` | Enables the OpenAI provider (preferred when set). |
|
|
143
|
+
| `OPENAI_MODEL` | Optional default model for `OpenAILLMProvider` (default: `gpt-4.1-mini`). Reasoning models such as `gpt-5-mini` work too; temperature is left at the API default for them. |
|
|
144
|
+
| `OPENAI_BASE_URL` | Optional OpenAI-compatible endpoint (Azure OpenAI proxy, vLLM, LM Studio, ...). |
|
|
145
|
+
| `MISTRAL_API_KEY` | Enables the Mistral provider (used when no OpenAI key is set). |
|
|
132
146
|
| `MISTRAL_MODEL` | Optional default model for `MistralLLMProvider` (default: `mistral-large-latest`). Use e.g. `mistral-small-latest` if your plan doesn't include the large model. |
|
|
147
|
+
| `AGENTENSEMBLE_LLM_PROVIDER` | Optional. Force `openai` or `mistral` when both keys are set. |
|
|
133
148
|
| `SERPER_API_KEY` | Optional. When set, `SearchTool()` uses Serper; otherwise it falls back to DuckDuckGo (needs the `search` extra). |
|
|
134
149
|
|
|
135
150
|
Copy `.env.example` to `.env` and fill in your keys (`KEY=value`, no spaces around `=`); the
|
|
@@ -144,7 +159,7 @@ Without an API key, LLM agents can still be constructed (and inspected or regist
|
|
|
144
159
|
|
|
145
160
|
- **Modular** — Agents, tools, orchestration, and memory are decoupled
|
|
146
161
|
- **Async-first** — `run()` and `arun()` on all agents; parallel execution via `asyncio.gather`
|
|
147
|
-
- **Provider-agnostic** — LLM interface
|
|
162
|
+
- **Provider-agnostic** — one LLM interface, OpenAI and Mistral built in
|
|
148
163
|
- **Production patterns** — Runner, retries, hooks, tracing, session memory
|
|
149
164
|
|
|
150
165
|
---
|
|
@@ -196,7 +211,7 @@ Without an API key, LLM agents can still be constructed (and inspected or regist
|
|
|
196
211
|
|
|
197
212
|
```bash
|
|
198
213
|
pip install "agentensemble[search]"
|
|
199
|
-
export MISTRAL_API_KEY=...
|
|
214
|
+
export OPENAI_API_KEY=... # or: export MISTRAL_API_KEY=...
|
|
200
215
|
```
|
|
201
216
|
|
|
202
217
|
```python
|
|
@@ -213,7 +228,7 @@ print(result["result"])
|
|
|
213
228
|
|
|
214
229
|
## Examples — Code + Output
|
|
215
230
|
|
|
216
|
-
Each example is runnable. Requires `MISTRAL_API_KEY` (and the `search` extra for `SearchTool`; `SERPER_API_KEY` is optional). Outputs below are from the author's runs; LLM answers will vary.
|
|
231
|
+
Each example is runnable. Requires `OPENAI_API_KEY` or `MISTRAL_API_KEY` (and the `search` extra for `SearchTool`; `SERPER_API_KEY` is optional). Outputs below are from the author's runs; LLM answers will vary.
|
|
217
232
|
|
|
218
233
|
---
|
|
219
234
|
|
|
@@ -697,7 +712,7 @@ Runs all demos with real API calls.
|
|
|
697
712
|
agentensemble/
|
|
698
713
|
├── agents/ # ReAct, StateGraph, RAG, Hybrid, Structured
|
|
699
714
|
├── core/ # AgentProtocol, RunnableProtocol
|
|
700
|
-
├── llm/ # LLMProvider, MistralLLMProvider
|
|
715
|
+
├── llm/ # LLMProvider, OpenAILLMProvider, MistralLLMProvider, get_default_provider
|
|
701
716
|
├── memory/ # Session, InMemorySession, SQLiteSession
|
|
702
717
|
├── orchestration/ # Ensemble, Supervisor, Swarm, Pipeline, Debate
|
|
703
718
|
├── tools/ # Tool protocol, @function_tool, built-ins
|
|
@@ -715,7 +730,7 @@ agentensemble/
|
|
|
715
730
|
|
|
716
731
|
| Feature | AgentEnsemble | LangGraph | CrewAI | AutoGen |
|
|
717
732
|
| ------------------ | ------------------------- | ------------ | ------ | ------- |
|
|
718
|
-
| Multi-provider LLM | Mistral (extensible)
|
|
733
|
+
| Multi-provider LLM | OpenAI, Mistral (extensible) | Any | Any | Any |
|
|
719
734
|
| Async API | `arun()` everywhere | Yes | Yes | Yes |
|
|
720
735
|
| Router agents | RouterAgent | Manual | Yes | Manual |
|
|
721
736
|
| Planner pattern | PlannerAgent | Manual | Yes | Manual |
|
|
@@ -62,15 +62,29 @@ print(graph.run("What is multi-agent AI?")["result"])
|
|
|
62
62
|
# Summary for: What is multi-agent AI?
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
LLM-powered agents (`ReActAgent`, `HybridAgent`, `RouterAgent`, `StructuredAgent`)
|
|
66
|
-
|
|
65
|
+
LLM-powered agents (`ReActAgent`, `HybridAgent`, `RouterAgent`, `StructuredAgent`) work with
|
|
66
|
+
**OpenAI** or **Mistral**. The provider is picked from your environment: OpenAI if
|
|
67
|
+
`OPENAI_API_KEY` is set, otherwise Mistral if `MISTRAL_API_KEY` is set. You can also pass any
|
|
68
|
+
`LLMProvider` explicitly via `llm=...`:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from agentensemble import ReActAgent
|
|
72
|
+
from agentensemble.llm import OpenAILLMProvider, MistralLLMProvider
|
|
73
|
+
|
|
74
|
+
agent = ReActAgent(tools=[...], llm=OpenAILLMProvider(model="gpt-4.1-mini"))
|
|
75
|
+
agent = ReActAgent(tools=[...], llm=MistralLLMProvider(model="mistral-small-latest"))
|
|
76
|
+
```
|
|
67
77
|
|
|
68
78
|
## Configuration
|
|
69
79
|
|
|
70
80
|
| Variable | Purpose |
|
|
71
81
|
| --- | --- |
|
|
72
|
-
| `
|
|
82
|
+
| `OPENAI_API_KEY` | Enables the OpenAI provider (preferred when set). |
|
|
83
|
+
| `OPENAI_MODEL` | Optional default model for `OpenAILLMProvider` (default: `gpt-4.1-mini`). Reasoning models such as `gpt-5-mini` work too; temperature is left at the API default for them. |
|
|
84
|
+
| `OPENAI_BASE_URL` | Optional OpenAI-compatible endpoint (Azure OpenAI proxy, vLLM, LM Studio, ...). |
|
|
85
|
+
| `MISTRAL_API_KEY` | Enables the Mistral provider (used when no OpenAI key is set). |
|
|
73
86
|
| `MISTRAL_MODEL` | Optional default model for `MistralLLMProvider` (default: `mistral-large-latest`). Use e.g. `mistral-small-latest` if your plan doesn't include the large model. |
|
|
87
|
+
| `AGENTENSEMBLE_LLM_PROVIDER` | Optional. Force `openai` or `mistral` when both keys are set. |
|
|
74
88
|
| `SERPER_API_KEY` | Optional. When set, `SearchTool()` uses Serper; otherwise it falls back to DuckDuckGo (needs the `search` extra). |
|
|
75
89
|
|
|
76
90
|
Copy `.env.example` to `.env` and fill in your keys (`KEY=value`, no spaces around `=`); the
|
|
@@ -85,7 +99,7 @@ Without an API key, LLM agents can still be constructed (and inspected or regist
|
|
|
85
99
|
|
|
86
100
|
- **Modular** — Agents, tools, orchestration, and memory are decoupled
|
|
87
101
|
- **Async-first** — `run()` and `arun()` on all agents; parallel execution via `asyncio.gather`
|
|
88
|
-
- **Provider-agnostic** — LLM interface
|
|
102
|
+
- **Provider-agnostic** — one LLM interface, OpenAI and Mistral built in
|
|
89
103
|
- **Production patterns** — Runner, retries, hooks, tracing, session memory
|
|
90
104
|
|
|
91
105
|
---
|
|
@@ -137,7 +151,7 @@ Without an API key, LLM agents can still be constructed (and inspected or regist
|
|
|
137
151
|
|
|
138
152
|
```bash
|
|
139
153
|
pip install "agentensemble[search]"
|
|
140
|
-
export MISTRAL_API_KEY=...
|
|
154
|
+
export OPENAI_API_KEY=... # or: export MISTRAL_API_KEY=...
|
|
141
155
|
```
|
|
142
156
|
|
|
143
157
|
```python
|
|
@@ -154,7 +168,7 @@ print(result["result"])
|
|
|
154
168
|
|
|
155
169
|
## Examples — Code + Output
|
|
156
170
|
|
|
157
|
-
Each example is runnable. Requires `MISTRAL_API_KEY` (and the `search` extra for `SearchTool`; `SERPER_API_KEY` is optional). Outputs below are from the author's runs; LLM answers will vary.
|
|
171
|
+
Each example is runnable. Requires `OPENAI_API_KEY` or `MISTRAL_API_KEY` (and the `search` extra for `SearchTool`; `SERPER_API_KEY` is optional). Outputs below are from the author's runs; LLM answers will vary.
|
|
158
172
|
|
|
159
173
|
---
|
|
160
174
|
|
|
@@ -638,7 +652,7 @@ Runs all demos with real API calls.
|
|
|
638
652
|
agentensemble/
|
|
639
653
|
├── agents/ # ReAct, StateGraph, RAG, Hybrid, Structured
|
|
640
654
|
├── core/ # AgentProtocol, RunnableProtocol
|
|
641
|
-
├── llm/ # LLMProvider, MistralLLMProvider
|
|
655
|
+
├── llm/ # LLMProvider, OpenAILLMProvider, MistralLLMProvider, get_default_provider
|
|
642
656
|
├── memory/ # Session, InMemorySession, SQLiteSession
|
|
643
657
|
├── orchestration/ # Ensemble, Supervisor, Swarm, Pipeline, Debate
|
|
644
658
|
├── tools/ # Tool protocol, @function_tool, built-ins
|
|
@@ -656,7 +670,7 @@ agentensemble/
|
|
|
656
670
|
|
|
657
671
|
| Feature | AgentEnsemble | LangGraph | CrewAI | AutoGen |
|
|
658
672
|
| ------------------ | ------------------------- | ------------ | ------ | ------- |
|
|
659
|
-
| Multi-provider LLM | Mistral (extensible)
|
|
673
|
+
| Multi-provider LLM | OpenAI, Mistral (extensible) | Any | Any | Any |
|
|
660
674
|
| Async API | `arun()` everywhere | Yes | Yes | Yes |
|
|
661
675
|
| Router agents | RouterAgent | Manual | Yes | Manual |
|
|
662
676
|
| Planner pattern | PlannerAgent | Manual | Yes | Manual |
|
|
@@ -9,7 +9,7 @@ from importlib.metadata import PackageNotFoundError, version
|
|
|
9
9
|
try:
|
|
10
10
|
__version__ = version("agentensemble")
|
|
11
11
|
except PackageNotFoundError: # running from a source checkout without installation
|
|
12
|
-
__version__ = "0.
|
|
12
|
+
__version__ = "0.5.0"
|
|
13
13
|
__author__ = "Irfan Ali"
|
|
14
14
|
|
|
15
15
|
# Core imports
|
|
@@ -9,17 +9,9 @@ import asyncio
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from agentensemble.agents.base import AgentState, BaseAgent
|
|
12
|
+
from agentensemble.llm.factory import get_default_provider
|
|
12
13
|
from agentensemble.llm.interface import LLMMessage, LLMProvider, ToolSchema
|
|
13
14
|
|
|
14
|
-
try:
|
|
15
|
-
from agentensemble.llm.mistral_provider import MistralLLMProvider
|
|
16
|
-
|
|
17
|
-
LLM_AVAILABLE = True
|
|
18
|
-
except ImportError:
|
|
19
|
-
LLM_AVAILABLE = False
|
|
20
|
-
MistralLLMProvider = None
|
|
21
|
-
|
|
22
|
-
|
|
23
15
|
# Routing tool: LLM chooses next action via function calling
|
|
24
16
|
ROUTE_ACTION_SCHEMA = ToolSchema(
|
|
25
17
|
name="choose_next_action",
|
|
@@ -66,16 +58,16 @@ class HybridAgent(BaseAgent):
|
|
|
66
58
|
tools: Available tools (search, rag, validator)
|
|
67
59
|
max_iterations: Maximum iterations
|
|
68
60
|
early_stopping: Enable early stopping when answer found
|
|
69
|
-
llm: LLM provider for routing (default:
|
|
61
|
+
llm: LLM provider for routing (default: OpenAI or Mistral, picked from the environment).
|
|
70
62
|
If None, uses fixed pipeline: SEARCH -> RAG -> VALIDATE -> ANSWER
|
|
71
63
|
**kwargs: Additional configuration
|
|
72
64
|
"""
|
|
73
65
|
super().__init__(name, tools, max_iterations, **kwargs)
|
|
74
66
|
self.early_stopping = early_stopping
|
|
75
67
|
self.llm = llm
|
|
76
|
-
if self.llm is None
|
|
68
|
+
if self.llm is None:
|
|
77
69
|
try:
|
|
78
|
-
self.llm =
|
|
70
|
+
self.llm = get_default_provider(temperature=0.2)
|
|
79
71
|
except (ValueError, ImportError):
|
|
80
72
|
self.llm = None # Fall back to fixed pipeline if no API key
|
|
81
73
|
|
|
@@ -28,13 +28,7 @@ except ImportError:
|
|
|
28
28
|
TraceEvent = None
|
|
29
29
|
estimate_cost = None
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
from agentensemble.llm.mistral_provider import MistralLLMProvider
|
|
33
|
-
|
|
34
|
-
LLM_AVAILABLE = True
|
|
35
|
-
except ImportError:
|
|
36
|
-
LLM_AVAILABLE = False
|
|
37
|
-
MistralLLMProvider = None
|
|
31
|
+
from agentensemble.llm.factory import get_default_provider
|
|
38
32
|
|
|
39
33
|
|
|
40
34
|
class ReActAgent(BaseAgent):
|
|
@@ -61,16 +55,16 @@ class ReActAgent(BaseAgent):
|
|
|
61
55
|
name: Agent name
|
|
62
56
|
tools: Available tools (SearchTool, RAGTool, FunctionTool, etc.)
|
|
63
57
|
max_iterations: Maximum reasoning/acting cycles
|
|
64
|
-
llm: LLM provider (default:
|
|
58
|
+
llm: LLM provider (default: OpenAI or Mistral, picked from the environment)
|
|
65
59
|
session: Optional Session for multi-turn conversation history
|
|
66
60
|
**kwargs: Additional configuration
|
|
67
61
|
"""
|
|
68
62
|
super().__init__(name, tools, max_iterations, **kwargs)
|
|
69
63
|
self.llm = llm
|
|
70
64
|
self.session = session
|
|
71
|
-
if self.llm is None
|
|
65
|
+
if self.llm is None:
|
|
72
66
|
try:
|
|
73
|
-
self.llm =
|
|
67
|
+
self.llm = get_default_provider(temperature=0.2)
|
|
74
68
|
except (ValueError, ImportError):
|
|
75
69
|
# No API key yet: the agent can still be constructed and inspected;
|
|
76
70
|
# run()/stream() raise a clear RuntimeError until an LLM is available.
|
|
@@ -117,7 +111,7 @@ class ReActAgent(BaseAgent):
|
|
|
117
111
|
"""Stream events during ReAct loop."""
|
|
118
112
|
if self.llm is None:
|
|
119
113
|
raise RuntimeError(
|
|
120
|
-
"LLM required for ReActAgent.
|
|
114
|
+
"LLM required for ReActAgent. Set OPENAI_API_KEY or MISTRAL_API_KEY, or pass llm=."
|
|
121
115
|
)
|
|
122
116
|
|
|
123
117
|
state = AgentState(query=query, context=kwargs.get("context", {}))
|
|
@@ -277,7 +271,7 @@ class ReActAgent(BaseAgent):
|
|
|
277
271
|
"""Async ReAct loop: Reason -> Act -> Observe until final answer."""
|
|
278
272
|
if self.llm is None:
|
|
279
273
|
raise RuntimeError(
|
|
280
|
-
"LLM required for ReActAgent.
|
|
274
|
+
"LLM required for ReActAgent. Set OPENAI_API_KEY or MISTRAL_API_KEY, or pass llm=, "
|
|
281
275
|
"or pass llm=YourLLMProvider()."
|
|
282
276
|
)
|
|
283
277
|
|
|
@@ -14,13 +14,7 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
from pydantic import BaseModel
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
from agentensemble.utils.llm import get_mistral_model
|
|
19
|
-
|
|
20
|
-
LLM_AVAILABLE = True
|
|
21
|
-
except ImportError:
|
|
22
|
-
LLM_AVAILABLE = False
|
|
23
|
-
get_mistral_model = None
|
|
17
|
+
from agentensemble.utils.llm import get_chat_model
|
|
24
18
|
|
|
25
19
|
# Try langchain>=1.1 approach (create_agent with response_format)
|
|
26
20
|
try:
|
|
@@ -72,15 +66,9 @@ class StructuredAgent:
|
|
|
72
66
|
system_prompt: System prompt for the agent
|
|
73
67
|
**kwargs: Additional configuration
|
|
74
68
|
"""
|
|
75
|
-
if not LLM_AVAILABLE:
|
|
76
|
-
raise ImportError(
|
|
77
|
-
"langchain-mistralai is required for StructuredAgent. "
|
|
78
|
-
"Install with: pip install langchain-mistralai"
|
|
79
|
-
)
|
|
80
|
-
|
|
81
69
|
self.name = name
|
|
82
70
|
self.tools = tools or []
|
|
83
|
-
self.model = model or
|
|
71
|
+
self.model = model or get_chat_model(temperature=0.2)
|
|
84
72
|
self.response_format = response_format
|
|
85
73
|
self.system_prompt = system_prompt
|
|
86
74
|
self.config = kwargs
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
LLM Provider Interface
|
|
3
3
|
|
|
4
4
|
Provider-agnostic LLM abstraction for agent reasoning and tool calling.
|
|
5
|
-
|
|
5
|
+
Ships OpenAI and Mistral providers; any LangChain chat model can be wrapped the same way.
|
|
6
6
|
|
|
7
7
|
Architecture: Clean separation between provider-specific implementations
|
|
8
8
|
and agent logic. Enables multi-provider support without vendor lock-in.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from agentensemble.llm.factory import get_default_provider, resolve_provider_name
|
|
11
12
|
from agentensemble.llm.interface import (
|
|
12
13
|
LLMMessage,
|
|
13
14
|
LLMProvider,
|
|
@@ -24,6 +25,14 @@ except ImportError:
|
|
|
24
25
|
MISTRAL_AVAILABLE = False
|
|
25
26
|
MistralLLMProvider = None
|
|
26
27
|
|
|
28
|
+
try:
|
|
29
|
+
from agentensemble.llm.openai_provider import OpenAILLMProvider
|
|
30
|
+
|
|
31
|
+
OPENAI_AVAILABLE = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
OPENAI_AVAILABLE = False
|
|
34
|
+
OpenAILLMProvider = None
|
|
35
|
+
|
|
27
36
|
__all__ = [
|
|
28
37
|
"LLMProvider",
|
|
29
38
|
"LLMMessage",
|
|
@@ -32,4 +41,8 @@ __all__ = [
|
|
|
32
41
|
"ToolSchema",
|
|
33
42
|
"MistralLLMProvider",
|
|
34
43
|
"MISTRAL_AVAILABLE",
|
|
44
|
+
"OpenAILLMProvider",
|
|
45
|
+
"OPENAI_AVAILABLE",
|
|
46
|
+
"get_default_provider",
|
|
47
|
+
"resolve_provider_name",
|
|
35
48
|
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Default LLM provider selection.
|
|
3
|
+
|
|
4
|
+
Order of precedence:
|
|
5
|
+
1. ``AGENTENSEMBLE_LLM_PROVIDER`` env var ("openai" or "mistral"), if set.
|
|
6
|
+
2. OpenAI, if ``OPENAI_API_KEY`` is set.
|
|
7
|
+
3. Mistral, if ``MISTRAL_API_KEY`` is set.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from agentensemble.llm.interface import LLMProvider
|
|
14
|
+
|
|
15
|
+
PROVIDERS = ("openai", "mistral")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _build(name: str, temperature: float, **kwargs: Any) -> LLMProvider:
|
|
19
|
+
if name == "openai":
|
|
20
|
+
from agentensemble.llm.openai_provider import OpenAILLMProvider
|
|
21
|
+
|
|
22
|
+
return OpenAILLMProvider(temperature=temperature, **kwargs)
|
|
23
|
+
if name == "mistral":
|
|
24
|
+
from agentensemble.llm.mistral_provider import MistralLLMProvider
|
|
25
|
+
|
|
26
|
+
return MistralLLMProvider(temperature=temperature, **kwargs)
|
|
27
|
+
raise ValueError(f"Unknown LLM provider: {name!r}. Supported: {', '.join(PROVIDERS)}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_provider_name() -> str | None:
|
|
31
|
+
"""Return the provider that ``get_default_provider`` would use, or None if no key is set."""
|
|
32
|
+
explicit = os.getenv("AGENTENSEMBLE_LLM_PROVIDER", "").strip().lower()
|
|
33
|
+
if explicit:
|
|
34
|
+
return explicit
|
|
35
|
+
if os.getenv("OPENAI_API_KEY"):
|
|
36
|
+
return "openai"
|
|
37
|
+
if os.getenv("MISTRAL_API_KEY"):
|
|
38
|
+
return "mistral"
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_default_provider(
|
|
43
|
+
temperature: float = 0.2, provider: str | None = None, **kwargs: Any
|
|
44
|
+
) -> LLMProvider:
|
|
45
|
+
"""
|
|
46
|
+
Build the default LLM provider from the environment.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
temperature: Sampling temperature passed to the provider.
|
|
50
|
+
provider: Force a provider ("openai" or "mistral") instead of auto-detecting.
|
|
51
|
+
**kwargs: Passed to the provider constructor (e.g. ``model=``).
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If no provider can be determined or its API key is missing.
|
|
55
|
+
ImportError: If the provider's LangChain integration is not installed.
|
|
56
|
+
"""
|
|
57
|
+
name = (provider or resolve_provider_name() or "").lower()
|
|
58
|
+
if not name:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"No LLM provider configured. Set OPENAI_API_KEY or MISTRAL_API_KEY "
|
|
61
|
+
"(or AGENTENSEMBLE_LLM_PROVIDER), or pass llm= explicitly."
|
|
62
|
+
)
|
|
63
|
+
return _build(name, temperature, **kwargs)
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Shared LangChain plumbing for chat-model providers.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Converts between AgentEnsemble's provider-agnostic message/tool models and
|
|
5
|
+
LangChain's chat-model API. Concrete providers (Mistral, OpenAI) only need to
|
|
6
|
+
build the underlying LangChain chat model.
|
|
6
7
|
"""
|
|
7
8
|
|
|
9
|
+
import asyncio
|
|
8
10
|
import json
|
|
9
11
|
from typing import Any
|
|
10
12
|
|
|
13
|
+
from langchain_core.messages import (
|
|
14
|
+
AIMessage,
|
|
15
|
+
BaseMessage,
|
|
16
|
+
HumanMessage,
|
|
17
|
+
SystemMessage,
|
|
18
|
+
ToolMessage,
|
|
19
|
+
)
|
|
20
|
+
from langchain_core.messages import (
|
|
21
|
+
ToolCall as LCToolCall,
|
|
22
|
+
)
|
|
23
|
+
from langchain_core.runnables import Runnable
|
|
24
|
+
|
|
11
25
|
from agentensemble.llm.interface import (
|
|
12
26
|
LLMMessage,
|
|
13
27
|
LLMProvider,
|
|
@@ -16,30 +30,9 @@ from agentensemble.llm.interface import (
|
|
|
16
30
|
ToolSchema,
|
|
17
31
|
)
|
|
18
32
|
|
|
19
|
-
try:
|
|
20
|
-
from langchain_core.messages import (
|
|
21
|
-
AIMessage,
|
|
22
|
-
BaseMessage,
|
|
23
|
-
HumanMessage,
|
|
24
|
-
SystemMessage,
|
|
25
|
-
ToolMessage,
|
|
26
|
-
)
|
|
27
|
-
from langchain_core.messages import (
|
|
28
|
-
ToolCall as LCToolCall,
|
|
29
|
-
)
|
|
30
|
-
from langchain_core.runnables import Runnable
|
|
31
|
-
from langchain_mistralai import ChatMistralAI
|
|
32
|
-
|
|
33
|
-
LANGCHAIN_AVAILABLE = True
|
|
34
|
-
except ImportError:
|
|
35
|
-
LANGCHAIN_AVAILABLE = False
|
|
36
|
-
ChatMistralAI = None
|
|
37
|
-
LCToolCall = None
|
|
38
|
-
Runnable = None
|
|
39
|
-
|
|
40
33
|
|
|
41
34
|
def _tool_schema_to_langchain(schema: ToolSchema) -> dict[str, Any]:
|
|
42
|
-
"""Convert ToolSchema to OpenAI-style format
|
|
35
|
+
"""Convert ToolSchema to OpenAI-style function format (also accepted by Mistral)."""
|
|
43
36
|
return {
|
|
44
37
|
"type": "function",
|
|
45
38
|
"function": {
|
|
@@ -52,7 +45,7 @@ def _tool_schema_to_langchain(schema: ToolSchema) -> dict[str, Any]:
|
|
|
52
45
|
}
|
|
53
46
|
|
|
54
47
|
|
|
55
|
-
def _messages_to_langchain(messages: list[LLMMessage]) -> list[
|
|
48
|
+
def _messages_to_langchain(messages: list[LLMMessage]) -> list[BaseMessage]:
|
|
56
49
|
"""Convert LLMMessage list to LangChain format."""
|
|
57
50
|
result: list[BaseMessage] = []
|
|
58
51
|
for msg in messages:
|
|
@@ -86,7 +79,7 @@ def _messages_to_langchain(messages: list[LLMMessage]) -> list["BaseMessage"]:
|
|
|
86
79
|
return result
|
|
87
80
|
|
|
88
81
|
|
|
89
|
-
def _extract_usage(msg:
|
|
82
|
+
def _extract_usage(msg: AIMessage) -> dict[str, int] | None:
|
|
90
83
|
"""Extract token usage from LangChain AIMessage (usage_metadata or response_metadata)."""
|
|
91
84
|
um = getattr(msg, "usage_metadata", None)
|
|
92
85
|
if um and isinstance(um, dict):
|
|
@@ -96,7 +89,7 @@ def _extract_usage(msg: "AIMessage") -> dict[str, int] | None:
|
|
|
96
89
|
"total_tokens": um.get("total_tokens", 0),
|
|
97
90
|
}
|
|
98
91
|
rm = getattr(msg, "response_metadata", None) or {}
|
|
99
|
-
usage = rm.get("usage") or rm.get("usage_metadata") or {}
|
|
92
|
+
usage = rm.get("usage") or rm.get("usage_metadata") or rm.get("token_usage") or {}
|
|
100
93
|
if usage:
|
|
101
94
|
return {
|
|
102
95
|
"input_tokens": usage.get("input_tokens", usage.get("prompt_tokens", 0)),
|
|
@@ -129,7 +122,7 @@ def _parse_ai_message(msg: AIMessage) -> LLMResponse:
|
|
|
129
122
|
args = {}
|
|
130
123
|
tool_calls.append(
|
|
131
124
|
ToolCall(
|
|
132
|
-
id=tc_id,
|
|
125
|
+
id=tc_id or "",
|
|
133
126
|
name=tc_name,
|
|
134
127
|
arguments=args if isinstance(args, dict) else {},
|
|
135
128
|
)
|
|
@@ -143,71 +136,57 @@ def _parse_ai_message(msg: AIMessage) -> LLMResponse:
|
|
|
143
136
|
)
|
|
144
137
|
|
|
145
138
|
|
|
146
|
-
class
|
|
139
|
+
class LangChainChatProvider(LLMProvider):
|
|
147
140
|
"""
|
|
148
|
-
|
|
141
|
+
Base provider wrapping any LangChain chat model that supports ``bind_tools``.
|
|
149
142
|
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
Subclasses implement ``_build_model()``, call ``_init_model()`` at the end of
|
|
144
|
+
``__init__``, set ``self.model_name``, and may override ``REQUIRED_TOOL_CHOICE``
|
|
145
|
+
with the provider's spelling of "must call a tool".
|
|
146
|
+
|
|
147
|
+
Async HTTP clients are bound to the event loop they were first used on. The sync
|
|
148
|
+
agent APIs call ``asyncio.run()`` (a fresh loop per call), so the model is rebuilt
|
|
149
|
+
whenever ``agenerate`` runs on a different loop than last time.
|
|
152
150
|
"""
|
|
153
151
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
model: str | None = None,
|
|
157
|
-
temperature: float = 0.2,
|
|
158
|
-
api_key: str | None = None,
|
|
159
|
-
**kwargs: Any,
|
|
160
|
-
):
|
|
161
|
-
"""
|
|
162
|
-
Initialize Mistral provider.
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
model: Model name (e.g., mistral-large-latest, mistral-small-latest). Defaults to
|
|
166
|
-
the ``MISTRAL_MODEL`` env var, else ``mistral-large-latest``.
|
|
167
|
-
temperature: Lower = more deterministic (0.2 recommended for tool use)
|
|
168
|
-
api_key: Mistral API key (defaults to MISTRAL_API_KEY env)
|
|
169
|
-
**kwargs: Passed to ChatMistralAI
|
|
170
|
-
"""
|
|
171
|
-
if not LANGCHAIN_AVAILABLE:
|
|
172
|
-
raise ImportError(
|
|
173
|
-
"langchain-mistralai and langchain-core required. "
|
|
174
|
-
"Install: pip install langchain-mistralai langchain-core"
|
|
175
|
-
)
|
|
152
|
+
#: Value sent to the API when callers ask for ``tool_choice="required"``.
|
|
153
|
+
REQUIRED_TOOL_CHOICE: str = "required"
|
|
176
154
|
|
|
177
|
-
|
|
155
|
+
_model: Any
|
|
156
|
+
_model_loop: asyncio.AbstractEventLoop | None = None
|
|
157
|
+
model_name: str
|
|
178
158
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
raise ValueError("MISTRAL_API_KEY required. Set in .env or pass api_key.")
|
|
159
|
+
def _build_model(self) -> Any:
|
|
160
|
+
"""Create the underlying LangChain chat model."""
|
|
161
|
+
raise NotImplementedError
|
|
183
162
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
self.
|
|
163
|
+
def _init_model(self) -> None:
|
|
164
|
+
self._model = self._build_model()
|
|
165
|
+
self._model_loop = None
|
|
166
|
+
|
|
167
|
+
def _ensure_model_for_running_loop(self) -> None:
|
|
168
|
+
loop = asyncio.get_running_loop()
|
|
169
|
+
if self._model_loop is loop:
|
|
170
|
+
return
|
|
171
|
+
if self._model_loop is not None:
|
|
172
|
+
self._model = self._build_model()
|
|
173
|
+
self._model_loop = loop
|
|
191
174
|
|
|
192
175
|
def _bind_tools(
|
|
193
176
|
self,
|
|
194
177
|
tools: list[ToolSchema] | None = None,
|
|
195
178
|
tool_choice: str | None = None,
|
|
196
|
-
) ->
|
|
179
|
+
) -> Runnable:
|
|
197
180
|
"""Bind tools to model for function calling."""
|
|
198
181
|
if not tools:
|
|
199
182
|
return self._model
|
|
200
183
|
|
|
201
184
|
tool_defs = [_tool_schema_to_langchain(t) for t in tools]
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if tool_choice
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
elif tool_choice and tool_choice != "auto":
|
|
208
|
-
bound = bound.bind(tool_choice=tool_choice)
|
|
209
|
-
|
|
210
|
-
return bound
|
|
185
|
+
if tool_choice == "required":
|
|
186
|
+
return self._model.bind_tools(tool_defs, tool_choice=self.REQUIRED_TOOL_CHOICE)
|
|
187
|
+
if tool_choice and tool_choice != "auto":
|
|
188
|
+
return self._model.bind_tools(tool_defs, tool_choice=tool_choice)
|
|
189
|
+
return self._model.bind_tools(tool_defs)
|
|
211
190
|
|
|
212
191
|
def generate(
|
|
213
192
|
self,
|
|
@@ -230,6 +209,7 @@ class MistralLLMProvider(LLMProvider):
|
|
|
230
209
|
**kwargs: Any,
|
|
231
210
|
) -> LLMResponse:
|
|
232
211
|
"""Async generation with optional tool calling."""
|
|
212
|
+
self._ensure_model_for_running_loop()
|
|
233
213
|
lc_messages = _messages_to_langchain(messages)
|
|
234
214
|
bound = self._bind_tools(tools, tool_choice)
|
|
235
215
|
response = await bound.ainvoke(lc_messages, **kwargs)
|