corentinm7 commited on
Commit
84de7d8
·
verified ·
1 Parent(s): 3904c60

Upload folder using huggingface_hub

Browse files
CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
- # v0.0.2
 
 
2
 
 
3
  - More lax python version for HuggingFace Space
4
 
5
  # v0.0.1
 
1
+ # v0.0.3
2
+
3
+ - Async Query of Mistral API
4
 
5
+ # v0.0.2
6
  - More lax python version for HuggingFace Space
7
 
8
  # v0.0.1
Dockerfile CHANGED
@@ -12,12 +12,14 @@ RUN apt update && \
12
  git \
13
  && rm -rf /var/lib/apt/lists/*
14
 
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
20
 
21
- EXPOSE 8000
22
 
23
- CMD ["uv", "run", "opensymbiose/main.py"]
 
 
 
12
  git \
13
  && rm -rf /var/lib/apt/lists/*
14
 
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
20
 
21
+ EXPOSE 7860
22
 
23
+ ENV PYTHONPATH=/src
24
+ ENV GRADIO_SERVER_NAME=0.0.0.0
25
+ CMD ["uv", "run", "gradio", "/src/opensymbiose/gradio/app.py"]
Makefile CHANGED
@@ -53,10 +53,10 @@ dockerbuild:
53
  docker build -t opensymbiose:latest .
54
 
55
  dockerrun:
56
- docker run --rm opensymbiose:latest
57
 
58
  allci:
59
  $(MAKE) check
60
  $(MAKE) format
61
  $(MAKE) type
62
- $(MAKE) cov
 
53
  docker build -t opensymbiose:latest .
54
 
55
  dockerrun:
56
+ docker run --rm -p 7860:7860 --env-file .env opensymbiose:latest
57
 
58
  allci:
59
  $(MAKE) check
60
  $(MAKE) format
61
  $(MAKE) type
62
+ $(MAKE) cov
README.md CHANGED
@@ -7,7 +7,7 @@ python_version: 3.13
7
  ---
8
  # OpenSymbiose: Open Source Biotechnology AI Agent
9
 
10
- OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researcher.
11
 
12
  [DOCUMENTATION](https://lambda-science.github.io/OpenSymbiose/) - [DEMO ON HUGGINGFACE](https://huggingface.co/spaces/corentinm7/opensymbiose) - [CODE REPO](https://github.com/lambda-science/OpenSymbiose)
13
 
@@ -15,9 +15,12 @@ Creator and Maintainer: [**Corentin Meyer**, PhD](https://cmeyer.fr/) - <contact
15
 
16
  ## Installation
17
 
18
- From PyPi: `pip install opensymbiose`
 
19
 
20
- ## Usage
21
-
22
- Placeholder
23
 
 
 
 
 
 
7
  ---
8
  # OpenSymbiose: Open Source Biotechnology AI Agent
9
 
10
+ OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researchers.
11
 
12
  [DOCUMENTATION](https://lambda-science.github.io/OpenSymbiose/) - [DEMO ON HUGGINGFACE](https://huggingface.co/spaces/corentinm7/opensymbiose) - [CODE REPO](https://github.com/lambda-science/OpenSymbiose)
13
 
 
15
 
16
  ## Installation
17
 
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`
24
+ - Locally: Run in your terminal `opensymbiose` it will boot the gradio app
25
+ - In Docker: `make dockerbuild && make dockerrun` to run in Docker.
26
+ - From GitHub: clone the repository and run `make dev`. It will launch the Gradio app with hot-reloading.
docs/index.md CHANGED
@@ -7,14 +7,20 @@ hide:
7
 
8
  # OpenSymbiose: Open Source Biotechnology AI Agent
9
 
10
- OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researcher.
 
 
11
 
12
  Creator and Maintainer: [**Corentin Meyer**, PhD](https://cmeyer.fr/) - <[email protected]>
13
 
14
  ## Installation
15
 
16
- From PyPi: `pip install opensymbiose`
 
17
 
18
- ## Usage
19
 
20
- Placeholder
 
 
 
 
7
 
8
  # OpenSymbiose: Open Source Biotechnology AI Agent
9
 
10
+ OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researchers.
11
+
12
+ [DOCUMENTATION](https://lambda-science.github.io/OpenSymbiose/) - [DEMO ON HUGGINGFACE](https://huggingface.co/spaces/corentinm7/opensymbiose) - [CODE REPO](https://github.com/lambda-science/OpenSymbiose)
13
 
14
  Creator and Maintainer: [**Corentin Meyer**, PhD](https://cmeyer.fr/) - <[email protected]>
15
 
16
  ## Installation
17
 
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`
24
+ - Locally: Run in your terminal `opensymbiose` it will boot the gradio app
25
+ - In Docker: `make dockerbuild && make dockerrun` to run in Docker.
26
+ - From GitHub: clone the repository and run `make dev`. It will launch the Gradio app with hot-reloading.
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
  [project]
2
  name = "opensymbiose"
3
- version = "0.0.2"
4
  description = "OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researcher."
5
  readme = "README.md"
6
  requires-python = ">=3.10"
 
1
  [project]
2
  name = "opensymbiose"
3
+ version = "0.0.3"
4
  description = "OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researcher."
5
  readme = "README.md"
6
  requires-python = ">=3.10"
src/opensymbiose/agents/__init__.py CHANGED
@@ -8,4 +8,4 @@ from opensymbiose.agents.agent import Agent
8
  from opensymbiose.agents.agent_manager import AgentManager
9
  from opensymbiose.agents.create_agents import setup_agents, get_agents
10
 
11
- __all__ = ['Agent', 'AgentManager', 'setup_agents', 'get_agents']
 
8
  from opensymbiose.agents.agent_manager import AgentManager
9
  from opensymbiose.agents.create_agents import setup_agents, get_agents
10
 
11
+ __all__ = ["Agent", "AgentManager", "setup_agents", "get_agents"]
src/opensymbiose/agents/agent.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  Agent class to represent individual Mistral AI agents.
3
  """
 
4
  from typing import Any
5
 
6
  from mistralai import Mistral
@@ -14,7 +15,7 @@ class Agent:
14
  def __init__(self, agent_data: Any):
15
  """
16
  Initialize an Agent object from Mistral API agent data.
17
-
18
  Args:
19
  agent_data: The agent data returned from Mistral API
20
  """
@@ -23,49 +24,47 @@ class Agent:
23
  self.description = agent_data.description
24
  self.model = agent_data.model
25
  self.tools = agent_data.tools
26
- self.handoffs = agent_data.handoffs if hasattr(agent_data, 'handoffs') else []
27
  self._raw_data = agent_data
28
 
29
  @property
30
  def raw_data(self) -> Any:
31
  """
32
  Get the raw agent data from Mistral API.
33
-
34
  Returns:
35
  The raw agent data
36
  """
37
  return self._raw_data
38
 
39
- def add_handoff(self, agent_id: str, client: Mistral) -> None:
40
  """
41
  Add a handoff to another agent.
42
-
43
  Args:
44
  agent_id: The ID of the agent to handoff to
45
  client: The Mistral client instance
46
  """
47
  if agent_id not in self.handoffs:
48
  self.handoffs.append(agent_id)
49
- updated_agent = client.beta.agents.update(
50
- agent_id=self.id,
51
- handoffs=self.handoffs
52
  )
53
  # Update the raw data with the updated agent
54
  self._raw_data = updated_agent
55
 
56
- def remove_handoff(self, agent_id: str, client: Mistral) -> None:
57
  """
58
  Remove a handoff to another agent.
59
-
60
  Args:
61
  agent_id: The ID of the agent to remove handoff from
62
  client: The Mistral client instance
63
  """
64
  if agent_id in self.handoffs:
65
  self.handoffs.remove(agent_id)
66
- updated_agent = client.beta.agents.update(
67
- agent_id=self.id,
68
- handoffs=self.handoffs
69
  )
70
  # Update the raw data with the updated agent
71
  self._raw_data = updated_agent
@@ -73,7 +72,7 @@ class Agent:
73
  def __str__(self) -> str:
74
  """
75
  String representation of the agent.
76
-
77
  Returns:
78
  A string representation of the agent
79
  """
@@ -82,10 +81,12 @@ class Agent:
82
  def __repr__(self) -> str:
83
  """
84
  Detailed representation of the agent.
85
-
86
  Returns:
87
  A detailed representation of the agent
88
  """
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})")
 
 
 
1
  """
2
  Agent class to represent individual Mistral AI agents.
3
  """
4
+
5
  from typing import Any
6
 
7
  from mistralai import Mistral
 
15
  def __init__(self, agent_data: Any):
16
  """
17
  Initialize an Agent object from Mistral API agent data.
18
+
19
  Args:
20
  agent_data: The agent data returned from Mistral API
21
  """
 
24
  self.description = agent_data.description
25
  self.model = agent_data.model
26
  self.tools = agent_data.tools
27
+ self.handoffs = agent_data.handoffs if hasattr(agent_data, "handoffs") else []
28
  self._raw_data = agent_data
29
 
30
  @property
31
  def raw_data(self) -> Any:
32
  """
33
  Get the raw agent data from Mistral API.
34
+
35
  Returns:
36
  The raw agent data
37
  """
38
  return self._raw_data
39
 
40
+ async def add_handoff(self, agent_id: str, client: Mistral) -> None:
41
  """
42
  Add a handoff to another agent.
43
+
44
  Args:
45
  agent_id: The ID of the agent to handoff to
46
  client: The Mistral client instance
47
  """
48
  if agent_id not in self.handoffs:
49
  self.handoffs.append(agent_id)
50
+ updated_agent = client.beta.agents.update_async(
51
+ agent_id=self.id, handoffs=self.handoffs
 
52
  )
53
  # Update the raw data with the updated agent
54
  self._raw_data = updated_agent
55
 
56
+ async def remove_handoff(self, agent_id: str, client: Mistral) -> None:
57
  """
58
  Remove a handoff to another agent.
59
+
60
  Args:
61
  agent_id: The ID of the agent to remove handoff from
62
  client: The Mistral client instance
63
  """
64
  if agent_id in self.handoffs:
65
  self.handoffs.remove(agent_id)
66
+ updated_agent = client.beta.agents.update_async(
67
+ agent_id=self.id, handoffs=self.handoffs
 
68
  )
69
  # Update the raw data with the updated agent
70
  self._raw_data = updated_agent
 
72
  def __str__(self) -> str:
73
  """
74
  String representation of the agent.
75
+
76
  Returns:
77
  A string representation of the agent
78
  """
 
81
  def __repr__(self) -> str:
82
  """
83
  Detailed representation of the agent.
84
+
85
  Returns:
86
  A detailed representation of the agent
87
  """
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
+ )
src/opensymbiose/agents/agent_manager.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  AgentManager class to manage Mistral AI agents.
3
  """
 
4
  import os
5
  from typing import Dict, List, Optional
6
 
@@ -20,10 +21,10 @@ class AgentManager:
20
  def __new__(cls, api_key: Optional[str] = None):
21
  """
22
  Create a new instance of AgentManager or return the existing one (Singleton pattern).
23
-
24
  Args:
25
  api_key: The Mistral API key. If not provided, it will be read from the environment.
26
-
27
  Returns:
28
  The AgentManager instance
29
  """
@@ -35,7 +36,7 @@ class AgentManager:
35
  def __init__(self, api_key: Optional[str] = None):
36
  """
37
  Initialize the AgentManager with the Mistral API key.
38
-
39
  Args:
40
  api_key: The Mistral API key. If not provided, it will be read from the environment.
41
  """
@@ -46,81 +47,75 @@ class AgentManager:
46
  self.api_key = api_key or os.environ.get("MISTRAL_API_KEY")
47
  if not self.api_key:
48
  raise ValueError(
49
- "Mistral API key is required. Provide it as an argument or set the MISTRAL_API_KEY environment variable.")
 
50
 
51
  self.client = Mistral(self.api_key)
52
  self.agents: Dict[str, Agent] = {}
53
  self._initialized = True
54
 
55
- def list_agents(self) -> List[Agent]:
56
  """
57
  List all agents from the Mistral API.
58
-
59
  Returns:
60
  A list of Agent objects
61
  """
62
- agent_list = self.client.beta.agents.list()
63
  return [Agent(agent) for agent in agent_list]
64
 
65
- def refresh_agents(self) -> None:
66
  """
67
  Refresh the local cache of agents from the Mistral API.
68
  """
69
  self.agents = {}
70
- agent_list = self.client.beta.agents.list()
71
  for agent_data in agent_list:
72
  agent = Agent(agent_data)
73
  self.agents[agent.name] = agent
74
 
75
- def get_agent(self, agent_name: str) -> Optional[Agent]:
76
  """
77
  Get an agent by name.
78
-
79
  Args:
80
  agent_name: The name of the agent
81
-
82
  Returns:
83
  The Agent object if found, None otherwise
84
  """
85
  # Refresh agents if not already loaded
86
  if not self.agents:
87
- self.refresh_agents()
88
 
89
  return self.agents.get(agent_name)
90
 
91
- def get_or_create_agent(
92
- self,
93
- agent_name: str,
94
- model: str,
95
- description: str,
96
- tools: List[Dict[str, str]]
97
  ) -> Agent:
98
  """
99
  Get an agent by name or create it if it doesn't exist.
100
-
101
  Args:
102
  agent_name: The name of the agent
103
  model: The model to use for the agent
104
  description: The description of the agent
105
  tools: The tools to enable for the agent
106
-
107
  Returns:
108
  The Agent object
109
  """
110
  # Refresh agents if not already loaded
111
  if not self.agents:
112
- self.refresh_agents()
113
 
114
  # Check if agent exists
115
  agent = self.agents.get(agent_name)
116
 
117
  # Create agent if it doesn't exist
118
  if not agent:
119
- agent_data = self.client.beta.agents.create(
120
- model=model,
121
- description=description,
122
- name=agent_name,
123
- tools=tools
124
  )
125
  agent = Agent(agent_data)
126
  self.agents[agent_name] = agent
@@ -130,23 +125,23 @@ class AgentManager:
130
 
131
  return agent
132
 
133
- def create_handoff(self, from_agent_name: str, to_agent_name: str) -> None:
134
  """
135
  Create a handoff from one agent to another.
136
-
137
  Args:
138
  from_agent_name: The name of the agent to handoff from
139
  to_agent_name: The name of the agent to handoff to
140
  """
141
- from_agent = self.get_agent(from_agent_name)
142
- to_agent = self.get_agent(to_agent_name)
143
 
144
  if not from_agent:
145
  raise ValueError(f"Agent '{from_agent_name}' not found")
146
  if not to_agent:
147
  raise ValueError(f"Agent '{to_agent_name}' not found")
148
 
149
- from_agent.add_handoff(to_agent.id, self.client)
150
 
151
  # Update the local cache
152
  self.agents[from_agent_name] = from_agent
 
1
  """
2
  AgentManager class to manage Mistral AI agents.
3
  """
4
+
5
  import os
6
  from typing import Dict, List, Optional
7
 
 
21
  def __new__(cls, api_key: Optional[str] = None):
22
  """
23
  Create a new instance of AgentManager or return the existing one (Singleton pattern).
24
+
25
  Args:
26
  api_key: The Mistral API key. If not provided, it will be read from the environment.
27
+
28
  Returns:
29
  The AgentManager instance
30
  """
 
36
  def __init__(self, api_key: Optional[str] = None):
37
  """
38
  Initialize the AgentManager with the Mistral API key.
39
+
40
  Args:
41
  api_key: The Mistral API key. If not provided, it will be read from the environment.
42
  """
 
47
  self.api_key = api_key or os.environ.get("MISTRAL_API_KEY")
48
  if not self.api_key:
49
  raise ValueError(
50
+ "Mistral API key is required. Provide it as an argument or set the MISTRAL_API_KEY environment variable."
51
+ )
52
 
53
  self.client = Mistral(self.api_key)
54
  self.agents: Dict[str, Agent] = {}
55
  self._initialized = True
56
 
57
+ async def list_agents(self) -> List[Agent]:
58
  """
59
  List all agents from the Mistral API.
60
+
61
  Returns:
62
  A list of Agent objects
63
  """
64
+ agent_list = await self.client.beta.agents.list_async()
65
  return [Agent(agent) for agent in agent_list]
66
 
67
+ async def refresh_agents(self) -> None:
68
  """
69
  Refresh the local cache of agents from the Mistral API.
70
  """
71
  self.agents = {}
72
+ agent_list = await self.client.beta.agents.list_async()
73
  for agent_data in agent_list:
74
  agent = Agent(agent_data)
75
  self.agents[agent.name] = agent
76
 
77
+ async def get_agent(self, agent_name: str) -> Optional[Agent]:
78
  """
79
  Get an agent by name.
80
+
81
  Args:
82
  agent_name: The name of the agent
83
+
84
  Returns:
85
  The Agent object if found, None otherwise
86
  """
87
  # Refresh agents if not already loaded
88
  if not self.agents:
89
+ await self.refresh_agents()
90
 
91
  return self.agents.get(agent_name)
92
 
93
+ async def get_or_create_agent(
94
+ self, agent_name: str, model: str, description: str, tools: List[Dict[str, str]]
 
 
 
 
95
  ) -> Agent:
96
  """
97
  Get an agent by name or create it if it doesn't exist.
98
+
99
  Args:
100
  agent_name: The name of the agent
101
  model: The model to use for the agent
102
  description: The description of the agent
103
  tools: The tools to enable for the agent
104
+
105
  Returns:
106
  The Agent object
107
  """
108
  # Refresh agents if not already loaded
109
  if not self.agents:
110
+ await self.refresh_agents()
111
 
112
  # Check if agent exists
113
  agent = self.agents.get(agent_name)
114
 
115
  # Create agent if it doesn't exist
116
  if not agent:
117
+ agent_data = await self.client.beta.agents.create_async(
118
+ model=model, description=description, name=agent_name, tools=tools
 
 
 
119
  )
120
  agent = Agent(agent_data)
121
  self.agents[agent_name] = agent
 
125
 
126
  return agent
127
 
128
+ async def create_handoff(self, from_agent_name: str, to_agent_name: str) -> None:
129
  """
130
  Create a handoff from one agent to another.
131
+
132
  Args:
133
  from_agent_name: The name of the agent to handoff from
134
  to_agent_name: The name of the agent to handoff to
135
  """
136
+ from_agent = await self.get_agent(from_agent_name)
137
+ to_agent = await self.get_agent(to_agent_name)
138
 
139
  if not from_agent:
140
  raise ValueError(f"Agent '{from_agent_name}' not found")
141
  if not to_agent:
142
  raise ValueError(f"Agent '{to_agent_name}' not found")
143
 
144
+ await from_agent.add_handoff(to_agent.id, self.client)
145
 
146
  # Update the local cache
147
  self.agents[from_agent_name] = from_agent
src/opensymbiose/agents/create_agents.py CHANGED
@@ -2,10 +2,11 @@
2
  Module for creating and managing Mistral AI agents.
3
  This module provides a simple interface to create and manage agents using the AgentManager class.
4
  """
 
5
  from opensymbiose.agents.agent_manager import AgentManager
6
 
7
 
8
- def setup_agents():
9
  """
10
  Set up the agents for the application.
11
 
@@ -16,41 +17,39 @@ def setup_agents():
16
  agent_manager = AgentManager()
17
 
18
  # Get or create the test agent with web search tool
19
- test_agent = agent_manager.get_or_create_agent(
20
  agent_name="Test Agent Tools",
21
  model="mistral-medium-latest",
22
  description="An agent with tools",
23
- tools=[{"type": "web_search"}]
24
  )
25
 
26
  # Get or create the calculating agent with code interpreter tool
27
- calculating_agent = agent_manager.get_or_create_agent(
28
  agent_name="Calculating Agent",
29
  model="mistral-medium-latest",
30
  description="A calculating agent with tools",
31
- tools=[{"type": "code_interpreter"}]
32
  )
33
 
34
  # Create handoff from test agent to calculating agent
35
- agent_manager.create_handoff("Test Agent Tools", "Calculating Agent")
36
 
37
  # Return a dictionary of agent names to Agent objects
38
- return {
39
- "test_agent_tools": test_agent,
40
- "calculating_agent": calculating_agent
41
- }
42
 
43
 
44
  # Create a convenience function to get the agents
45
- def get_agents():
46
  """
47
  Get the agents for the application.
48
 
49
  Returns:
50
  A dictionary of agent names to Agent objects
51
  """
52
- return setup_agents()
53
 
54
 
55
  # For backwards compatibility and direct script execution
56
- symbiose_agents = setup_agents() if __name__ == "__main__" else {}
 
 
2
  Module for creating and managing Mistral AI agents.
3
  This module provides a simple interface to create and manage agents using the AgentManager class.
4
  """
5
+
6
  from opensymbiose.agents.agent_manager import AgentManager
7
 
8
 
9
+ async def setup_agents():
10
  """
11
  Set up the agents for the application.
12
 
 
17
  agent_manager = AgentManager()
18
 
19
  # Get or create the test agent with web search tool
20
+ test_agent = await agent_manager.get_or_create_agent(
21
  agent_name="Test Agent Tools",
22
  model="mistral-medium-latest",
23
  description="An agent with tools",
24
+ tools=[{"type": "web_search"}],
25
  )
26
 
27
  # Get or create the calculating agent with code interpreter tool
28
+ calculating_agent = await agent_manager.get_or_create_agent(
29
  agent_name="Calculating Agent",
30
  model="mistral-medium-latest",
31
  description="A calculating agent with tools",
32
+ tools=[{"type": "code_interpreter"}],
33
  )
34
 
35
  # Create handoff from test agent to calculating agent
36
+ await agent_manager.create_handoff("Test Agent Tools", "Calculating Agent")
37
 
38
  # Return a dictionary of agent names to Agent objects
39
+ return {"test_agent_tools": test_agent, "calculating_agent": calculating_agent}
 
 
 
40
 
41
 
42
  # Create a convenience function to get the agents
43
+ async def get_agents():
44
  """
45
  Get the agents for the application.
46
 
47
  Returns:
48
  A dictionary of agent names to Agent objects
49
  """
50
+ return await setup_agents()
51
 
52
 
53
  # For backwards compatibility and direct script execution
54
+ # Note: This is now just a placeholder as setup_agents() is async and can't be awaited at module level
55
+ symbiose_agents = {}
src/opensymbiose/agents/test_agent_manager.py CHANGED
@@ -1,11 +1,13 @@
1
  """
2
  Test script for the AgentManager and Agent classes.
3
  """
 
 
4
  import os
5
  import sys
6
 
7
  # Add the parent directory to the path to allow importing the modules
8
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..')))
9
 
10
  from opensymbiose.agents.agent_manager import AgentManager
11
  from opensymbiose.agents.create_agents import setup_agents, get_agents
@@ -40,22 +42,24 @@ def test_agent_manager():
40
  print("AgentManager tests completed successfully")
41
 
42
 
43
- def test_create_agents():
44
  """
45
  Test the create_agents module.
46
  """
47
  print("\nTesting create_agents module...")
48
 
49
  # Test setup_agents function
50
- agents = setup_agents()
51
  assert "test_agent_tools" in agents, "Test agent not created"
52
  assert "calculating_agent" in agents, "Calculating agent not created"
53
 
54
  print(f"✓ setup_agents created {len(agents)} agents")
55
 
56
  # Test get_agents function
57
- agents2 = get_agents()
58
- assert agents2["test_agent_tools"].id == agents["test_agent_tools"].id, "get_agents returned different agents"
 
 
59
 
60
  print("✓ get_agents works correctly")
61
 
@@ -70,7 +74,8 @@ def test_create_agents():
70
  print("create_agents tests completed successfully")
71
 
72
 
73
- if __name__ == "__main__":
 
74
  # Check if MISTRAL_API_KEY is set
75
  if not os.environ.get("MISTRAL_API_KEY"):
76
  print("Error: MISTRAL_API_KEY environment variable is not set")
@@ -78,6 +83,10 @@ if __name__ == "__main__":
78
  sys.exit(1)
79
 
80
  test_agent_manager()
81
- test_create_agents()
82
 
83
  print("\nAll tests completed successfully!")
 
 
 
 
 
1
  """
2
  Test script for the AgentManager and Agent classes.
3
  """
4
+
5
+ import asyncio
6
  import os
7
  import sys
8
 
9
  # Add the parent directory to the path to allow importing the modules
10
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")))
11
 
12
  from opensymbiose.agents.agent_manager import AgentManager
13
  from opensymbiose.agents.create_agents import setup_agents, get_agents
 
42
  print("AgentManager tests completed successfully")
43
 
44
 
45
+ async def test_create_agents():
46
  """
47
  Test the create_agents module.
48
  """
49
  print("\nTesting create_agents module...")
50
 
51
  # Test setup_agents function
52
+ agents = await setup_agents()
53
  assert "test_agent_tools" in agents, "Test agent not created"
54
  assert "calculating_agent" in agents, "Calculating agent not created"
55
 
56
  print(f"✓ setup_agents created {len(agents)} agents")
57
 
58
  # Test get_agents function
59
+ agents2 = await get_agents()
60
+ assert agents2["test_agent_tools"].id == agents["test_agent_tools"].id, (
61
+ "get_agents returned different agents"
62
+ )
63
 
64
  print("✓ get_agents works correctly")
65
 
 
74
  print("create_agents tests completed successfully")
75
 
76
 
77
+ async def main():
78
+ """Run all tests."""
79
  # Check if MISTRAL_API_KEY is set
80
  if not os.environ.get("MISTRAL_API_KEY"):
81
  print("Error: MISTRAL_API_KEY environment variable is not set")
 
83
  sys.exit(1)
84
 
85
  test_agent_manager()
86
+ await test_create_agents()
87
 
88
  print("\nAll tests completed successfully!")
89
+
90
+
91
+ if __name__ == "__main__":
92
+ asyncio.run(main())
src/opensymbiose/gradio/app.py CHANGED
@@ -12,8 +12,8 @@ from opensymbiose.agents.create_agents import get_agents
12
 
13
  logging.basicConfig(
14
  level=logging.WARNING,
15
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
16
- handlers=[logging.StreamHandler()]
17
  )
18
  logger = logging.getLogger(__name__)
19
  client = Mistral(api_key=opensymbiose.config.MISTRAL_API_KEY)
@@ -23,7 +23,8 @@ model = "mistral-medium-latest"
23
  logger.info(f"Gradio version: {gr.__version__}")
24
  logger.info(f"Python version: {sys.version}")
25
 
26
- def get_mistral_answer(message: str, history = None):
 
27
  # Start global execution timer
28
  global_start_time = time.time()
29
  logger.info(f"Processing message: {message}")
@@ -31,73 +32,140 @@ def get_mistral_answer(message: str, history = None):
31
  # Dictionary to track tool execution times
32
  tool_timers = {}
33
 
34
- agents = get_agents()
35
  main_agent = agents["test_agent_tools"]
36
- stream_response = client.beta.conversations.start_stream(
37
  agent_id=main_agent.id, inputs=message, store=False
38
  )
39
  messages = []
40
  # Add an initial empty message for the assistant's response
41
  messages.append(asdict(ChatMessage(role="assistant", content="")))
42
- yield messages
43
  full_response = ""
44
- for chunk in stream_response:
45
  logger.info(f"Chunk: {chunk}")
46
  if chunk.event == "message.output.delta":
47
- if isinstance(chunk.data.content, dict) and "filepath" in chunk.data.content:
 
 
 
48
  # Handle file messages with proper alt_text
49
  file_path = chunk.data.content["filepath"]
50
- alt_text = chunk.data.content.get("alt_text", "File attachment") # Default alt_text if not provided
51
- messages.append(asdict(ChatMessage(role="assistant", content=file_path,
52
- metadata={"type": "file", "alt_text": alt_text})))
 
 
 
 
 
 
 
 
 
53
  elif isinstance(chunk.data.content, str):
54
  full_response += chunk.data.content
55
- if messages and messages[-1].get("role") == "assistant" and not messages[-1].get("metadata"):
56
- messages[-1] = asdict(ChatMessage(role="assistant", content=full_response))
 
 
 
 
 
 
57
  else:
58
- messages.append(asdict(ChatMessage(role="assistant", content=full_response)))
 
 
59
  elif isinstance(chunk.data.content, ToolReferenceChunk):
60
- full_response += f"([{chunk.data.content.title}]({chunk.data.content.url})) "
61
- if messages and messages[-1].get("role") == "assistant" and not messages[-1].get("metadata"):
62
- messages[-1] = asdict(ChatMessage(role="assistant", content=full_response))
 
 
 
 
 
 
 
 
63
  else:
64
- messages.append(asdict(ChatMessage(role="assistant", content=full_response)))
 
 
65
  yield messages
66
  elif chunk.event == "tool.execution.started":
67
  # Start timer for this tool
68
  tool_timers[chunk.data.name] = time.time()
69
  # Add a new message for tool execution start
70
- messages.append(asdict(ChatMessage(role="assistant", content="",
71
- metadata={"title": f"🛠️ Using tool {chunk.data.name}..."})))
 
 
 
 
 
 
 
72
  yield messages
73
  elif chunk.event == "tool.execution.done":
74
  # Calculate tool execution duration
75
  tool_duration = time.time() - tool_timers.get(chunk.data.name, time.time())
76
  # Add a new message for tool execution completion
77
- messages.append(asdict(ChatMessage(role="assistant", content="",
78
- metadata={"title": f"🛠️ Finished using {chunk.data.name}.",
79
- "duration": round(tool_duration, 2)})))
 
 
 
 
 
 
 
 
 
80
  # Add a new empty message for the continuing assistant response
81
- messages.append(asdict(ChatMessage(role="assistant", content=full_response)))
 
 
82
  yield messages
83
  elif chunk.event == "agent.handoff.started":
84
  # Start timer for agent handoff
85
  tool_timers["agent_handoff"] = time.time()
86
  # Add a new message for agent handoff start
87
- messages.append(asdict(ChatMessage(role="assistant", content="",
88
- metadata={
89
- "title": f"🔄 Handing off from agent {chunk.data.previous_agent_name}..."})))
 
 
 
 
 
 
 
 
90
  yield messages
91
  elif chunk.event == "agent.handoff.done":
92
  # Calculate handoff duration
93
- handoff_duration = time.time() - tool_timers.get("agent_handoff", time.time())
 
 
94
  # Add a new message for agent handoff completion
95
- messages.append(asdict(ChatMessage(role="assistant", content="",
96
- metadata={
97
- "title": f"✅ Handoff complete. Now using agent {chunk.data.next_agent_name}.",
98
- "duration": round(handoff_duration, 2)})))
 
 
 
 
 
 
 
 
99
  # Add a new empty message for the continuing assistant response
100
- messages.append(asdict(ChatMessage(role="assistant", content=full_response)))
 
 
101
  yield messages
102
  elif chunk.event == "function.call.delta":
103
  # Start timer for function call
@@ -106,32 +174,66 @@ def get_mistral_answer(message: str, history = None):
106
  function_info = f"Calling function: {chunk.data.name} with arguments: {chunk.data.arguments}"
107
  # Store the function name for potential future duration calculation
108
  tool_timers[f"function_{chunk.data.name}"] = function_start_time
109
- messages.append(asdict(ChatMessage(role="assistant", content="",
110
- metadata={"title": f"📞 {function_info}"})))
 
 
 
 
 
 
 
111
  yield messages
112
  elif chunk.event == "conversation.response.started":
113
  # Add a new message for conversation response start
114
- messages.append(asdict(ChatMessage(role="assistant", content="",
115
- metadata={"title": f"🚀 Symbiose agent is starting...",
116
- "log": f"{chunk.data.conversation_id}"})))
 
 
 
 
 
 
 
 
 
117
  yield messages
118
  elif chunk.event == "conversation.response.done":
119
  # Calculate global execution duration
120
  global_duration = time.time() - global_start_time
121
  # Add a new message for conversation response completion
122
- messages.append(asdict(ChatMessage(role="assistant", content="",
123
- metadata={"title": f"✅ Conversation response complete.",
124
- "log": f"Tokens: Prompt: {chunk.data.usage.prompt_tokens}, Completion: {chunk.data.usage.completion_tokens}, Total: {chunk.data.usage.total_tokens} Connectors: {chunk.data.usage.connector_tokens}",
125
- "duration": round(global_duration, 2)})))
126
- logger.info(f"Conversation response complete. Duration: {round(global_duration, 2)}s, Total tokens: {chunk.data.usage.total_tokens}")
 
 
 
 
 
 
 
 
 
 
 
127
  yield messages
128
  elif chunk.event == "conversation.response.error":
129
  # Add a new message for conversation response error
130
  error_message = f"Error: {chunk.data.message} (Code: {chunk.data.code})"
131
  logger.error(f"Conversation response error: {error_message}")
132
  logger.error(f"Chunk: {chunk}")
133
- messages.append(asdict(ChatMessage(role="assistant", content="",
134
- metadata={"title": f"❌ {error_message}"})))
 
 
 
 
 
 
 
135
  yield messages
136
  else:
137
  # For other events, just update the last message
@@ -145,13 +247,17 @@ with gr.Blocks() as demo:
145
  type="messages",
146
  title="Open-Symbiose🧬",
147
  description="OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researcher",
148
- examples=["search internet: CEO of google", "Use your calculator agent to do 1273*3/12^2"],
 
 
 
149
  save_history=True,
150
  flagging_mode="manual",
151
- cache_examples=False # Disable caching for examples to prevent startup validation errors
 
152
  )
153
 
154
  if __name__ == "__main__":
155
  logger.setLevel(logging.INFO)
156
  logging.getLogger().setLevel(logging.INFO)
157
- demo.launch()
 
12
 
13
  logging.basicConfig(
14
  level=logging.WARNING,
15
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
16
+ handlers=[logging.StreamHandler()],
17
  )
18
  logger = logging.getLogger(__name__)
19
  client = Mistral(api_key=opensymbiose.config.MISTRAL_API_KEY)
 
23
  logger.info(f"Gradio version: {gr.__version__}")
24
  logger.info(f"Python version: {sys.version}")
25
 
26
+
27
+ async def get_mistral_answer(message: str, history=None):
28
  # Start global execution timer
29
  global_start_time = time.time()
30
  logger.info(f"Processing message: {message}")
 
32
  # Dictionary to track tool execution times
33
  tool_timers = {}
34
 
35
+ agents = await get_agents()
36
  main_agent = agents["test_agent_tools"]
37
+ stream_response = await client.beta.conversations.start_stream_async(
38
  agent_id=main_agent.id, inputs=message, store=False
39
  )
40
  messages = []
41
  # Add an initial empty message for the assistant's response
42
  messages.append(asdict(ChatMessage(role="assistant", content="")))
 
43
  full_response = ""
44
+ async for chunk in stream_response:
45
  logger.info(f"Chunk: {chunk}")
46
  if chunk.event == "message.output.delta":
47
+ if (
48
+ isinstance(chunk.data.content, dict)
49
+ and "filepath" in chunk.data.content
50
+ ):
51
  # Handle file messages with proper alt_text
52
  file_path = chunk.data.content["filepath"]
53
+ alt_text = chunk.data.content.get(
54
+ "alt_text", "File attachment"
55
+ ) # Default alt_text if not provided
56
+ messages.append(
57
+ asdict(
58
+ ChatMessage(
59
+ role="assistant",
60
+ content=file_path,
61
+ metadata={"type": "file", "alt_text": alt_text},
62
+ )
63
+ )
64
+ )
65
  elif isinstance(chunk.data.content, str):
66
  full_response += chunk.data.content
67
+ if (
68
+ messages
69
+ and messages[-1].get("role") == "assistant"
70
+ and not messages[-1].get("metadata")
71
+ ):
72
+ messages[-1] = asdict(
73
+ ChatMessage(role="assistant", content=full_response)
74
+ )
75
  else:
76
+ messages.append(
77
+ asdict(ChatMessage(role="assistant", content=full_response))
78
+ )
79
  elif isinstance(chunk.data.content, ToolReferenceChunk):
80
+ full_response += (
81
+ f"([{chunk.data.content.title}]({chunk.data.content.url})) "
82
+ )
83
+ if (
84
+ messages
85
+ and messages[-1].get("role") == "assistant"
86
+ and not messages[-1].get("metadata")
87
+ ):
88
+ messages[-1] = asdict(
89
+ ChatMessage(role="assistant", content=full_response)
90
+ )
91
  else:
92
+ messages.append(
93
+ asdict(ChatMessage(role="assistant", content=full_response))
94
+ )
95
  yield messages
96
  elif chunk.event == "tool.execution.started":
97
  # Start timer for this tool
98
  tool_timers[chunk.data.name] = time.time()
99
  # Add a new message for tool execution start
100
+ messages.append(
101
+ asdict(
102
+ ChatMessage(
103
+ role="assistant",
104
+ content="",
105
+ metadata={"title": f"🛠️ Using tool {chunk.data.name}..."},
106
+ )
107
+ )
108
+ )
109
  yield messages
110
  elif chunk.event == "tool.execution.done":
111
  # Calculate tool execution duration
112
  tool_duration = time.time() - tool_timers.get(chunk.data.name, time.time())
113
  # Add a new message for tool execution completion
114
+ messages.append(
115
+ asdict(
116
+ ChatMessage(
117
+ role="assistant",
118
+ content="",
119
+ metadata={
120
+ "title": f"🛠️ Finished using {chunk.data.name}.",
121
+ "duration": round(tool_duration, 2),
122
+ },
123
+ )
124
+ )
125
+ )
126
  # Add a new empty message for the continuing assistant response
127
+ messages.append(
128
+ asdict(ChatMessage(role="assistant", content=full_response))
129
+ )
130
  yield messages
131
  elif chunk.event == "agent.handoff.started":
132
  # Start timer for agent handoff
133
  tool_timers["agent_handoff"] = time.time()
134
  # Add a new message for agent handoff start
135
+ messages.append(
136
+ asdict(
137
+ ChatMessage(
138
+ role="assistant",
139
+ content="",
140
+ metadata={
141
+ "title": f"🔄 Handing off from agent {chunk.data.previous_agent_name}..."
142
+ },
143
+ )
144
+ )
145
+ )
146
  yield messages
147
  elif chunk.event == "agent.handoff.done":
148
  # Calculate handoff duration
149
+ handoff_duration = time.time() - tool_timers.get(
150
+ "agent_handoff", time.time()
151
+ )
152
  # Add a new message for agent handoff completion
153
+ messages.append(
154
+ asdict(
155
+ ChatMessage(
156
+ role="assistant",
157
+ content="",
158
+ metadata={
159
+ "title": f"✅ Handoff complete. Now using agent {chunk.data.next_agent_name}.",
160
+ "duration": round(handoff_duration, 2),
161
+ },
162
+ )
163
+ )
164
+ )
165
  # Add a new empty message for the continuing assistant response
166
+ messages.append(
167
+ asdict(ChatMessage(role="assistant", content=full_response))
168
+ )
169
  yield messages
170
  elif chunk.event == "function.call.delta":
171
  # Start timer for function call
 
174
  function_info = f"Calling function: {chunk.data.name} with arguments: {chunk.data.arguments}"
175
  # Store the function name for potential future duration calculation
176
  tool_timers[f"function_{chunk.data.name}"] = function_start_time
177
+ messages.append(
178
+ asdict(
179
+ ChatMessage(
180
+ role="assistant",
181
+ content="",
182
+ metadata={"title": f"📞 {function_info}"},
183
+ )
184
+ )
185
+ )
186
  yield messages
187
  elif chunk.event == "conversation.response.started":
188
  # Add a new message for conversation response start
189
+ messages.append(
190
+ asdict(
191
+ ChatMessage(
192
+ role="assistant",
193
+ content="",
194
+ metadata={
195
+ "title": "🚀 Symbiose agent is starting...",
196
+ "log": f"{chunk.data.conversation_id}",
197
+ },
198
+ )
199
+ )
200
+ )
201
  yield messages
202
  elif chunk.event == "conversation.response.done":
203
  # Calculate global execution duration
204
  global_duration = time.time() - global_start_time
205
  # Add a new message for conversation response completion
206
+ messages.append(
207
+ asdict(
208
+ ChatMessage(
209
+ role="assistant",
210
+ content="",
211
+ metadata={
212
+ "title": "✅ Conversation response complete.",
213
+ "log": f"Tokens: Prompt: {chunk.data.usage.prompt_tokens}, Completion: {chunk.data.usage.completion_tokens}, Total: {chunk.data.usage.total_tokens} Connectors: {chunk.data.usage.connector_tokens}",
214
+ "duration": round(global_duration, 2),
215
+ },
216
+ )
217
+ )
218
+ )
219
+ logger.info(
220
+ f"Conversation response complete. Duration: {round(global_duration, 2)}s, Total tokens: {chunk.data.usage.total_tokens}"
221
+ )
222
  yield messages
223
  elif chunk.event == "conversation.response.error":
224
  # Add a new message for conversation response error
225
  error_message = f"Error: {chunk.data.message} (Code: {chunk.data.code})"
226
  logger.error(f"Conversation response error: {error_message}")
227
  logger.error(f"Chunk: {chunk}")
228
+ messages.append(
229
+ asdict(
230
+ ChatMessage(
231
+ role="assistant",
232
+ content="",
233
+ metadata={"title": f"❌ {error_message}"},
234
+ )
235
+ )
236
+ )
237
  yield messages
238
  else:
239
  # For other events, just update the last message
 
247
  type="messages",
248
  title="Open-Symbiose🧬",
249
  description="OpenSymbiose is an open-source biotechnology / biology research AI agent designed to support researcher",
250
+ examples=[
251
+ "search internet: CEO of google",
252
+ "Use your calculator agent to do 1273*3/12^2",
253
+ ],
254
  save_history=True,
255
  flagging_mode="manual",
256
+ cache_examples=False, # Disable caching for examples to prevent startup validation errors
257
+ concurrency_limit=None,
258
  )
259
 
260
  if __name__ == "__main__":
261
  logger.setLevel(logging.INFO)
262
  logging.getLogger().setLevel(logging.INFO)
263
+ demo.launch(ssr_mode=False)
uv.lock CHANGED
@@ -1310,7 +1310,7 @@ wheels = [
1310
 
1311
  [[package]]
1312
  name = "opensymbiose"
1313
- version = "0.0.2"
1314
  source = { editable = "." }
1315
  dependencies = [
1316
  { name = "biopython" },
 
1310
 
1311
  [[package]]
1312
  name = "opensymbiose"
1313
+ version = "0.0.3"
1314
  source = { editable = "." }
1315
  dependencies = [
1316
  { name = "biopython" },