1
1
import asyncio
2
2
import os
3
- import socket
4
3
import sys
5
- from typing import Optional
6
- from urllib .parse import urlparse
7
4
8
- import nest_asyncio
9
- import yaml
10
-
11
- from agno import agent , team
5
+ from agno .agent import Agent
12
6
from agno .models .openai import OpenAIChat
13
7
from agno .playground import Playground , serve_playground_app
14
- from agno .tools .mcp import MCPTools , Toolkit
15
- from agno .tools .reasoning import ReasoningTools
8
+ from agno .team import Team
9
+ from agno .tools import Toolkit
10
+ from agno .tools .mcp import MCPTools
16
11
from fastapi .middleware .cors import CORSMiddleware
12
+ import nest_asyncio
13
+ import yaml
17
14
18
15
# Allow nested event loops
19
16
nest_asyncio .apply ()
20
17
21
- DOCKER_MODEL_PROVIDER = "docker"
22
-
23
- class Agent (agent .Agent ):
24
- @property
25
- def is_streamable (self ) -> bool :
26
- if self .stream is not None :
27
- return self .stream
28
- return super ().is_streamable
29
-
30
-
31
- class Team (team .Team ):
32
- @property
33
- def is_streamable (self ) -> bool :
34
- stream = getattr (self , "stream" )
35
- if stream is not None :
36
- return stream
37
- return super ().is_streamable
38
18
39
-
40
- def should_stream (model_provider : str , tools : list [Toolkit ]) -> Optional [bool ]:
41
- """Returns whether a model with the given provider and tools can stream"""
42
- if model_provider == DOCKER_MODEL_PROVIDER and len (tools ) > 0 :
43
- # DMR doesn't yet support tools with streaming
44
- return True
45
- # Let the model/options decide
46
- return None
47
-
48
-
49
- def create_model_from_config (entity_data : dict , entity_id : str ) -> tuple [OpenAIChat , str ]:
19
+ def create_model_from_config (entity_data : dict , entity_id : str ) -> OpenAIChat :
50
20
"""Create a model instance from entity configuration data."""
51
- model_name = entity_data .get ("model" )
52
- if not model_name :
53
- model_name = os .getenv ("MODEL_RUNNER_MODEL" )
54
- temperature = entity_data .get ("temperature" , None )
55
- provider = entity_data .get ("model_provider" , "docker" )
56
- model = create_model (model_name , provider , temperature )
57
- return model , provider
21
+ model = entity_data .get ("model" , {})
22
+ name = model .get ("name" )
23
+ if not name :
24
+ raise ValueError (
25
+ f"Model name not specified for { entity_id } . Please set 'model.name' in the configuration."
26
+ )
27
+ provider = model .get ("provider" , "" )
28
+ temperature = entity_data .get ("temperature" )
29
+ return create_model (name , provider , temperature )
58
30
59
31
60
- def create_model (model_name : str , provider : str , temperature : float ) -> OpenAIChat :
32
+ def create_model (
33
+ model_name : str , provider : str , temperature : float | None
34
+ ) -> OpenAIChat :
61
35
"""Create a model instance based on the model name and provider."""
62
- print (f"creating model { model_name } with provider { provider } and temperature { temperature } " )
63
- if provider == DOCKER_MODEL_PROVIDER :
36
+ print (
37
+ f"creating model { model_name } with provider { provider } and temperature { temperature } "
38
+ )
39
+ if provider == "docker" :
64
40
base_url = os .getenv ("MODEL_RUNNER_URL" )
65
41
if base_url is None :
66
- base_url = "http://model-runner.docker.internal/engines/llama.cpp/v1"
42
+ raise ValueError (
43
+ f"MODEL_RUNNER_URL environment variable not set for { model_name } ."
44
+ )
67
45
model = OpenAIChat (id = model_name , base_url = base_url , temperature = temperature )
68
46
model .role_map = {
69
47
"system" : "system" ,
@@ -91,36 +69,16 @@ async def create_mcp_tools(tools_list: list[str], entity_type: str) -> list[Tool
91
69
92
70
tool_names = [name .split (":" , 1 )[1 ] for name in tools_list ]
93
71
94
- # Always use socat, but the endpoint can be different (mock vs real gateway)
95
- endpoint = os .environ ['MCPGATEWAY_ENDPOINT' ]
96
- print (f"DEBUG: { entity_type } connecting to MCP gateway at { endpoint } " )
97
-
98
- # Parse endpoint to extract host and port
99
- try :
100
- # Handle both URL format (http://host:port/path) and host:port format
101
- if endpoint .startswith ('http://' ) or endpoint .startswith ('https://' ):
102
- parsed = urlparse (endpoint )
103
- host = parsed .hostname
104
- port = parsed .port
105
- tcp_endpoint = f"{ host } :{ port } "
106
- else :
107
- # Legacy host:port format
108
- host , port = endpoint .split (':' )
109
- port = int (port )
110
- tcp_endpoint = endpoint
111
-
112
- # Test TCP connection first
113
- sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
114
- sock .settimeout (5 )
115
- sock .connect ((host , port ))
116
- sock .close ()
117
- print (f"DEBUG: TCP connection to { host } :{ port } successful" )
118
- except Exception as e :
119
- print (f"ERROR: TCP connection to { endpoint } failed: { e } " )
120
- raise
72
+ url = os .environ .get ("MCPGATEWAY_URL" )
73
+ if not url :
74
+ raise ValueError (
75
+ f"MCPGATEWAY_URL environment variable not set for { entity_type } tools"
76
+ )
77
+ print (f"DEBUG: { entity_type } connecting to MCP gateway at { url } " )
121
78
122
79
t = MCPTools (
123
- command = f"socat STDIO TCP:{ tcp_endpoint } " ,
80
+ url = url ,
81
+ transport = "sse" ,
124
82
include_tools = tool_names ,
125
83
)
126
84
mcp_tools = await t .__aenter__ ()
@@ -130,9 +88,9 @@ async def create_mcp_tools(tools_list: list[str], entity_type: str) -> list[Tool
130
88
def get_common_config (entity_data : dict ) -> dict :
131
89
"""Extract common configuration options."""
132
90
return {
133
- ' markdown' : entity_data .get ("markdown" , False ),
134
- ' add_datetime_to_instructions' : True ,
135
- ' debug_mode' : True ,
91
+ " markdown" : entity_data .get ("markdown" , False ),
92
+ " add_datetime_to_instructions" : True ,
93
+ " debug_mode" : True ,
136
94
}
137
95
138
96
@@ -145,11 +103,11 @@ async def run_server(config) -> None:
145
103
teams_by_id = {}
146
104
147
105
for agent_id , agent_data in config .get ("agents" , {}).items ():
148
- model , provider = create_model_from_config (agent_data , agent_id )
106
+ model = create_model_from_config (agent_data , agent_id )
149
107
common_config = get_common_config (agent_data )
150
108
151
109
tools : list [Toolkit ] = [
152
- # ReasoningTools(think=True, analyze=True)
110
+ # ReasoningTools(think=True, analyze=True)
153
111
]
154
112
tools_list = agent_data .get ("tools" , [])
155
113
mcp_tools = await create_mcp_tools (tools_list , "Agent" )
@@ -160,10 +118,9 @@ async def run_server(config) -> None:
160
118
role = agent_data .get ("role" , "" ),
161
119
description = agent_data .get ("description" ),
162
120
instructions = agent_data .get ("instructions" ),
163
- tools = tools ,
121
+ tools = tools , # type: ignore
164
122
model = model ,
165
123
show_tool_calls = True ,
166
- stream = should_stream (provider , tools ),
167
124
** common_config ,
168
125
)
169
126
agents_by_id [agent_id ] = agent
@@ -172,7 +129,7 @@ async def run_server(config) -> None:
172
129
agents .append (agent )
173
130
174
131
for team_id , team_data in config .get ("teams" , {}).items ():
175
- model , provider = create_model_from_config (team_data , team_id )
132
+ model = create_model_from_config (team_data , team_id )
176
133
common_config = get_common_config (team_data )
177
134
178
135
team_agents : list [Agent | Team ] = []
@@ -184,7 +141,7 @@ async def run_server(config) -> None:
184
141
team_agents .append (agent )
185
142
186
143
team_tools : list [Toolkit ] = [
187
- # ReasoningTools(think=True, analyze=True)
144
+ # ReasoningTools(think=True, analyze=True)
188
145
]
189
146
tools_list = team_data .get ("tools" , [])
190
147
mcp_tools = await create_mcp_tools (tools_list , "Team" )
@@ -193,21 +150,20 @@ async def run_server(config) -> None:
193
150
team = Team (
194
151
name = team_data .get ("name" , "" ),
195
152
mode = team_data .get ("mode" , "coordinate" ),
196
- members = team_agents , # type: ignore
153
+ members = team_agents ,
197
154
description = team_data .get ("description" ),
198
155
instructions = team_data .get ("instructions" ),
199
- tools = team_tools , # type: ignore,
156
+ tools = team_tools , # type: ignore
200
157
model = model ,
201
158
# show_members_responses=True,
202
159
# show_tool_calls=True,
203
160
** common_config ,
204
161
)
205
- team .stream = should_stream (provider , team_tools )
206
162
teams_by_id [team_id ] = team
207
163
if team_data .get ("chat" , True ):
208
164
teams .append (team )
209
165
210
- playground = Playground (agents = agents , teams = teams ) # type: ignore
166
+ playground = Playground (agents = agents , teams = teams )
211
167
212
168
app = playground .get_app ()
213
169
app .add_middleware (
@@ -225,7 +181,8 @@ async def run_server(config) -> None:
225
181
def main ():
226
182
config_filename = sys .argv [1 ] if len (sys .argv ) > 1 else "/agents.yaml"
227
183
with open (config_filename , "r" ) as f :
228
- config = yaml .safe_load (f )
184
+ expanded = os .path .expandvars (f .read ())
185
+ config = yaml .safe_load (expanded )
229
186
230
187
asyncio .run (run_server (config ))
231
188
0 commit comments