Skip to content

Commit 2d84a6a

Browse files
authored
Calling Built-in tools (#418)
* Built in tools rnd 1 Signed-off-by: Trevor Grant <[email protected]> * Builtin Tool Calling Works Signed-off-by: Trevor Grant <[email protected]> --------- Signed-off-by: Trevor Grant <[email protected]>
1 parent 5303851 commit 2d84a6a

File tree

17 files changed

+359
-65
lines changed

17 files changed

+359
-65
lines changed

webapp/packages/api/user-service/agent_factory/__init__.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# webapp/packages/api/user-service/agent_factory/__init__.py
12
from .remote_mcp_client import RemoteMCPClient
23
from .prompts import how_to_use_tools, \
34
how_to_use_litellm, \
@@ -10,7 +11,9 @@
1011
import asyncio
1112

1213
from services.database_service import get_database_service
14+
from services.llm_service import call_llm
1315
from config import settings
16+
from config.provider_config import PROVIDER_CONFIG
1417

1518
async def generate_agent_code(request: GenerateCodeRequest):
1619
"""
@@ -112,13 +115,26 @@ async def generate_agent_code(request: GenerateCodeRequest):
112115
{"role": "system", "content": system_prompt},
113116
{"role": "user", "content": request.description},
114117
]
118+
119+
# Build tools list from config
120+
built_in_tools = []
121+
model_tool_config = PROVIDER_CONFIG.get(provider, {}).get("models", {}).get(model, {}).get("built_in_tools", [])
122+
if request.built_in_tools:
123+
for tool_id in request.built_in_tools:
124+
tool_conf = next((t for t in model_tool_config if t["id"] == tool_id), None)
125+
if tool_conf:
126+
built_in_tools.append(tool_conf["tool_config"])
115127

116128
config = request.composer_model_config.parameters
117-
code_gen_task = acompletion(
118-
model=f"{provider}/{model}",
119-
messages=code_gen_messages,
120-
**config
121-
)
129+
130+
async def code_gen_with_thoughts():
131+
return await call_llm(
132+
provider=provider,
133+
model=model,
134+
messages=code_gen_messages,
135+
parameters=config,
136+
tools=built_in_tools if built_in_tools else None,
137+
)
122138

123139
# ---- Friendly Name and Docstring Generation Task ----
124140
name_doc_prompt = f"""
@@ -157,15 +173,12 @@ async def generate_agent_code(request: GenerateCodeRequest):
157173
)
158174

159175
# ---- Run tasks concurrently ----
160-
code_response, name_doc_response = await asyncio.gather(
161-
code_gen_task,
176+
(code_body, thoughts), name_doc_response = await asyncio.gather(
177+
code_gen_with_thoughts(),
162178
name_doc_gen_task
163179
)
164180

165181
# ---- Process Code Generation Response ----
166-
code_body = code_response.choices[0].message.content
167-
168-
169182
# Clean up potential markdown formatting from the response
170183
if code_body.strip().startswith("```python"):
171184
code_body = code_body.strip()[len("```python"):].strip()
@@ -202,5 +215,4 @@ async def run(input_dict, tools):
202215
friendly_name = "parsing_error_function"
203216
docstring = f"Could not parse docstring from LLM response:\n{name_doc_content}"
204217

205-
return GenerateCodeResponse(code=full_code, friendly_name=friendly_name, docstring=docstring)
206-
218+
return GenerateCodeResponse(code=full_code, friendly_name=friendly_name, docstring=docstring, thoughts=thoughts)

webapp/packages/api/user-service/agent_factory/demo_factory.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# webapp/packages/api/user-service/agent_factory/demo_factory.py
22
from .prompts import how_to_build_demo_app_template
3-
from litellm import acompletion
43
from models.demo import GenerateDemoCodeRequest, GenerateDemoCodeResponse, DeployedApi
54
import json
65

6+
from services.llm_service import call_llm
7+
from config.provider_config import PROVIDER_CONFIG
8+
79
def _format_api_docs(apis: list[DeployedApi]) -> str:
810
"""Formats the API information into a markdown string for the prompt."""
911
if not apis:
@@ -55,13 +57,23 @@ async def generate_demo_code(request: GenerateDemoCodeRequest) -> GenerateDemoCo
5557
if provider == "openai":
5658
config['response_format'] = { "type": "json_object" }
5759

58-
response = await acompletion(
59-
model=f"{provider}/{model}",
60+
# Build tools list from config
61+
built_in_tools = []
62+
model_tool_config = PROVIDER_CONFIG.get(provider, {}).get("models", {}).get(model, {}).get("built_in_tools", [])
63+
if request.built_in_tools:
64+
for tool_id in request.built_in_tools:
65+
tool_conf = next((t for t in model_tool_config if t["id"] == tool_id), None)
66+
if tool_conf:
67+
built_in_tools.append(tool_conf["tool_config"])
68+
69+
response_content, thoughts = await call_llm(
70+
provider=provider,
71+
model=model,
6072
messages=messages,
61-
**config
73+
parameters=config,
74+
tools=built_in_tools if built_in_tools else None
6275
)
6376

64-
response_content = response.choices[0].message.content
6577
try:
6678
# Clean up potential markdown
6779
if response_content.strip().startswith("```json"):
@@ -70,7 +82,11 @@ async def generate_demo_code(request: GenerateDemoCodeRequest) -> GenerateDemoCo
7082
response_content = response_content.strip()[:-len("```")].strip()
7183

7284
code_json = json.loads(response_content)
73-
return GenerateDemoCodeResponse(**code_json)
85+
86+
# Ensure the response includes the thoughts
87+
response_obj = GenerateDemoCodeResponse(**code_json)
88+
response_obj.thoughts = thoughts
89+
return response_obj
7490
except (json.JSONDecodeError, TypeError) as e:
7591
print(f"Error parsing demo code JSON from LLM: {e}")
7692
print(f"LLM response was: {response_content}")

webapp/packages/api/user-service/config/provider_config.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,32 @@
5252
}
5353
}
5454
},
55+
"gpt-4.1-mini": {
56+
"api_style": "responses",
57+
"parameters": {
58+
"temperature": {
59+
"type": "float",
60+
"default": 0.7,
61+
"min": 0.0,
62+
"max": 2.0,
63+
"description": "Controls randomness"
64+
},
65+
"max_tokens": {
66+
"type": "integer",
67+
"default": 2048,
68+
"min": 1,
69+
"max": 4096,
70+
"description": "Maximum tokens in response"
71+
}
72+
},
73+
"built_in_tools": [
74+
{
75+
"id": "web_search",
76+
"description": "Performs a web search.",
77+
"tool_config": {"type": "web_search_preview", "search_context_size": "low"}
78+
}
79+
]
80+
},
5581
"gpt-4.1-nano": {
5682
"parameters": {
5783
"temperature": {
@@ -89,7 +115,14 @@
89115
"default": 0,
90116
"choices": ["disable", "low", "medium", "high"],
91117
}
92-
}
118+
},
119+
"built_in_tools": [
120+
{
121+
"id": "google_search",
122+
"description": "Performs a Google search.",
123+
"tool_config": {"google_search": {}}
124+
}
125+
]
93126
}
94127
}
95128
},
@@ -126,7 +159,14 @@
126159
"max": 100,
127160
"description": "Top-k sampling"
128161
}
129-
}
162+
},
163+
"built_in_tools": [
164+
{
165+
"id": "web_search",
166+
"description": "Performs a web search.",
167+
"tool_config": {"name": "web_search"}
168+
}
169+
]
130170
},
131171
"claude-3-sonnet": {
132172
"parameters": {

webapp/packages/api/user-service/main.py

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,29 @@
2222
from services.database_service import get_database_service, DatabaseService
2323
from config import settings
2424
from services.mcp_client_service import McpClientService, get_mcp_client_service
25-
# New imports for observability
25+
2626
from services.observability_service import (
2727
get_observability_service,
2828
ObservabilityMiddleware,
2929
ObservabilityService,
3030
get_sanitized_request_data
3131
)
3232

33+
from services.llm_service import call_llm
34+
35+
# Import the shared provider configuration
36+
from config.provider_config import PROVIDER_CONFIG as APP_PROVIDER_CONFIG
37+
from models.agent import (
38+
GenerateCodeRequest, GenerateCodeResponse, RunCodeRequest,
39+
RunCodeResponse, Agent, CreateAgentRequest, Deployment, DeployedApi
40+
)
41+
from models.demo import (
42+
GenerateDemoCodeRequest, GenerateDemoCodeResponse,
43+
CreateDemoAppRequest, DemoApp
44+
)
45+
46+
from agent_factory.remote_mcp_client import RemoteMCPClient
47+
3348

3449
# --- Firebase Admin SDK Initialization ---
3550
if settings.APP_ENV == "firebase":
@@ -60,18 +75,6 @@ async def startup_event():
6075

6176
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
6277

63-
# Import the shared provider configuration
64-
from config.provider_config import PROVIDER_CONFIG as APP_PROVIDER_CONFIG
65-
from models.agent import (
66-
GenerateCodeRequest, GenerateCodeResponse, RunCodeRequest,
67-
RunCodeResponse, Agent, CreateAgentRequest, Deployment, DeployedApi
68-
)
69-
from models.demo import (
70-
GenerateDemoCodeRequest, GenerateDemoCodeResponse,
71-
CreateDemoAppRequest, DemoApp
72-
)
73-
74-
from agent_factory.remote_mcp_client import RemoteMCPClient
7578

7679

7780
frontend_url = os.getenv("FRONTEND_URL", "http://localhost:3000")
@@ -116,19 +119,6 @@ async def get_current_user(request: Request, token: str = Depends(oauth2_scheme)
116119
raise HTTPException(status_code=500, detail=f"Authentication error: {e}")
117120

118121

119-
# Models
120-
class ChatMessage(BaseModel):
121-
role: str = Field(..., pattern="^(user|assistant|system)$")
122-
content: str
123-
124-
class ChatRequest(BaseModel):
125-
messages: List[ChatMessage]
126-
provider: str = "openai"
127-
model: str = "gpt-3.5-turbo"
128-
parameters: Dict[str, Any] = {}
129-
stream: bool = False
130-
131-
132122
class ListMcpToolsRequest(BaseModel):
133123
mcp_url: str
134124
auth_token: Optional[str] = None
@@ -143,7 +133,7 @@ class FetchSpecRequest(BaseModel):
143133
url: str
144134

145135
# Import models after defining local ones to avoid circular dependencies
146-
from models.chat import ChatResponse, ProviderConfig, SessionData
136+
from models.chat import ChatRequest, ChatMessage, ChatResponse, ProviderConfig, SessionData
147137

148138

149139
# --- Dependencies ---
@@ -164,31 +154,42 @@ async def process_chat(ticket_id: str, request: ChatRequest, user: dict, req: Re
164154
ticket_data = {
165155
"status": "processing",
166156
"created_at": datetime.utcnow().isoformat(), # Use isoformat for JSON serialization
167-
"request": request.dict()
157+
"request": request.dict(by_alias=True)
168158
}
169159
db_service.save("tickets", ticket_id, ticket_data)
170160

171161
# Convert messages to format expected by litellm
172162
messages = [{"role": msg.role, "content": msg.content} for msg in request.messages]
173163

174-
model_name = f"{request.provider}/{request.model}" if request.provider not in ["openai", "azure"] else request.model
164+
# Build tools list from config
165+
built_in_tools = []
166+
model_tool_config = APP_PROVIDER_CONFIG.get(request.provider, {}).get("models", {}).get(request.model, {}).get("built_in_tools", [])
167+
if request.built_in_tools:
168+
for tool_id in request.built_in_tools:
169+
tool_conf = next((t for t in model_tool_config if t["id"] == tool_id), None)
170+
if tool_conf:
171+
built_in_tools.append(tool_conf["tool_config"])
172+
175173

176-
logger.log("INFO", "llm_request", f"Initiating LLM call to {model_name}", metadata={"request": get_sanitized_request_data(req)})
174+
logger.log("INFO", "llm_request", f"Initiating LLM call to {request.provider}/{request.model}", metadata={"request": get_sanitized_request_data(req)})
177175

178-
response = await litellm.acompletion(
179-
model=model_name,
176+
content, thoughts = await call_llm(
177+
provider=request.provider,
178+
model=request.model,
180179
messages=messages,
181-
**request.parameters
180+
parameters=request.parameters,
181+
tools=built_in_tools if built_in_tools else None
182182
)
183183

184184
# Update ticket with success
185185
ticket_data.update({
186186
"status": "completed",
187187
"completed_at": datetime.utcnow().isoformat(),
188188
"result": {
189-
"content": response.choices[0].message.content,
190-
"model": response.model,
191-
"usage": response.usage.dict() if response.usage else None
189+
"content": content,
190+
"thoughts": thoughts,
191+
"model": f"{request.provider}/{request.model}",
192+
# Usage data is not consistently available across both litellm APIs, so omitting for now
192193
}
193194
})
194195
db_service.save("tickets", ticket_id, ticket_data)
@@ -736,7 +737,7 @@ def build_cors_headers(req, *, origin_override=None):
736737
# "access-control-allow-credentials": "true",
737738
}
738739

739-
@https_fn.on_request(memory=1024)
740+
@https_fn.on_request(memory=2048, timeout_sec=540)
740741
def api(req: https_fn.Request):
741742
"""
742743
An HTTPS Cloud Function that wraps the FastAPI application.

webapp/packages/api/user-service/models/agent.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# webapp/packages/api/user-service/models/agent.py
12
from pydantic import BaseModel, Field
23
from pydantic.config import ConfigDict
34
from pydantic.alias_generators import to_camel
@@ -21,12 +22,15 @@ class GenerateCodeRequest(BaseModel):
2122
invokable_models: Optional[List[ProviderConfig]] = Field(None, alias="invokableModels")
2223
swagger_specs: Optional[List[SwaggerSpec]] = Field(None, alias="swaggerSpecs")
2324
gofannon_agents: Optional[List[str]] = Field(None, alias="gofannonAgents")
25+
built_in_tools: Optional[List[str]] = Field(default_factory=list, alias="builtInTools")
2426
model_config = ConfigDict(populate_by_name=True)
2527

2628
class GenerateCodeResponse(BaseModel):
2729
code: str
2830
friendly_name: str = Field(..., alias="friendlyName")
2931
docstring: str
32+
thoughts: Optional[Any] = None
33+
3034

3135
model_config = ConfigDict(populate_by_name=True)
3236

@@ -42,6 +46,8 @@ class CreateAgentRequest(BaseModel):
4246
output_schema: Optional[Dict[str, Any]] = Field(..., alias="outputSchema")
4347
invokable_models: Optional[List[ProviderConfig]] = Field(None, alias="invokableModels")
4448
gofannon_agents: Optional[List[str]] = Field(default_factory=list, alias="gofannonAgents")
49+
composer_thoughts: Optional[Any] = Field(None, alias="composerThoughts")
50+
4551

4652
model_config = ConfigDict(
4753
populate_by_name=True,
@@ -82,3 +88,4 @@ class DeployedApi(BaseModel):
8288
input_schema: Dict[str, Any] = Field(..., alias="inputSchema")
8389
output_schema: Dict[str, Any] = Field(..., alias="outputSchema")
8490
model_config = ConfigDict(populate_by_name=True)
91+

webapp/packages/api/user-service/models/chat.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from pydantic import BaseModel, Field
1+
# webapp/packages/api/user-service/models/chat.py
2+
from pydantic import BaseModel, Field, ConfigDict
23
from typing import Optional, Dict, Any, List
34
from enum import Enum
45
from datetime import datetime
@@ -19,6 +20,9 @@ class ChatRequest(BaseModel):
1920
model: str = "gpt-3.5-turbo"
2021
parameters: Dict[str, Any] = {} # Renamed from 'config' to 'parameters'
2122
stream: bool = False
23+
built_in_tools: Optional[List[str]] = Field(default_factory=list, alias="builtInTools")
24+
model_config = ConfigDict(populate_by_name=True)
25+
2226

2327
class ChatResponse(BaseModel):
2428
ticket_id: str

0 commit comments

Comments
 (0)