66
77from pydantic_ai import Agent , RunContext
88from pydantic_ai .usage import UsageLimits
9- from pydantic_ai .models .openai import OpenAIResponsesModel , OpenAIChatModel
9+ from pydantic_ai .models .openai import OpenAIResponsesModel
1010from pydantic_ai .providers .openai import OpenAIProvider
1111from pydantic_ai .messages import BinaryContent
1212
1313from ai_agent .generator .prompts import get_agent_system_prompt
14- from ai_agent .generator .schema import ToolSelection , Conversation , ConversationStatus
14+ from ai_agent .generator .schema import ToolSelection
1515from ai_agent .utils .config import get_config
1616from .models import AgentToolSelection , ToolRunLog
1717from .tools .repo_info_tool import tool_repo_summary , RepoSummaryInput
4444 base_url = agent_model_config .base_url ,
4545 api_key = api_key ,
4646 )
47- openai_model = OpenAIChatModel (
48- model_name = agent_model_config .name ,
49- provider = provider ,
50- )
5147else :
5248 provider = OpenAIProvider (api_key = api_key )
5349
@@ -164,51 +160,38 @@ async def search_alternative(
164160
165161@agent .tool (retries = 2 , prepare = cap_prepare )
166162@limit_tool_calls ("repo_info" , cap = 12 )
167- async def repo_info (ctx : RunContext [AgentState ], url : str , tool_name : str = None ) -> dict :
163+ async def repo_info (ctx : RunContext [AgentState ], url : str ) -> dict :
168164 """
169165 Fetch a short summary of a GitHub repository.
170166
171167 Non-GitHub URLs are ignored; the tool returns a small dict noting
172- that it was skipped. If a tool_name is provided and the URL is not
173- a GitHub URL, the tool will attempt to look up the GitHub URL from
174- the catalog.
175-
176- Args:
177- url: Repository URL or GitHub owner/repo format
178- tool_name: Optional tool name to look up in catalog if URL is not GitHub
168+ that it was skipped.
179169 """
180170 norm_url = coerce_github_url_or_none (url )
181-
182- # If URL is not a GitHub URL and tool_name is provided, try catalog lookup
183- if not norm_url and tool_name :
184- log .info (f"Non-GitHub URL provided, tool_name={ tool_name } , attempting catalog lookup" )
185- # The tool_repo_summary will handle the catalog lookup
186- norm_url = url # Pass through, tool_repo_summary will handle it
187- elif not norm_url :
171+ if not norm_url :
188172 payload = {
189173 "tool" : "repo_info" ,
190174 "url" : url ,
191175 "skipped" : True ,
192176 "reason" : "NON_GITHUB_URL" ,
193- "hint" : "Pass a GitHub repo URL or 'owner/repo' to repo_info(url). Optionally provide tool_name for catalog lookup. " ,
177+ "hint" : "Pass a GitHub repo URL or 'owner/repo' to repo_info(url)." ,
194178 "timestamp" : datetime .now ().isoformat ()
195179 }
196180 ctx .deps .tool_calls .append (payload )
197181 return {k : v for k , v in payload .items () if k != "tool" }
198182
199183 try :
200- out = await tool_repo_summary (RepoSummaryInput (url = norm_url , tool_name = tool_name ))
184+ out = await tool_repo_summary (RepoSummaryInput (url = norm_url ))
201185 except Exception as e :
202186 ctx .deps .tool_calls .append (
203- {"tool" : "repo_info" , "url" : norm_url , "tool_name" : tool_name , " error" : str (e ), "timestamp" : datetime .now ().isoformat ()}
187+ {"tool" : "repo_info" , "url" : norm_url , "error" : str (e ), "timestamp" : datetime .now ().isoformat ()}
204188 )
205189 raise
206190
207191 ctx .deps .tool_calls .append (
208192 {
209193 "tool" : "repo_info" ,
210194 "url" : norm_url ,
211- "tool_name" : tool_name ,
212195 "truncated" : getattr (out , "truncated" , False ),
213196 "timestamp" : datetime .now ().isoformat ()
214197 }
@@ -261,7 +244,6 @@ def run_agent(
261244 image_bytes : bytes | None = None ,
262245 model : str | None = None ,
263246 base_url : str | None = None ,
264- api_key_env : str | None = None ,
265247 top_k : int | None = None ,
266248 num_choices : int | None = None ,
267249 image_metadata : str | None = None ,
@@ -333,19 +315,30 @@ def run_agent(
333315
334316 # When model is provided from UI, base_url comes with it (can be None for OpenAI)
335317 if model :
336- # Use api_key_env from config if provided, otherwise default to OPENAI_API_KEY
337- key_env_name = api_key_env if api_key_env else "OPENAI_API_KEY"
338- runtime_api_key = os .getenv (key_env_name )
339- if not runtime_api_key :
340- raise ValueError (f"{ key_env_name } not found in environment. Cannot use this model." )
341- effective_base_url = base_url # Can be None for OpenAI
342- log .info (f"✓ Using { key_env_name } for model { effective_model } " )
343- log .debug (f"{ key_env_name } starts with: { runtime_api_key [:10 ] if runtime_api_key else 'NONE' } ... (len={ len (runtime_api_key ) if runtime_api_key else 0 } )" )
318+ if base_url and "inference.rcp.epfl.ch" in base_url :
319+ runtime_api_key = os .getenv ("EPFL_API_KEY" )
320+ if not runtime_api_key :
321+ raise ValueError ("EPFL_API_KEY not found. Cannot use EPFL models without VPN and API key." )
322+ effective_base_url = base_url
323+ log .info ("✓ Using EPFL_API_KEY for EPFL inference server" )
324+ else :
325+ runtime_api_key = os .getenv ("OPENAI_API_KEY" )
326+ if not runtime_api_key :
327+ raise ValueError ("OPENAI_API_KEY not found. Cannot use OpenAI models." )
328+ effective_base_url = base_url # None for OpenAI
329+ log .info ("✓ Using OPENAI_API_KEY for OpenAI endpoint" )
344330 else :
345- # No model override - use config defaults
346331 effective_base_url = agent_model_config .base_url
347- runtime_api_key = api_key # Already loaded from config at startup
348- log .info (f"✓ Using API key from config for model { effective_model } " )
332+ if effective_base_url and "inference.rcp.epfl.ch" in effective_base_url :
333+ runtime_api_key = os .getenv ("EPFL_API_KEY" )
334+ if not runtime_api_key :
335+ raise ValueError ("EPFL_API_KEY not found" )
336+ log .info ("✓ Using EPFL_API_KEY from config" )
337+ else :
338+ runtime_api_key = os .getenv ("OPENAI_API_KEY" )
339+ if not runtime_api_key :
340+ raise ValueError ("OPENAI_API_KEY not found" )
341+ log .info ("✓ Using OPENAI_API_KEY from config" )
349342
350343 # Log runtime configuration
351344 endpoint_display = effective_base_url if effective_base_url else "api.openai.com"
@@ -369,13 +362,7 @@ def run_agent(
369362 base_url = effective_base_url ,
370363 api_key = runtime_api_key ,
371364 )
372-
373- # Use OpenAIModel (chat/completions) for custom endpoints, OpenAIResponsesModel for default OpenAI
374- if effective_base_url :
375- log .info ("Using OpenAIChatModel (chat/completions API) for custom endpoint" )
376- runtime_model = OpenAIChatModel (model_name = effective_model , provider = runtime_provider )
377- else :
378- runtime_model = OpenAIResponsesModel (model_name = effective_model , provider = runtime_provider )
365+ runtime_model = OpenAIResponsesModel (model_name = effective_model , provider = runtime_provider )
379366
380367 agent_instance = Agent (
381368 model = runtime_model ,
@@ -429,51 +416,27 @@ def run_agent(
429416 user_prompt = prompt
430417
431418 # ---- 6) Run the agent --------------------------------------------------
432- try :
433- run_result = agent_instance .run_sync (
434- user_prompt ,
435- deps = deps ,
436- output_type = ToolSelection ,
437- usage_limits = UsageLimits (tool_calls_limit = 20 ),
438- )
439- result = run_result .output
440-
441- log .info (f"✅ Agent execution complete - choices returned: { len (result .choices )} " )
419+ run_result = agent_instance .run_sync (
420+ user_prompt ,
421+ deps = deps ,
422+ output_type = ToolSelection ,
423+ usage_limits = UsageLimits (tool_calls_limit = 20 ),
424+ )
425+ result = run_result .output
442426
443- # Log usage (helpful, but may not explicitly expose image-specific counters)
444- if run_result .usage :
445- usage = run_result .usage ()
446- log .info (
447- f"📊 Usage: total_tokens={ usage .total_tokens } , "
448- f"input_tokens={ usage .input_tokens } , output_tokens={ usage .output_tokens } "
449- )
427+ log .info (f"✅ Agent execution complete - choices returned: { len (result .choices )} " )
450428
451- # Warn if using non-OpenAI endpoint with images
452- if image_bytes and effective_base_url :
453- log .warning ("⚠️ Using custom endpoint - confirm the selected model supports vision." )
429+ # Log usage (helpful, but may not explicitly expose image-specific counters)
430+ if run_result .usage :
431+ usage = run_result .usage ()
432+ log .info (
433+ f"📊 Usage: total_tokens={ usage .total_tokens } , "
434+ f"request_tokens={ usage .request_tokens } , response_tokens={ usage .response_tokens } "
435+ )
454436
455- except Exception as e :
456- # Handle global tool quota limit (UsageLimitExceeded) and other errors gracefully
457- error_msg = str (e )
458- log .warning (f"⚠️ Agent execution encountered an error: { error_msg } " )
459-
460- # Check if this is a usage limit error (global tool quota)
461- if "UsageLimitExceeded" in str (type (e ).__name__ ) or "tool_calls_limit" in error_msg .lower ():
462- log .warning ("Global tool call quota reached - continuing with partial results" )
463-
464- result = ToolSelection (
465- conversation = Conversation (
466- status = ConversationStatus .COMPLETE ,
467- context = "The agent reached the maximum number of tool calls allowed. Please try a more specific query or break down your request into smaller parts." ,
468- question = None ,
469- options = None
470- ),
471- choices = [],
472- explanation = "Tool call limit reached during execution. Try refining your query." ,
473- reason = None
474- )
475- else :
476- raise
437+ if image_bytes and ("inference.rcp.epfl.ch" in endpoint_display ):
438+ log .warning ("⚠️ Using EPFL inference server - confirm the selected model supports vision on that endpoint." )
439+ log .warning (" OpenAI billing/dashboard may not reflect image usage when using a non-OpenAI endpoint." )
477440
478441 # ---- 7) Convert raw tool call records into ToolRunLog objects ----------
479442 for tc in getattr (deps , "tool_calls" , []):
0 commit comments