1010import litellm
1111import pydantic
1212from litellm import (
13- ChatCompletionTextObject ,
1413 Choices ,
1514 Message ,
1615 OpenAIChatCompletionToolParam ,
2019from litellm .types .utils import ModelResponse
2120
2221from effectful .handlers .llm import Template , Tool
23- from effectful .handlers .llm .encoding import type_to_encodable_type
22+ from effectful .handlers .llm .encoding import Encodable
2423from effectful .ops .semantics import fwd , handler
2524from effectful .ops .syntax import ObjectInterpretation , defop , implements
2625
@@ -75,21 +74,25 @@ def completion(*args, **kwargs) -> Any:
7574 return litellm .completion (* args , ** kwargs )
7675
7776
78- def parameter_model (tool : Tool ) -> type [pydantic .BaseModel ]:
79- fields = {
80- name : type_to_encodable_type (param .annotation ).t
77+ def parameter_model (
78+ tool : Tool , ctx : Mapping [str , Any ] | None = None
79+ ) -> type [pydantic .BaseModel ]:
80+ fields : dict [str , Any ] = {
81+ name : Encodable .define (param .annotation , ctx ).enc
8182 for name , param in tool .__signature__ .parameters .items ()
8283 }
8384 parameter_model = pydantic .create_model (
8485 "Params" ,
8586 __config__ = {"extra" : "forbid" },
86- ** fields , # type: ignore
87+ ** fields ,
8788 )
8889 return parameter_model
8990
9091
91- def function_definition (tool : Tool ) -> OpenAIChatCompletionToolParam :
92- param_model = parameter_model (tool )
92+ def function_definition (
93+ tool : Tool , ctx : Mapping [str , Any ] | None = None
94+ ) -> OpenAIChatCompletionToolParam :
95+ param_model = parameter_model (tool , ctx )
9396 response_format = litellm .utils .type_to_response_format_param (param_model )
9497 description = tool .__default__ .__doc__
9598 assert response_format is not None
@@ -114,25 +117,25 @@ def call_with_json_args(
114117
115118 """
116119 sig = tool .__signature__
117- param_model = parameter_model (tool )
120+ param_model = parameter_model (tool , context )
118121 try :
119122 # build dict of raw encodable types U
120123 raw_args = param_model .model_validate_json (json_str )
121124
122125 # use encoders to decode Us to python types T
123126 params : dict [str , Any ] = {
124- param_name : type_to_encodable_type (
125- sig .parameters [param_name ].annotation
126- ).decode (getattr (raw_args , param_name ), context )
127+ param_name : Encodable . define (
128+ sig .parameters [param_name ].annotation , context
129+ ).decode (getattr (raw_args , param_name ))
127130 for param_name in raw_args .model_fields_set
128131 }
129132
130133 # call tool with python types
131134 result = tool (** params )
132135
133136 # serialize back to U using encoder for return type
134- encoded_ty = type_to_encodable_type (sig .return_annotation )
135- encoded_value = encoded_ty .encode (result , context )
137+ encoded_ty = Encodable . define (sig .return_annotation , context )
138+ encoded_value = encoded_ty .encode (result )
136139
137140 # serialise back to Json
138141 return encoded_ty .serialize (encoded_value )
@@ -149,8 +152,12 @@ def compute_response(template: Template, model_input: list[Any]) -> ModelRespons
149152 ret_type = template .__signature__ .return_annotation
150153 tools = template .tools
151154
152- tool_schemas = [function_definition (t ) for t in tools .values ()]
153- response_encoding_type : type | None = type_to_encodable_type (ret_type ).t
155+ tool_schemas = [
156+ function_definition (t , template .__context__ ) for t in tools .values ()
157+ ]
158+ response_encoding_type : type | None = Encodable .define (
159+ ret_type , template .__context__
160+ ).enc
154161 if response_encoding_type == str :
155162 response_encoding_type = None
156163
@@ -202,18 +209,18 @@ def decode_response[**P, T](template: Callable[P, T], response: ModelResponse) -
202209 assert result_str
203210
204211 ret_type = template .__signature__ .return_annotation
205- encodable_ty = type_to_encodable_type (ret_type )
212+ encodable_ty = Encodable . define (ret_type , template . __context__ )
206213
207- if encodable_ty .t == str :
214+ if encodable_ty .enc == str :
208215 # if encoding as a type, value is just directly what the llm returned
209- value = result_str
216+ value : Any = result_str
217+ return typing .cast (T , encodable_ty .decode (value ))
210218 else :
211- Result = pydantic .create_model ("Result" , value = encodable_ty .t )
219+ Result = pydantic .create_model ("Result" , value = encodable_ty .enc )
212220 result = Result .model_validate_json (result_str )
213221 assert isinstance (result , Result )
214- value = result .value # type: ignore
215-
216- return encodable_ty .decode (value , template .__context__ ) # type: ignore
222+ value = getattr (result , "value" )
223+ return typing .cast (T , encodable_ty .decode (value ))
217224
218225
219226@defop
@@ -229,29 +236,16 @@ def format_model_input[**P, T](
229236 # encode arguments
230237 arguments = {}
231238 for param in bound_args .arguments :
232- encoder = type_to_encodable_type (
233- template .__signature__ .parameters [param ].annotation
239+ encoder = Encodable . define (
240+ template .__signature__ .parameters [param ].annotation , template . __context__
234241 )
235- encoded = encoder .encode (bound_args .arguments [param ], template . __context__ )
242+ encoded = encoder .encode (bound_args .arguments [param ])
236243 arguments [param ] = encoder .serialize (encoded )
237244
238245 prompt = _OpenAIPromptFormatter ().format_as_messages (
239246 template .__prompt_template__ , ** arguments
240247 )
241248
242- # install prefix if the return type has a return annotation
243- ret_type = template .__signature__ .return_annotation
244- origin = typing .get_origin (ret_type )
245- ret_type = ret_type if origin is None else origin
246- ret_type_encoder = type_to_encodable_type (ret_type )
247- prompt_prefix = ret_type_encoder .encoding_instructions ()
248-
249- if prompt_prefix :
250- prefix : list [ChatCompletionTextObject ] = [
251- {"type" : "text" , "text" : prompt_prefix }
252- ]
253- prompt = prefix + prompt
254-
255249 # Note: The OpenAI api only seems to accept images in the 'user' role. The
256250 # effect of different roles on the model's response is currently unclear.
257251 messages = [{"type" : "message" , "content" : prompt , "role" : "user" }]
0 commit comments