
    WiLc                       d Z ddlmZ ddlZddlZddlZddlmZmZm	Z	 ddl
mZmZmZmZmZmZmZmZmZ ddlmZ ddlmZmZmZ ddlmZmZmZ erdd	lm Z!  ejD                  d
      Z# edd      Z$ ede      Z% ed      Z&ddZ'	 	 	 	 d dZ(	 	 	 	 d!dZ)	 	 	 	 	 	 d"dZ*d#dZ+d$dZ,d%dZ- G d dee$         Z.d&dZ/d Z0d'dZ1d(dZ2d)dZ3y)*zwCore utilities for instructor library.

This module contains generic utility functions that are not provider-specific.
    )annotationsN)AsyncGenerator	GeneratorIterable)	TYPE_CHECKINGAnyCallableGenericUnionTypeVarcastget_args
get_origin)CompletionUsage)ChatCompletionChatCompletionMessageChatCompletionMessageParam)	BaseModelValidationErrorcreate_modelUsage
instructorR_coT)	covariantT_Model)boundTc                v    | j                  d      }| j                  d      }|dk7  r|dk7  r
| ||dz    }|S | }|S )ai  
    Extract JSON from a string that may contain extra text.

    The function looks for the first '{' and the last '}' in the string and
    returns the content between them, inclusive. If no braces are found,
    the original string is returned.

    Args:
        content: The string that may contain JSON

    Returns:
        The extracted JSON string
    {}   )findrfind)contentfirst_brace
last_bracejson_contents       a/home/sietch6/trending-topics-pipeline/venv/lib/python3.12/site-packages/instructor/utils/core.pyextract_json_from_codeblockr+   +   sT     ,,s#Ks#JbZ2-{Z!^<      c              #  n  K   d}d}d}d}d}g }g }g }| D ]  }	|	D ]  }
|s+|
dk(  r&|j                  |
       t        |      dk(  rd}d}g }1t        |      dkD  r|
dk7  rg }|rG|sE|
dk(  r|dz  }|dk(  rd}d}^|dkD  rd}|
dk(  r$d}|j                  d       |j                  |
       |r|
dk(  r|s| }n|
d	k(  r|rd}|j                  |
       d}|r,|s*|
dk(  r|dz  }|dk(  rd}|D ]  }|  g }d} |dkD  rd}|sS|
dk(  r|j                  d       n<|
d
k(  r7|r5|j                          |s#|j                  |
       |D ]  }|  g }d} B|j                  |
       O|rS|rW|
dk(  s^d}|j                  d       |j                  |
         |r|r|D ]  }|  yyyw)aJ  
    Extract JSON from a stream of chunks, handling JSON in code blocks.

    This optimized version extracts JSON from markdown code blocks or plain JSON
    by implementing a state machine approach.

    The state machine tracks several states:
    - Whether we're inside a code block (```json ... ```)
    - Whether we've started tracking a JSON object
    - Whether we're inside a string literal
    - The stack of open braces to properly identify the JSON structure

    Args:
        chunks: An iterable of string chunks

    Yields:
        Characters within the JSON object
    Fr   `   Tr#   r    "\r!   Nappendlenpopchunksin_codeblockcodeblock_delimiter_countjson_started	in_stringescape_nextbrace_stackbuffercodeblock_bufferchunkcharcs               r*   extract_json_from_streamrC   D   s`    , L !LIKKF  X$ W	$DDCK ''-'(A-#'L01-')$%&*ts{#%  L3;-2-0A5',451.2 .
 3;#'L&&s+MM$' 3;{$-IT\i"&KMM$'"'K  	s{1Q6149+0L%+ (&'(%'F+0L! 2Q6451 !s{#**3/#)*"MM$/%+ (&'(%'F+0L! d#  #""3'd#oW	$X$v  	AG	 |s   E*F5.F52F59<F5c                 K   d}d}d}d}d}g }g }g }| 2 3 d{   }	|	D ]  }
|s+|
dk(  r&|j                  |
       t        |      dk(  rd}d}g }1t        |      dkD  r|
dk7  rg }|rG|sE|
dk(  r|dz  }|dk(  rd}d}^|dkD  rd}|
dk(  r$d}|j                  d       |j                  |
       |r|
d	k(  r|s| }n|
d
k(  r|rd}|j                  |
       d}|r-|s+|
dk(  r|dz  }|dk(  rd}|D ]  }| 	 g }d} |dkD  rd}|sT|
dk(  r|j                  d       n=|
dk(  r8|r6|j                          |s$|j                  |
       |D ]  }| 	 g }d} I|j                  |
       Q|rU|rY|
dk(  s`d}|j                  d       |j                  |
        7 6 |r|r|D ]  }| 	 yyyw)a^  
    Extract JSON from an async stream of chunks, handling JSON in code blocks.

    This optimized version extracts JSON from markdown code blocks or plain JSON
    by implementing a state machine approach.

    The state machine tracks several states:
    - Whether we're inside a code block (```json ... ```)
    - Whether we've started tracking a JSON object
    - Whether we're inside a string literal
    - The stack of open braces to properly identify the JSON structure

    Args:
        chunks: An async generator yielding string chunks

    Yields:
        Characters within the JSON object
    Fr   Nr.   r/   Tr#   r    r0   r1   r!   r2   r6   s               r*   extract_json_from_stream_asyncrE      si    , L !LIKKF  X$ X$e W	$DDCK ''-'(A-#'L01-')$%&*ts{#%  L3;-2-0A5',451.2 .
 3;#'L&&s+MM$' 3;{$-IT\i"&KMM$'"'K  	s{1Q6149+0L%+ (&'(%'F+0L! 2Q6451 !s{#**3/#)*"MM$/%+ (&'(%'F+0L! d#  #""3'd#oW	$X$vv  	AG	 |s8   G F,F)F,EG 5G 9G  )G )F,,G c                   | y t        | dd       }t        |t              rSt        |t              rB|xj                  |j                  xs dz  c_        |xj                  |j                  xs dz  c_        |xj
                  |j
                  xs dz  c_        |j                  x}rZ|j                  x}rL|j                  xs d|j                  xs dz   |_        |j                  xs d|j                  xs dz   |_        |j                  x}rZ|j                  x}rL|j                  xs d|j                  xs dz   |_        |j                  xs d|j                  xs dz   |_
        || _        | S 	 ddlm} t        ||      rt        ||      r|j                  sd|_        |j                  sd|_        |xj                   |j                   xs dz  c_        |xj"                  |j"                  xs dz  c_        |xj                  |j                  xs dz  c_        |xj                  |j                  xs dz  c_        || _        | S t&        j)                  d       | S # t$        $ r Y "w xY w)Nusager   r   z<No compatible response.usage found, token usage not updated.)getattr
isinstanceOpenAIUsagecompletion_tokensprompt_tokenstotal_tokenscompletion_tokens_detailsaudio_tokensreasoning_tokensprompt_tokens_detailscached_tokensrG   anthropic.typesr   cache_creation_input_tokenscache_read_input_tokensinput_tokensoutput_tokensImportErrorloggerdebug)responsetotal_usageresponse_usagertdttdrpdtpdAnthropicUsages           r*   update_total_usagerc   F  sT    Xw5N.+.:k;3W%%)I)I)NQN%!!^%A%A%FQF!  N$?$?$D1D !;;;C;888C8 # 0 0 5A#:J:J:OaPC$'$8$8$=A$$)$C  "777C7444C4 # 0 0 5A#:J:J:OaPC!$!2!2!7aC<M<M<RQR SC$;nn5*;
 :::;766673$$(C(C(HqH$%%)E)E)JJ%33::?a3 //66;!/ )HNO LLOPO	  s   6CI& &	I21I2c                8   | j                   | j                  xs dd}t        | d      r"| j                  | j	                         d   |d<   t        | d      r| j
                  |d   rt        |d   t              smd}|d   D ]^  }t        |t              s|j                  d      }|dk(  r|j                  dd      }||z  }B|dk(  sH|j                  dd      }||z  }` ||d<   |dxx   t        j                  | j	                         d         z  cc<   |S )	zDumps a message to a dict, to be returned to the OpenAI API.
    Workaround for an issue with the OpenAI API, where the `tool_calls` field isn't allowed to be present in requests
    if it isn't used.
     roler&   
tool_callsfunction_callr&   typetextrefusal)rg   r&   hasattrrh   
model_dumpri   rI   strdictgetjsondumps)messageretresponse_messagecontent_messagemessage_typetext_contentrefusal_contents          r*   dump_messager{     s*    ??(b'C w%'*<*<*H#..0>L)!!-	N#i.#.$&#&y> 	<ot4#2#6#6v#>L#v-'6':':62'F(L8(%2*9*=*=i*L(O;(	< .C	NI$**W%7%7%9/%JKKJr,   c                    t        j                  |       }t        | d      r2| j                  } |xs t        j                  |       }t        | d      r2|S )zGReturns true if the callable is async, accounting for wrapped callables__wrapped__)inspectiscoroutinefunctionrm   r}   )funcis_coroutines     r*   is_asyncr     sP    ..t4L
$
&#Hw'B'B4'H $
& r,   c                `   | sg S t        |       }g }d}t        | dt        d|             D ])  \  }}t        |j	                  dd      t
              r'd} n |r|dkD  rt        d | dd D              }| D ]  }|j	                  dd	      }|j	                  dd      }|st        |t
              rd
|dg}|ra||d   d   k(  rV|r|d   dxx   d| z  cc<   bt        |t              r|d   d   j                  |       |d   d   j                  |       |j                  ||d        |S )a  
    Merge consecutive messages from the same role into a single message.

    This optimized version pre-allocates the result list and minimizes operations.

    Args:
        messages: List of message dictionaries to merge

    Returns:
        List of merged message dictionaries
    TN
   r&   re   Fc              3  \   K   | ]$  }t        |j                  d d      t               & yw)r&   re   N)rI   rq   ro   .0ms     r*   	<genexpr>z-merge_consecutive_messages.<locals>.<genexpr>  s"     WA*QUU9b%93?Ws   *,rg   userrk   )rj   rk   r"   z

rf   )
r4   	enumerateminrI   rq   ro   alllistextendr3   )	messagesmessage_countnew_messagesflat_string_ir   rt   rg   new_contents	            r*   merge_consecutive_messagesr     si    	 MML
 K8$<c"m&<=> A!%%	2.4K }r)WRTRUWW  H{{66*kk)R0 z+s;$*K@AK DL$4V$<<R +k]/CC+ k40 $Y/66{C !$Y/66{C + FG-H0 r,   c                       e Zd ZdZddZddZy)classpropertya  Descriptor for class-level properties.

    Examples:
        >>> from instructor.utils import classproperty

        >>> class MyClass:
        ...     @classproperty
        ...     def my_property(cls):
        ...         return cls

        >>> assert MyClass.my_property
    c                    || _         y N	cproperty)selfmethods     r*   __init__zclassproperty.__init__  s	    r,   c                $    | j                  |      S r   r   )r   instanceclss      r*   __get__zclassproperty.__get__  s    ~~c""r,   N)r   zCallable[[Any], R_co]returnNone)r   objectr   z	type[Any]r   r   )__name__
__module____qualname____doc__r   r    r,   r*   r   r     s     #r,   r   c                n    | sdgS | j                  dd      }t        |t              r|r|S dgS ||gS dgS )a  
    Extract content from a message and ensure it's returned as a list.

    This optimized version handles different message formats more efficiently.

    Args:
        message: A message in ChatCompletionMessageParam format

    Returns:
        The message content as a list
    re   r&   )rq   rI   r   )rt   r&   s     r*   get_message_contentr     sS     t kk)R(G '4 !w+t+ *G3333r,   c                 r    t        t        d      st        j                  t        _        d } | t        _        y)a   Disable URLs in Pydantic ValidationError messages.

    This function monkey-patches Pydantic's ValidationError.__str__ method
    to prevent URLs from being included in error messages. This is necessary
    because Pydantic reads the PYDANTIC_ERRORS_INCLUDE_URL environment variable
    at import time, not at validation time, so setting it later has no effect.

    The function works by storing the original __str__ method and replacing it
    with a version that filters out URLs from the error message.
    _original_strc                    t        j                  |       }g }|j                  d      D ]  }d|vs|j                  |        dj	                  |      S )N
zhttps://errors.pydantic.dev)r   r   splitr3   join)r   outputlineslines       r*   __str__z+disable_pydantic_error_url.<locals>.__str__%  sV     ..t4 LL& 	#D,D8T"	# yyr,   N)rm   r   r   r   )r   s    r*   disable_pydantic_error_urlr     s+     ?O4(7(?(?%	  &Or,   c                b    t        | t              xr t        | t              xr t	        | d      S )N__annotations__)rI   rj   
issubclassrp   rm   )r   s    r*   is_typed_dictr   4  s0    3 	,sD!	,C*+r,   c                    ddl m}  ||       S )z5Check if a type is a simple type that can be adapted.r   )is_simple_type)instructor.dsl.simple_typer   )typehint_is_simple_types     r*   r   r   <  s    L8$$r,   c                8   | yt        |       }|t        u rVt        |       rKt        |       }|r|d   nd}dd}| ||      rt        }n"ddlm} |j                  |       } t        |       }t        |       r`t        | dd      }t        | di       }t        t        t           t        |fi  |j                         D 	ci c]
  \  }}	||	df c}	}      } t        |       }|t        t        hv rdd	lm}
 t        |       }|r|d   t#        d
      |d   }t        |      r[t        t        t           t        t        |dd      fi t        |di       j                         D 	ci c]
  \  }}	||	df c}	}      } |
t        t        t           |            } t        |       rddlm} |j                  |       } ddlm}m} t+        j,                  |       rt/        | |      s
 ||       } | S t+        j,                  |       s ||       } | S c c}	}w c c}	}w )a  
    Prepares the response model for use in the API call.

    This function performs several transformations on the input response_model:
    1. If the response_model is None, it returns None.
    2. If it's a simple type, it wraps it in a ModelAdapter.
    3. If it's a TypedDict, it converts it to a Pydantic BaseModel.
    4. If it's an Iterable, it wraps the element type in an IterableModel.
    5. If it's not already a subclass of OpenAISchema, it applies the openai_schema decorator.

    Args:
        response_model (type[T] | None): The input response model to be prepared.

    Returns:
        type[T] | None: The prepared response model, or None if the input was None.
    Nr   c                    t        j                  |       rt        | t              ryt	        |       t
        u xr t        d t        |       D              S )NTc              3  j   K   | ]+  }t        j                  |      xr t        |t               - y wr   )r~   isclassr   r   r   s     r*   r   zAprepare_response_model.<locals>._is_model_type.<locals>.<genexpr>b  s-      2DE"?z!Y'??2s   13)r~   r   r   r   r   r   r   r   )ts    r*   _is_model_typez.prepare_response_model.<locals>._is_model_type_  sI    q!jI&>a=E) c 2IQRS2 / r,   )ModelAdapterr   TypedDictModelr   .)IterableModelzGresponse_model must be parameterized, e.g. list[User] or Iterable[User]   )OpenAISchemaopenai_schema)r   r   r   bool)r   r   r   r   r   r   __class_getitem__r   rH   r   rj   r   r   itemsr   instructor.dsl.iterabler   
ValueErrorprocessing.function_callsr   r   r~   r   r   )response_modeloriginargsinnerr   r   
model_namer   kvr   iterable_element_classr   r   s                 r*   prepare_response_modelr   C  s-   " 'F ~.8'QT	 !6F? *;;NKN/F ^$^Z9IJ
n.?DO+<;+<+<+>?41a1q#h;?
 'F(D!!9'tAwY  "&a/0%)Y2J@PQ %,24Er%%'	 Aq As8&" 'tDO=S'TUn%; &77G H ~&z.,/W&~6  __^,&~6] @(s   ?H&H)r&   ro   r   ro   )r7   zIterable[str]r   zGenerator[str, None, None])r7   AsyncGenerator[str, None]r   r   )r[   zT_Model | Noner\   zOpenAIUsage | AnthropicUsager   zT_Model | ChatCompletion | None)rt   r   r   r   )r   zCallable[..., Any]r   r   )r   list[dict[str, Any]]r   r   )rt   r   r   z	list[Any])r   r   )r   ztype[T]r   r   )r   type[T] | Noner   r   )4r   
__future__r   r~   rr   loggingcollections.abcr   r   r   typingr   r   r	   r
   r   r   r   r   r   openai.typesr   rJ   openai.types.chatr   r   r   pydanticr   r   r   rS   r   rb   	getLoggerrY   r   r   r   r+   rC   rE   rc   r{   r   r   r   r   r   r   r   r   r   r,   r*   <module>r      s  
 #    ? ?
 
 
 8 
 > = 7			<	(v&
)9
-CL2~~~B~%~~B66-6 %6rB9x#GDM #*46&>%dr,   