Skip to content

Commit 53dd5ac

Browse files
Feat: Mistral Async Client
1 parent d7999d6 commit 53dd5ac

File tree

10 files changed

+270
-125
lines changed

10 files changed

+270
-125
lines changed

Dockerfile

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ RUN apt update && \
1212
git \
1313
&& rm -rf /var/lib/apt/lists/*
1414

15-
WORKDIR /app
16-
COPY README.md /app/
17-
COPY pyproject.toml /app/
18-
RUN uv sync --no-install-project --no-dev
19-
COPY src/opensymbiose /app/opensymbiose
15+
WORKDIR /src
16+
COPY README.md /src/
17+
COPY pyproject.toml /src/
18+
RUN uv sync --no-dev
19+
COPY src/opensymbiose /src/opensymbiose
2020

21-
EXPOSE 8000
21+
EXPOSE 7860
2222

23-
CMD ["uv", "run", "opensymbiose/main.py"]
23+
ENV PYTHONPATH=/src
24+
CMD ["uv", "run", "gradio", "/src/opensymbiose/gradio/app.py"]

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ dockerbuild:
5353
docker build -t opensymbiose:latest .
5454

5555
dockerrun:
56-
docker run --rm opensymbiose:latest
56+
docker run --rm -p 7860:7860 --env-file .env opensymbiose:latest
5757

5858
allci:
5959
$(MAKE) check
6060
$(MAKE) format
6161
$(MAKE) type
62-
$(MAKE) cov
62+
$(MAKE) cov

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@ title: opensymbiose
33
app_file: src/opensymbiose/gradio/app.py
44
sdk: gradio
55
sdk_version: 5.31.0
6+
python_version: 3.13
67
---
78
# OpenSymbiose: Open Source Biotechnology AI Agent
89

9-
OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researcher.
10+
OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researchers.
1011

1112
[DOCUMENTATION](https://lambda-science.github.io/OpenSymbiose/) - [DEMO ON HUGGINGFACE](https://huggingface.co/spaces/corentinm7/opensymbiose) - [CODE REPO](https://github.com/lambda-science/OpenSymbiose)
1213

1314
Creator and Maintainer: [**Corentin Meyer**, PhD](https://cmeyer.fr/) - <[email protected]>
1415

1516
## Installation
1617

17-
From PyPi: `pip install opensymbiose`
18+
If you want just to interact with it, an online demo is available
19+
on [HuggingFace](https://huggingface.co/spaces/corentinm7/opensymbiose)
20+
21+
**Note:** In every-case you will need an environment variable or a .env file with your `MISTRAL_API_KEY` secret.
22+
23+
- Install from PyPi: `pip install opensymbiose` and run in your terminal `opensymbiose` it will boot the gradio app.
24+
- From GitHub: clone the repository and run `make dev`. It will launch the Gradio app with hot-reloading.
1825

1926
## Usage
2027

src/opensymbiose/agents/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
from opensymbiose.agents.agent_manager import AgentManager
99
from opensymbiose.agents.create_agents import setup_agents, get_agents
1010

11-
__all__ = ['Agent', 'AgentManager', 'setup_agents', 'get_agents']
11+
__all__ = ["Agent", "AgentManager", "setup_agents", "get_agents"]

src/opensymbiose/agents/agent.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Agent class to represent individual Mistral AI agents.
33
"""
4+
45
from typing import Any
56

67
from mistralai import Mistral
@@ -14,7 +15,7 @@ class Agent:
1415
def __init__(self, agent_data: Any):
1516
"""
1617
Initialize an Agent object from Mistral API agent data.
17-
18+
1819
Args:
1920
agent_data: The agent data returned from Mistral API
2021
"""
@@ -23,57 +24,55 @@ def __init__(self, agent_data: Any):
2324
self.description = agent_data.description
2425
self.model = agent_data.model
2526
self.tools = agent_data.tools
26-
self.handoffs = agent_data.handoffs if hasattr(agent_data, 'handoffs') else []
27+
self.handoffs = agent_data.handoffs if hasattr(agent_data, "handoffs") else []
2728
self._raw_data = agent_data
2829

2930
@property
3031
def raw_data(self) -> Any:
3132
"""
3233
Get the raw agent data from Mistral API.
33-
34+
3435
Returns:
3536
The raw agent data
3637
"""
3738
return self._raw_data
3839

39-
def add_handoff(self, agent_id: str, client: Mistral) -> None:
40+
async def add_handoff(self, agent_id: str, client: Mistral) -> None:
4041
"""
4142
Add a handoff to another agent.
42-
43+
4344
Args:
4445
agent_id: The ID of the agent to handoff to
4546
client: The Mistral client instance
4647
"""
4748
if agent_id not in self.handoffs:
4849
self.handoffs.append(agent_id)
49-
updated_agent = client.beta.agents.update(
50-
agent_id=self.id,
51-
handoffs=self.handoffs
50+
updated_agent = client.beta.agents.update_async(
51+
agent_id=self.id, handoffs=self.handoffs
5252
)
5353
# Update the raw data with the updated agent
5454
self._raw_data = updated_agent
5555

56-
def remove_handoff(self, agent_id: str, client: Mistral) -> None:
56+
async def remove_handoff(self, agent_id: str, client: Mistral) -> None:
5757
"""
5858
Remove a handoff to another agent.
59-
59+
6060
Args:
6161
agent_id: The ID of the agent to remove handoff from
6262
client: The Mistral client instance
6363
"""
6464
if agent_id in self.handoffs:
6565
self.handoffs.remove(agent_id)
66-
updated_agent = client.beta.agents.update(
67-
agent_id=self.id,
68-
handoffs=self.handoffs
66+
updated_agent = client.beta.agents.update_async(
67+
agent_id=self.id, handoffs=self.handoffs
6968
)
7069
# Update the raw data with the updated agent
7170
self._raw_data = updated_agent
7271

7372
def __str__(self) -> str:
7473
"""
7574
String representation of the agent.
76-
75+
7776
Returns:
7877
A string representation of the agent
7978
"""
@@ -82,10 +81,12 @@ def __str__(self) -> str:
8281
def __repr__(self) -> str:
8382
"""
8483
Detailed representation of the agent.
85-
84+
8685
Returns:
8786
A detailed representation of the agent
8887
"""
89-
return (f"Agent(id={self.id}, name={self.name}, "
90-
f"description={self.description}, model={self.model}, "
91-
f"tools={self.tools}, handoffs={self.handoffs})")
88+
return (
89+
f"Agent(id={self.id}, name={self.name}, "
90+
f"description={self.description}, model={self.model}, "
91+
f"tools={self.tools}, handoffs={self.handoffs})"
92+
)
Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
AgentManager class to manage Mistral AI agents.
33
"""
4+
45
import os
56
from typing import Dict, List, Optional
67

@@ -20,10 +21,10 @@ class AgentManager:
2021
def __new__(cls, api_key: Optional[str] = None):
2122
"""
2223
Create a new instance of AgentManager or return the existing one (Singleton pattern).
23-
24+
2425
Args:
2526
api_key: The Mistral API key. If not provided, it will be read from the environment.
26-
27+
2728
Returns:
2829
The AgentManager instance
2930
"""
@@ -35,7 +36,7 @@ def __new__(cls, api_key: Optional[str] = None):
3536
def __init__(self, api_key: Optional[str] = None):
3637
"""
3738
Initialize the AgentManager with the Mistral API key.
38-
39+
3940
Args:
4041
api_key: The Mistral API key. If not provided, it will be read from the environment.
4142
"""
@@ -46,81 +47,75 @@ def __init__(self, api_key: Optional[str] = None):
4647
self.api_key = api_key or os.environ.get("MISTRAL_API_KEY")
4748
if not self.api_key:
4849
raise ValueError(
49-
"Mistral API key is required. Provide it as an argument or set the MISTRAL_API_KEY environment variable.")
50+
"Mistral API key is required. Provide it as an argument or set the MISTRAL_API_KEY environment variable."
51+
)
5052

5153
self.client = Mistral(self.api_key)
5254
self.agents: Dict[str, Agent] = {}
5355
self._initialized = True
5456

55-
def list_agents(self) -> List[Agent]:
57+
async def list_agents(self) -> List[Agent]:
5658
"""
5759
List all agents from the Mistral API.
58-
60+
5961
Returns:
6062
A list of Agent objects
6163
"""
62-
agent_list = self.client.beta.agents.list()
64+
agent_list = await self.client.beta.agents.list_async()
6365
return [Agent(agent) for agent in agent_list]
6466

65-
def refresh_agents(self) -> None:
67+
async def refresh_agents(self) -> None:
6668
"""
6769
Refresh the local cache of agents from the Mistral API.
6870
"""
6971
self.agents = {}
70-
agent_list = self.client.beta.agents.list()
72+
agent_list = await self.client.beta.agents.list_async()
7173
for agent_data in agent_list:
7274
agent = Agent(agent_data)
7375
self.agents[agent.name] = agent
7476

75-
def get_agent(self, agent_name: str) -> Optional[Agent]:
77+
async def get_agent(self, agent_name: str) -> Optional[Agent]:
7678
"""
7779
Get an agent by name.
78-
80+
7981
Args:
8082
agent_name: The name of the agent
81-
83+
8284
Returns:
8385
The Agent object if found, None otherwise
8486
"""
8587
# Refresh agents if not already loaded
8688
if not self.agents:
87-
self.refresh_agents()
89+
await self.refresh_agents()
8890

8991
return self.agents.get(agent_name)
9092

91-
def get_or_create_agent(
92-
self,
93-
agent_name: str,
94-
model: str,
95-
description: str,
96-
tools: List[Dict[str, str]]
93+
async def get_or_create_agent(
94+
self, agent_name: str, model: str, description: str, tools: List[Dict[str, str]]
9795
) -> Agent:
9896
"""
9997
Get an agent by name or create it if it doesn't exist.
100-
98+
10199
Args:
102100
agent_name: The name of the agent
103101
model: The model to use for the agent
104102
description: The description of the agent
105103
tools: The tools to enable for the agent
106-
104+
107105
Returns:
108106
The Agent object
109107
"""
110108
# Refresh agents if not already loaded
111109
if not self.agents:
112-
self.refresh_agents()
110+
await self.refresh_agents()
113111

114112
# Check if agent exists
115113
agent = self.agents.get(agent_name)
116114

117115
# Create agent if it doesn't exist
118116
if not agent:
119-
agent_data = self.client.beta.agents.create(
120-
model=model,
121-
description=description,
122-
name=agent_name,
123-
tools=tools
117+
agent_data = await self.client.beta.agents.create_async(
118+
model=model, description=description, name=agent_name, tools=tools
124119
)
125120
agent = Agent(agent_data)
126121
self.agents[agent_name] = agent
@@ -130,23 +125,23 @@ def get_or_create_agent(
130125

131126
return agent
132127

133-
def create_handoff(self, from_agent_name: str, to_agent_name: str) -> None:
128+
async def create_handoff(self, from_agent_name: str, to_agent_name: str) -> None:
134129
"""
135130
Create a handoff from one agent to another.
136-
131+
137132
Args:
138133
from_agent_name: The name of the agent to handoff from
139134
to_agent_name: The name of the agent to handoff to
140135
"""
141-
from_agent = self.get_agent(from_agent_name)
142-
to_agent = self.get_agent(to_agent_name)
136+
from_agent = await self.get_agent(from_agent_name)
137+
to_agent = await self.get_agent(to_agent_name)
143138

144139
if not from_agent:
145140
raise ValueError(f"Agent '{from_agent_name}' not found")
146141
if not to_agent:
147142
raise ValueError(f"Agent '{to_agent_name}' not found")
148143

149-
from_agent.add_handoff(to_agent.id, self.client)
144+
await from_agent.add_handoff(to_agent.id, self.client)
150145

151146
# Update the local cache
152147
self.agents[from_agent_name] = from_agent

src/opensymbiose/agents/create_agents.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
Module for creating and managing Mistral AI agents.
33
This module provides a simple interface to create and manage agents using the AgentManager class.
44
"""
5+
56
from opensymbiose.agents.agent_manager import AgentManager
67

78

8-
def setup_agents():
9+
async def setup_agents():
910
"""
1011
Set up the agents for the application.
1112
@@ -16,41 +17,39 @@ def setup_agents():
1617
agent_manager = AgentManager()
1718

1819
# Get or create the test agent with web search tool
19-
test_agent = agent_manager.get_or_create_agent(
20+
test_agent = await agent_manager.get_or_create_agent(
2021
agent_name="Test Agent Tools",
2122
model="mistral-medium-latest",
2223
description="An agent with tools",
23-
tools=[{"type": "web_search"}]
24+
tools=[{"type": "web_search"}],
2425
)
2526

2627
# Get or create the calculating agent with code interpreter tool
27-
calculating_agent = agent_manager.get_or_create_agent(
28+
calculating_agent = await agent_manager.get_or_create_agent(
2829
agent_name="Calculating Agent",
2930
model="mistral-medium-latest",
3031
description="A calculating agent with tools",
31-
tools=[{"type": "code_interpreter"}]
32+
tools=[{"type": "code_interpreter"}],
3233
)
3334

3435
# Create handoff from test agent to calculating agent
35-
agent_manager.create_handoff("Test Agent Tools", "Calculating Agent")
36+
await agent_manager.create_handoff("Test Agent Tools", "Calculating Agent")
3637

3738
# Return a dictionary of agent names to Agent objects
38-
return {
39-
"test_agent_tools": test_agent,
40-
"calculating_agent": calculating_agent
41-
}
39+
return {"test_agent_tools": test_agent, "calculating_agent": calculating_agent}
4240

4341

4442
# Create a convenience function to get the agents
45-
def get_agents():
43+
async def get_agents():
4644
"""
4745
Get the agents for the application.
4846
4947
Returns:
5048
A dictionary of agent names to Agent objects
5149
"""
52-
return setup_agents()
50+
return await setup_agents()
5351

5452

5553
# For backwards compatibility and direct script execution
56-
symbiose_agents = setup_agents() if __name__ == "__main__" else {}
54+
# Note: This is now just a placeholder as setup_agents() is async and can't be awaited at module level
55+
symbiose_agents = {}

0 commit comments

Comments
 (0)