@@ -260,15 +260,19 @@ def call_assistant[T, U](
260260 includes the raw assistant message for retry handling.
261261 """
262262 tool_specs = {k : _function_model (t ) for k , t in tools .items ()}
263- response_model = pydantic .create_model (
264- "Response" , value = response_format .enc , __config__ = {"extra" : "forbid" }
263+ response_model = (
264+ response_format .enc
265+ if issubclass (response_format .enc , pydantic .BaseModel )
266+ else pydantic .create_model (
267+ "Response" , value = response_format .enc , __config__ = {"extra" : "forbid" }
268+ )
265269 )
266270
267271 messages = list (get_message_sequence ().values ())
268272 response : litellm .types .utils .ModelResponse = completion (
269273 model ,
270274 messages = list (messages ),
271- response_format = response_model ,
275+ response_format = response_model if response_format . enc is not str else None ,
272276 tools = list (tool_specs .values ()),
273277 ** kwargs ,
274278 )
@@ -291,17 +295,26 @@ def call_assistant[T, U](
291295 tool_calls .append (decoded_tool_call )
292296
293297 result = None
294- if not tool_calls :
298+ if not tool_calls and response_format . enc is not str :
295299 # return response
296300 serialized_result = message .get ("content" ) or message .get ("reasoning_content" )
297301 assert isinstance (serialized_result , str ), (
298302 "final response from the model should be a string"
299303 )
300304 try :
301305 raw_result = response_model .model_validate_json (serialized_result )
302- result = response_format .decode (raw_result .value ) # type: ignore
306+ result = response_format .decode (
307+ raw_result .value
308+ if not issubclass (response_format .enc , pydantic .BaseModel )
309+ else raw_result
310+ ) # type: ignore
303311 except (pydantic .ValidationError , TypeError , ValueError , SyntaxError ) as e :
304312 raise ResultDecodingError (e , raw_message = raw_message ) from e
313+ elif not tool_calls and response_format .enc is str :
314+ # if expecting a string result, return the raw content as the result
315+ content = message .get ("content" ) or message .get ("reasoning_content" )
316+ assert isinstance (content , str ), "Expected content to be a string"
317+ result = content
305318
306319 return (raw_message , tool_calls , result )
307320
@@ -387,7 +400,36 @@ def flush_text() -> None:
387400@Operation .define
388401def call_system (template : Template ) -> collections .abc .Sequence [Message ]:
389402 """Get system instruction message(s) to prepend to all LLM prompts."""
390- return ()
403+
404+ assert inspect .getdoc (type (template )) is not None
405+
406+ system_prompt = inspect .cleandoc (f"""
407+ You are responsible for implementing the `Template` '{ template .__name__ } ' defined in the module source code below.
408+
409+ First, as background, here is the class-level documentation for the `Template` class::
410+
411+ { inspect .getdoc (type (template ))}
412+ """ )
413+
414+ try :
415+ system_prompt += inspect .cleandoc (f"""
416+ Here is the source code of the module defining the `Template` instance '{ template .__name__ } '::
417+
418+ { inspect .getsource (inspect .getmodule (template ))}
419+ """ )
420+ except (TypeError , OSError ):
421+ system_prompt += inspect .cleandoc (f"""
422+ The source code for the module defining '{ template .__name__ } ' is not available.
423+ Instead, here are the signature and docstring of '{ template .__name__ } '::
424+
425+ { template .__name__ } :: { template .__signature__ .format ()}
426+
427+ { inspect .cleandoc (template .__prompt_template__ )}
428+ """ )
429+
430+ msg = _make_message (dict (role = "system" , content = system_prompt ))
431+ append_message (msg )
432+ return (msg ,)
391433
392434
393435class RetryLLMHandler (ObjectInterpretation ):
0 commit comments