1111import logging
1212import os
1313from typing import TYPE_CHECKING , Any , Callable , Dict , List , Optional , Type , cast
14+ import importlib .util as _importlib_util
1415
15- # Attempt to import OpenAI client
16- try :
17- from openai import AsyncOpenAI , OpenAI
18- from openai .types .chat import ChatCompletionMessage , ChatCompletionToolParam
19- from openai .types .chat .chat_completion_message_tool_call import (
20- ChatCompletionMessageToolCall ,
21- )
22-
23- OPENAI_AVAILABLE = True
24- except ImportError :
25- OPENAI_AVAILABLE = False
26- # Define dummy types if openai is not installed, to avoid runtime errors on load
27- from typing import Any , Dict , List , Optional , Union
28-
29- # Use simple class definitions for runtime and type checking
30- class OpenAI :
31- def __init__ (self , ** kwargs : Any ) -> None :
32- pass
16+ # Determine OpenAI availability without importing symbols for typing
17+ OPENAI_AVAILABLE = _importlib_util .find_spec ("openai" ) is not None
3318
34- class AsyncOpenAI :
35- def __init__ (self , ** kwargs : Any ) -> None :
36- pass
19+ # Expose AsyncOpenAI/OpenAI at module level for tests/patching, even if we import lazily elsewhere
20+ if OPENAI_AVAILABLE :
21+ try :
22+ from openai import AsyncOpenAI as AsyncOpenAI , OpenAI as OpenAI # type: ignore[import-not-found]
23+ except Exception :
3724
38- class ChatCompletionMessage :
39- content : str = ""
40- role : str = "assistant"
25+ class AsyncOpenAI : # type: ignore[no-redef]
26+ def __init__ ( self , ** _ : Any ) -> None :
27+ pass
4128
42- class ChatCompletionToolParam :
43- pass
29+ class OpenAI : # type: ignore[no-redef]
30+ def __init__ (self , ** _ : Any ) -> None :
31+ pass
32+ else :
4433
45- class ChatCompletionMessageToolCall :
46- pass
34+ class AsyncOpenAI : # type: ignore[no-redef]
35+ def __init__ (self , ** _ : Any ) -> None :
36+ pass
37+
38+ class OpenAI : # type: ignore[no-redef]
39+ def __init__ (self , ** _ : Any ) -> None :
40+ pass
4741
4842
4943# Max steps for the inner loop within a single user turn
@@ -71,17 +65,19 @@ def __init__(self, task_definition: TaskDefinitionModel):
7165 self .logger = logging .getLogger (f"Orchestrator.{ self .task_definition .name } " )
7266 self .logger .setLevel (logging .DEBUG ) # Ensure debug logs are processed
7367 self .logger .info (f"Orchestrator initialized for task: { self .task_definition .name } " )
74- self ._openai_client : Optional [AsyncOpenAI ] = None
68+ # Use Any here to avoid pyright stubs mismatches across openai versions
69+ self ._openai_client : Optional [Any ] = None
7570
7671 def _initialize_openai_client (self ):
7772 """Initializes the AsyncOpenAI client if available and not already initialized."""
7873 if not OPENAI_AVAILABLE :
7974 self .logger .warning ("OpenAI library not available. Cannot use OpenAI models." )
8075 return
8176 if self ._openai_client is None :
82- # Consider adding error handling for missing API key
8377 try :
84- self ._openai_client = AsyncOpenAI (api_key = os .environ .get ("OPENAI_API_KEY" ))
78+ from openai import AsyncOpenAI # type: ignore[import-not-found]
79+
80+ self ._openai_client = AsyncOpenAI (api_key = os .environ .get ("OPENAI_API_KEY" )) # type: ignore[call-arg]
8581 self .logger .info ("AsyncOpenAI client initialized." )
8682 except Exception as e :
8783 self .logger .error (f"Failed to initialize AsyncOpenAI client: { e } " )
@@ -94,7 +90,9 @@ def _initialize_fireworks_client(self):
9490 return
9591 if self ._openai_client is None :
9692 try :
97- self ._openai_client = AsyncOpenAI (
93+ from openai import AsyncOpenAI # type: ignore[import-not-found]
94+
95+ self ._openai_client = AsyncOpenAI ( # type: ignore[call-arg]
9896 api_key = os .environ .get ("FIREWORKS_API_KEY" ),
9997 base_url = "https://api.fireworks.ai/inference/v1" ,
10098 )
@@ -469,18 +467,20 @@ async def execute_task_poc(self, sample_data: Optional[Dict[str, Any]] = None) -
469467 # Initialize the episode resource with sample data if provided
470468 if sample_data :
471469 self .logger .info (f"Initializing episode resource with sample data: { sample_data } " )
472- if hasattr (episode_resource , "initialize" ):
473- await episode_resource .initialize (** sample_data )
470+ initializer = getattr (episode_resource , "initialize" , None )
471+ if callable (initializer ):
472+ await initializer (** sample_data ) # type: ignore[misc]
474473 else :
475474 self .logger .warning (
476475 f"Episode resource { type (episode_resource ).__name__ } does not have initialize method"
477476 )
478477
479478 # Get initial state for injection into first prompt (for HTTP rollout)
480479 initial_state_description = None
481- if hasattr (episode_resource , "get_initial_state_description" ):
480+ get_init_state = getattr (episode_resource , "get_initial_state_description" , None )
481+ if callable (get_init_state ):
482482 try :
483- initial_state_description = await episode_resource . get_initial_state_description ()
483+ initial_state_description = await get_init_state () # type: ignore[misc]
484484 self .logger .info ("Retrieved initial state description for first prompt" )
485485 except Exception as e :
486486 self .logger .warning (f"Failed to get initial state description: { e } " )
@@ -577,21 +577,21 @@ async def execute_task_poc(self, sample_data: Optional[Dict[str, Any]] = None) -
577577 ) # Get adapters for execution
578578
579579 # Format tools for OpenAI API (should be done once per user turn, or if tools change)
580- openai_tools : List [ChatCompletionToolParam ] = []
580+ openai_tools : List [Dict [ str , Any ] ] = []
581581 if OPENAI_AVAILABLE :
582582 # First add tools from the resource
583583 for spec in resource_tool_specs :
584584 # Ensure spec has the structure with name and parameters
585585 if "name" in spec and "parameters" in spec :
586586 openai_tools .append (
587- ChatCompletionToolParam (
588- type = "function" ,
589- function = {
587+ {
588+ " type" : "function" ,
589+ " function" : {
590590 "name" : spec ["name" ],
591591 "description" : spec .get ("description" , "" ),
592- "parameters" : spec ["parameters" ], # Assuming this matches OpenAI schema
592+ "parameters" : spec ["parameters" ], # Assuming OpenAI-compatible schema
593593 },
594- )
594+ }
595595 )
596596 else :
597597 self .logger .warning (f"Skipping tool spec due to missing name/parameters: { spec } " )
@@ -605,14 +605,14 @@ async def execute_task_poc(self, sample_data: Optional[Dict[str, Any]] = None) -
605605 registry_tools = self .tools_module .R .get_openai_tools ()
606606 for tool_spec in registry_tools :
607607 openai_tools .append (
608- ChatCompletionToolParam (
609- type = "function" ,
610- function = {
608+ {
609+ " type" : "function" ,
610+ " function" : {
611611 "name" : tool_spec ["name" ],
612612 "description" : tool_spec .get ("description" , "" ),
613613 "parameters" : tool_spec ["parameters" ],
614614 },
615- )
615+ }
616616 )
617617 else :
618618 self .logger .warning ("OpenAI not available, cannot format tools for API." )
@@ -642,6 +642,7 @@ async def execute_task_poc(self, sample_data: Optional[Dict[str, Any]] = None) -
642642 if not self ._openai_client :
643643 raise Exception ("OpenAI client not initialized" )
644644
645+ # type: ignore[reportUnknownMemberType]
645646 response = await self ._openai_client .chat .completions .create (
646647 model = agent_model_name ,
647648 messages = conversation_messages , # type: ignore
0 commit comments