
    "i                       S r SSKJr  SSKrSSKrSSKrSSKJrJrJ	r	J
r
Jr  SSKJr  SSKJrJrJr  SSKJr  SSKJr  SS	KJr  SS
KJr  SSKJr  SSKJrJr  SSKJ r J!r!J"r"J#r#J$r$J%r%J&r&J'r'J(r(  SSKJ)r*  SSK+J,r,  SSK-J.r.  SSK/J0r0J1r1J2r2J3r3  SSK4J5r5J6r6J7r7  SSK8J9r9J:r:J;r;  SSK<J=r=  SSK>J?r?J@r@  SSKAJBrBJCrC  SSKDJErEJFrFJGrG  SSKHJIrIJJrJJKrK  SSKLJMrM  SSKNJIrO  SSKPJQrQJRrR  SSKSJTrT  SSKUJVrVJWrWJXrX  \R                  " \Z5      r[    S(S jr\        S)S  jr]    S*S! jr^    S+S" jr_S,S# jr`S-S$ jraS.S% jrb " S& S'\5      rcg)/a=  Ollama chat models.

**Input Flow (LangChain -> Ollama)**

`_convert_messages_to_ollama_messages()`:

- Transforms LangChain messages to `ollama.Message` format
- Extracts text content, images (base64), and tool calls

`_chat_params()`:

- Combines messages with model parameters (temperature, top_p, etc.)
- Attaches tools if provided
- Configures reasoning/thinking mode via `think` parameter
- Sets output format (raw, JSON, or JSON schema)

**Output Flow (Ollama -> LangChain)**

1. **Ollama Response**

Stream dictionary chunks containing:
- `message`: Dict with `role`, `content`, `tool_calls`, `thinking`
- `done`: Boolean indicating completion
- `done_reason`: Reason for completion (`stop`, `length`, `load`)
- Token counts/timing metadata

2. **Response Processing** (`_iterate_over_stream()`)

- Extracts content from `message.content`
- Parses tool calls into `ToolCall`s
- Separates reasoning content when `reasoning=True` (stored in `additional_kwargs`)
- Builds usage metadata from token counts

3. **LangChain Output** (`ChatGenerationChunk` -> `AIMessage`)

- **Streaming**: Yields `ChatGenerationChunk` with `AIMessageChunk` content
- **Non-streaming**: Returns `ChatResult` with complete `AIMessage`
- Tool calls attached to `AIMessage.tool_calls`
- Reasoning content in `AIMessage.additional_kwargs['reasoning_content']`
    )annotationsN)AsyncIteratorCallableIteratorMappingSequence)
itemgetter)AnyLiteralcast)uuid4)CallbackManagerForLLMRun)AsyncCallbackManagerForLLMRun)OutputParserException)LanguageModelInput)BaseChatModelLangSmithParams)		AIMessageAIMessageChunkBaseMessageChatMessageHumanMessageSystemMessageToolCallToolMessageis_data_content_block)content)UsageMetadata)	tool_call)JsonOutputKeyToolsParserJsonOutputParserPydanticOutputParserPydanticToolsParser)ChatGenerationChatGenerationChunk
ChatResult)RunnableRunnableMapRunnablePassthrough)BaseTool)convert_to_json_schemaconvert_to_openai_tool)TypeBaseModelis_basemodel_subclass)AsyncClientClientMessage)	BaseModelPrivateAttrmodel_validator)JsonSchemaValue)r2   )Selfis_typeddict)_convert_from_v1_to_ollama   )merge_auth_headersparse_url_with_authvalidate_modelc                v    U c  gU R                  S5      nU R                  S5      nUb  Ub  [        UUX-   S9$ g)z7Get usage metadata from Ollama generation info mapping.Nprompt_eval_count
eval_count)input_tokensoutput_tokenstotal_tokens)getr   )generation_infor@   rA   s      ^/var/www/html/dynamic-report/venv/lib/python3.13/site-packages/langchain_ollama/chat_models.py(_get_usage_metadata_from_generation_inforF   b   sW     .223FGL / 3 3L AMM$=%'%5
 	

     c                   [         R                  " U 5      $ ! [         R                   ae     [        R                  " U 5      s $ ! [
        [        4 a8  nU(       a	  U s SnAs $ SUS   S    SUS   S    SU 3n[        U5      UeSnAff = f[         a6  nU(       a  U s SnA$ SUS   S    SUS   S    SU 3n[        U5      UeSnAff = f)	aF  Attempt to parse a JSON string for tool calling.

It first tries to use the standard `json.loads`. If that fails, it falls
back to `ast.literal_eval` to safely parse Python literals, which is more
robust against models using single quotes or containing apostrophes.

Args:
    json_string: JSON string to parse.
    raw_tool_call: Raw tool call to include in error message.
    skip: Whether to ignore parsing errors and return the value anyways.

Returns:
    The parsed JSON string or Python literal.

Raises:
    OutputParserException: If the string is invalid and `skip=False`.
Nz	Function functionnamez arguments:

	argumentsz:

are not valid JSON or a Python literal. Received error: z7

are not a string or a dictionary. Received TypeError )	jsonloadsJSONDecodeErrorastliteral_evalSyntaxError
ValueErrorr   	TypeError)json_stringraw_tool_callskipemsgs        rE   _parse_json_stringrY   s   s   .0zz+&& 4	4 ##K00Z( 
	4""M*5f=>o ,[9:##$#'  (,!3
	4  0j1&9:/Z(56 7../S2 	
 $C(a/0sV    CACBBBC%#BBCC
!C'#C

Cc                ^   SU ;  a  gU S   S   nU S   S   n0 n[        U[        5      (       as  UR                  5        H]  u  pEUS:X  a  XQ:X  a  M  [        U[        5      (       a2  [	        USU S9n[        U[        [
        45      (       a  XcU'   MS  XSU'   MY  XSU'   M_     U$ [	        USU S9nU$ )	zParse arguments by trying to parse any shallowly nested string-encoded JSON.

Band-aid fix for issue in Ollama with inconsistent tool call argument structure.
Should be removed/changed if fixed upstream.

See https://github.com/ollama/ollama/issues/6155
rI   NrJ   rK   functionNameT)rV   rU   F)
isinstancedictitemsstrrY   list)rU   function_namerK   parsed_argumentskeyvalueparsed_values          rE   _parse_arguments_from_tool_callrf      s     &!*-f5Mj)+6I)T""#//+JCn$)?%%%1M  lT4L99,8S),1S)(-% ,$  .E
 rG   c                    / nSU ;   ao  U S   R                  S5      =n(       aT  UR                  U Vs/ s H7  n[        [        [	        5       5      US   S   [        U5      =(       d    0 S9PM9     sn5        U$ s  snf )z$Get tool calls from Ollama response.message
tool_callsrI   rJ   )idrJ   args)rC   extendr   r_   r   rf   )responseri   raw_tool_callstcs       rE   _get_tool_calls_from_responserp      s     JH"9-11,??? ) )B 57|J/8<B
 )		
 s   >A:c                &    SU S   U S   U S   S.S.$ )z<Convert a LangChain tool call to an OpenAI tool call format.rI   rj   rJ   rk   )rJ   rK   )typerj   rI    )
tool_call_s    rE   !_lc_tool_call_to_openai_tool_callru      s.     v&#F+
 rG   c                    U S   S:X  aB  U R                  S5      S:X  a  U S   $ U R                  S5      (       a  U S   $ Sn[        U5      eSU S    S3n[        U5      e)	z@Format standard data content block to format expected by Ollama.rr   imagesource_typebase64dataz8Image data only supported through in-line base64 format.zBlocks of type z not supported.)rC   rR   )blockerror_messages     rE   "_get_image_from_data_content_blockr}      so    V}99]#x/= 99X?"R''%eFm_ODM
]
##rG   c                F    [        U [        5      =(       a    [        U 5      $ N)r\   rr   r.   )objs    rE   _is_pydantic_classr      s    c4 ?%:3%??rG   c                    ^  \ rS rSr% SrS\S'    SrS\S'    SrS	\S
'    SrS\S'    Sr	S\S'    Sr
S\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S'    SrS\S '    SrS!\S"'    0 rS#\S$'    0 rS#\S%'    0 rS#\S&'    \" 5       rS'\S('    \" 5       rS)\S*'     SB       SCS+ jjr \!" S,S-9SDS. j5       r"    SES/ jr# SB       SFS0 jjr$ SB       SGS1 jjr%   SH           SIS2 jjr&   SH           SJS3 jjr' SB     SKS4 jjr(  SL         SMS5 jjr) SB       SNS6 jjr*  SL         SOS7 jjr+ SB       SPS8 jjr,  SL         SQS9 jjr-  SL         SRS: jjr.\/SSS; j5       r0SS<.       STU 4S= jjjr1S>SS?.         SUS@ jjr2SAr3U =r4$ )V
ChatOllamai  ut'  Ollama chat model integration.

???+ note "Setup"

    Install `langchain-ollama` and download any models you want to use from ollama.

    ```bash
    ollama pull gpt-oss:20b
    pip install -U langchain-ollama
    ```

Key init args — completion params:
    model: str
        Name of Ollama model to use.
    reasoning: bool | None
        Controls the reasoning/thinking mode for
        [supported models](https://ollama.com/search?c=thinking).

        - `True`: Enables reasoning mode. The model's reasoning process will be
            captured and returned separately in the `additional_kwargs` of the
            response message, under `reasoning_content`. The main response
            content will not include the reasoning tags.
        - `False`: Disables reasoning mode. The model will not perform any reasoning,
            and the response will not include any reasoning content.
        - `None` (Default): The model will use its default reasoning behavior. Note
            however, if the model's default behavior *is* to perform reasoning, think tags
            (`<think>` and `</think>`) will be present within the main response content
            unless you set `reasoning` to `True`.
    temperature: float
        Sampling temperature. Ranges from `0.0` to `1.0`.
    num_predict: int | None
        Max number of tokens to generate.

See full list of supported init args and their descriptions in the params section.

Instantiate:
    ```python
    from langchain_ollama import ChatOllama

    model = ChatOllama(
        model="gpt-oss:20b",
        validate_model_on_init=True,
        temperature=0.8,
        num_predict=256,
        # other params ...
    )
    ```

Invoke:
    ```python
    messages = [
        ("system", "You are a helpful translator. Translate the user sentence to French."),
        ("human", "I love programming."),
    ]
    model.invoke(messages)
    ```

    ```python
    AIMessage(content='J'adore le programmation. (Note: "programming" can also refer to the act of writing code, so if you meant that, I could translate it as "J'adore programmer". But since you didn\'t specify, I assumed you were talking about the activity itself, which is what "le programmation" usually refers to.)', response_metadata={'model': 'llama3', 'created_at': '2024-07-04T03:37:50.182604Z', 'message': {'role': 'assistant', 'content': ''}, 'done_reason': 'stop', 'done': True, 'total_duration': 3576619666, 'load_duration': 788524916, 'prompt_eval_count': 32, 'prompt_eval_duration': 128125000, 'eval_count': 71, 'eval_duration': 2656556000}, id='run-ba48f958-6402-41a5-b461-5e250a4ebd36-0')
    ```

Stream:
    ```python
    for chunk in model.stream("Return the words Hello World!"):
        print(chunk.text, end="")
    ```

    ```python
    content='Hello' id='run-327ff5ad-45c8-49fe-965c-0a93982e9be1'
    content=' World' id='run-327ff5ad-45c8-49fe-965c-0a93982e9be1'
    content='!' id='run-327ff5ad-45c8-49fe-965c-0a93982e9be1'
    content='' response_metadata={'model': 'llama3', 'created_at': '2024-07-04T03:39:42.274449Z', 'message': {'role': 'assistant', 'content': ''}, 'done_reason': 'stop', 'done': True, 'total_duration': 411875125, 'load_duration': 1898166, 'prompt_eval_count': 14, 'prompt_eval_duration': 297320000, 'eval_count': 4, 'eval_duration': 111099000} id='run-327ff5ad-45c8-49fe-965c-0a93982e9be1'

    ```

    ```python
    stream = model.stream(messages)
    full = next(stream)
    for chunk in stream:
        full += chunk
    full
    ```

    ```python
    AIMessageChunk(
        content='Je adore le programmation.(Note: "programmation" is the formal way to say "programming" in French, but informally, people might use the phrase "le développement logiciel" or simply "le code")',
        response_metadata={
            "model": "llama3",
            "created_at": "2024-07-04T03:38:54.933154Z",
            "message": {"role": "assistant", "content": ""},
            "done_reason": "stop",
            "done": True,
            "total_duration": 1977300042,
            "load_duration": 1345709,
            "prompt_eval_duration": 159343000,
            "eval_count": 47,
            "eval_duration": 1815123000,
        },
        id="run-3c81a3ed-3e79-4dd3-a796-04064d804890",
    )
    ```

Async:
    ```python
    await model.ainvoke("Hello how are you!")
    ```

    ```python
    AIMessage(
        content="Hi there! I'm just an AI, so I don't have feelings or emotions like humans do. But I'm functioning properly and ready to help with any questions or tasks you may have! How can I assist you today?",
        response_metadata={
            "model": "llama3",
            "created_at": "2024-07-04T03:52:08.165478Z",
            "message": {"role": "assistant", "content": ""},
            "done_reason": "stop",
            "done": True,
            "total_duration": 2138492875,
            "load_duration": 1364000,
            "prompt_eval_count": 10,
            "prompt_eval_duration": 297081000,
            "eval_count": 47,
            "eval_duration": 1838524000,
        },
        id="run-29c510ae-49a4-4cdd-8f23-b972bfab1c49-0",
    )
    ```

    ```python
    async for chunk in model.astream("Say hello world!"):
        print(chunk.content)
    ```

    ```python
    HEL
    LO
    WORLD
    !
    ```

    ```python
    messages = [("human", "Say hello world!"), ("human", "Say goodbye world!")]
    await model.abatch(messages)
    ```

    ```python
    [
        AIMessage(
            content="HELLO, WORLD!",
            response_metadata={
                "model": "llama3",
                "created_at": "2024-07-04T03:55:07.315396Z",
                "message": {"role": "assistant", "content": ""},
                "done_reason": "stop",
                "done": True,
                "total_duration": 1696745458,
                "load_duration": 1505000,
                "prompt_eval_count": 8,
                "prompt_eval_duration": 111627000,
                "eval_count": 6,
                "eval_duration": 185181000,
            },
            id="run-da6c7562-e25a-4a44-987a-2c83cd8c2686-0",
        ),
        AIMessage(
            content="It's been a blast chatting with you! Say goodbye to the world for me, and don't forget to come back and visit us again soon!",
            response_metadata={
                "model": "llama3",
                "created_at": "2024-07-04T03:55:07.018076Z",
                "message": {"role": "assistant", "content": ""},
                "done_reason": "stop",
                "done": True,
                "total_duration": 1399391083,
                "load_duration": 1187417,
                "prompt_eval_count": 20,
                "prompt_eval_duration": 230349000,
                "eval_count": 31,
                "eval_duration": 1166047000,
            },
            id="run-96cad530-6f3e-4cf9-86b4-e0f8abba4cdb-0",
        ),
    ]
    ```

JSON mode:
    ```python
    json_model = ChatOllama(format="json")
    json_model.invoke(
        "Return a query for the weather in a random location and time of day with two keys: location and time_of_day. "
        "Respond using JSON only."
    ).content
    ```

    ```python
    '{"location": "Pune, India", "time_of_day": "morning"}'
    ```

Tool Calling:
    ```python
    from langchain_ollama import ChatOllama
    from pydantic import BaseModel, Field


    class Multiply(BaseModel):
        a: int = Field(..., description="First integer")
        b: int = Field(..., description="Second integer")


    ans = await chat.invoke("What is 45*67")
    ans.tool_calls
    ```

    ```python
    [
        {
            "name": "Multiply",
            "args": {"a": 45, "b": 67},
            "id": "420c3f3b-df10-4188-945f-eb3abdb40622",
            "type": "tool_call",
        }
    ]
    ```

Thinking / Reasoning:
    You can enable reasoning mode for models that support it by setting
    the `reasoning` parameter to `True` in either the constructor or
    the `invoke`/`stream` methods. This will enable the model to think
    through the problem and return the reasoning process separately in the
    `additional_kwargs` of the response message, under `reasoning_content`.

    If `reasoning` is set to `None`, the model will use its default reasoning
    behavior, and any reasoning content will *not* be captured under the
    `reasoning_content` key, but will be present within the main response content
    as think tags (`<think>` and `</think>`).

    !!! note
        This feature is only available for [models that support reasoning](https://ollama.com/search?c=thinking).

    ```python
    from langchain_ollama import ChatOllama

    model = ChatOllama(
        model="deepseek-r1:8b",
        validate_model_on_init=True,
        reasoning=True,
    )

    model.invoke("how many r in the word strawberry?")

    # or, on an invocation basis:

    model.invoke("how many r in the word strawberry?", reasoning=True)
    # or model.stream("how many r in the word strawberry?", reasoning=True)

    # If not provided, the invocation will default to the ChatOllama reasoning
    # param provided (None by default).
    ```

    ```python
    AIMessage(content='The word "strawberry" contains **three \'r\' letters**. Here\'s a breakdown for clarity:\n\n- The spelling of "strawberry" has two parts ... be 3.\n\nTo be thorough, let\'s confirm with an online source or common knowledge.\n\nI can recall that "strawberry" has: s-t-r-a-w-b-e-r-r-y — yes, three r\'s.\n\nPerhaps it\'s misspelled by some, but standard is correct.\n\nSo I think the response should be 3.\n'}, response_metadata={'model': 'deepseek-r1:8b', 'created_at': '2025-07-08T19:33:55.891269Z', 'done': True, 'done_reason': 'stop', 'total_duration': 98232561292, 'load_duration': 28036792, 'prompt_eval_count': 10, 'prompt_eval_duration': 40171834, 'eval_count': 3615, 'eval_duration': 98163832416, 'model_name': 'deepseek-r1:8b'}, id='run--18f8269f-6a35-4a7c-826d-b89d52c753b3-0', usage_metadata={'input_tokens': 10, 'output_tokens': 3615, 'total_tokens': 3625})

    ```
r_   modelNzbool | str | None	reasoningFboolvalidate_model_on_initz
int | Nonemirostatzfloat | Nonemirostat_etamirostat_taunum_ctxnum_gpu
num_threadnum_predictrepeat_last_nrepeat_penaltytemperatureseedlist[str] | Nonestoptfs_ztop_ktop_pz,Literal['', 'json'] | JsonSchemaValue | Noneformatzint | str | None
keep_alivez
str | Nonebase_urlzdict | Noneclient_kwargsasync_client_kwargssync_client_kwargsr0   _clientr/   _async_clientc                   U R                  U5      nU R                  b  Ub  Sn[        U5      eU R                  b  U R                  nUR                  SS5      nUc  U R                  U R
                  U R                  U R                  U R                  U R                  U R                  U R                  U R                  U R                  U R                  Uc  U R                  OUU R                  U R                   U R"                  S.R%                  5        VVs0 s H  u  pxUc  M
  Xx_M     nnnUUR                  SS5      UR                  SU R&                  5      UR                  SU R(                  5      UR                  S	U R*                  5      UUR                  S
U R,                  5      S.UEn	SU	;   a  U	R                  S5        UR/                  S5      =n
(       a  XS'   U	$ s  snnf )aM  Assemble the parameters for a chat completion request.

Args:
    messages: List of LangChain messages to send to the model.
    stop: Optional list of stop tokens to use for this invocation.
    **kwargs: Additional keyword arguments to include in the request.

Returns:
    A dictionary of parameters to pass to the Ollama client.
Nz2`stop` found in both the input and default params.options)r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   streamTr   r   r   r   )messagesr   r   thinkr   r   r   stricttools)$_convert_messages_to_ollama_messagesr   rR   popr   r   r   r   r   r   r   r   r   r   r   r   r   r   r^   r   r   r   r   rC   )selfr   r   kwargsollama_messagesrX   options_dictkvparamsr   s              rE   _chat_paramsChatOllama._chat_params  s     CCHM99 T%5FCS/!99 99Dzz)T2
 !%$($5$5$($5$5#||#||"&//#'#3#3%)%7%7&*&9&9#'#3#3 II)-DII4!ZZ!ZZ!ZZ  %'!DA" %   . (jj40ZZ4ZZT^^<jj4;;7# **\4??C	
 	
 vJJx JJw''5'#7OSs   	G*(G*after)modec                   U R                   =(       d    0 n[        U R                  5      u  p#[        X5        UnU R                  (       a  0 UEU R                  EnUnU R
                  (       a  0 UEU R
                  En[        SSU0UD6U l        [        SSU0UD6U l	        U R                  (       a   [        U R                  U R                  5        U $ )zSet clients to use for ollama.hostrs   )r   r;   r   r:   r   r   r0   r   r/   r   r   r<   r   )r   r   cleaned_urlauth_headersr   r   s         rE   _set_clientsChatOllama._set_clients  s     **0b$7$F!=7*""!R$6!R$:Q:Q!R+##"U%8"UD<T<T"UE;E2DE(QkQ=PQ&&4<<4rG   c           
        [        U5       H  u  p#[        U[        5      (       d  M  UR                  R	                  S5      S:X  d  M=  UR                  S[        [        SUR                  5      UR                  R	                  S5      5      0S9X'   M     / nU GH  nSnSn[        U[        5      (       a  SnO[        U[        5      (       a<  S	nUR                  (       a&  UR                   Vs/ s H  n[        U5      PM     snOSnOk[        U[        5      (       a  S
nOS[        U[        5      (       a  UR                  nO1[        U[        5      (       a  SnUR                   nOSn	[#        U	5      eSn
/ n[        UR                  [$        5      (       a  UR                  n
GOzUR                   GHi  n[        U[$        5      (       a
  U
SU 3-  n
M#  UR	                  S5      S:X  a  U
SUS    3-  n
ME  UR	                  S5      S:X  a  M\  UR	                  S5      S:X  a  SnUR	                  S5      n[        U[$        5      (       a  UnOF[        U[&        5      (       a$  SU;   a  [        US   [$        5      (       a  US   nOSn	[)        U	5      eUR+                  S5      n[-        U5      S:  a  UR/                  US   5        GM  UR/                  US   5        GM0  [1        U5      (       a  [3        U5      nUR/                  U5        GM_  Sn	[)        U	5      e   UU
US.nU(       a  UUS'   U(       a  UUS'   UR/                  U5        GM     U$ s  snf )zConvert a BaseMessage list to list of messages for Ollama to consume.

Args:
    messages: List of BaseMessage to convert.

Returns:
    List of messages in Ollama format.
output_versionv1r   zlist[types.ContentBlock]model_provider)updateNuser	assistantsystemtoolz-Received unsupported message type for Ollama. 
rr   texttool_use	image_urlurlzSOnly string image_url or dict with string 'url' inside content parts are supported.,r9   r   zsUnsupported message content type. Must either have type 'text' or type 'image_url' with a string 'image_url' field.)roler   imagesri   tool_call_id)	enumerater\   r   response_metadatarC   
model_copyr8   r   r   r   ri   ru   r   r   r   r   r   rS   r_   r]   rR   splitlenappendr   r}   )r   r   idxrh   r   r   ri   r   r   rX   r   r   content_partr   temp_image_urlimage_url_componentsrw   msg_s                     rE   r   /ChatOllama._convert_messages_to_ollama_messages)  s7    &h/LC 7I..--112BCtK !( 2 2!#= !;W__M#5599:JK$ !3 ! 0" !#G'+L6:J'<00GY//" )) *1);););I :)D);
   G]33G[11||G[11&33En$GF'//3//!//$+OOL!,44R~#66%))&1V;RV(<'=#>>%))&1Z? %))&1[@$(	)5)9)9+)F%nc::(6I&~t<< % 7 *>%+@# F F(6u(=I!F   #-S/1/8s/C, 34q8"MM*>q*AB"MM*>q*AB.|<< B< Pe,? 
 )o-U %4\ " D
 %/\"'3^$""4(m  p _s   1Mc                 #    U R                   " X40 UD6nUS   (       a4  U R                  R                  " S0 UD6I S h  vN   S h  vN nU7v   M  U R                  R                  " S0 UD6I S h  vN 7v   g  N< N5
 g  N7fNr   rs   )r   r   chat)r   r   r   r   chat_paramsparts         rE   _acreate_chat_streamChatOllama._acreate_chat_stream  s}      ''A&Ax $($6$6$;$;$Jk$JJJ d
**//>+>>> K J ?s@   =BA< BB A>	B 'B3B4	B>B  Bc              +    #    U R                   " X40 UD6nUS   (       a7  U R                  (       a%  U R                  R                  " S0 UD6 S h  vN   g g U R                  (       a  U R                  R                  " S0 UD6v   g g  N77fr   )r   r   r   )r   r   r   r   r   s        rE   _create_chat_streamChatOllama._create_chat_stream  st      ''A&Ax ||<<,,;{;;; \\,,##2k22  <s   AB
B8B
c                    S nU R                   " X40 UD6 H1  nUc  UnOXg-  nU(       d  M  UR                  UR                  UUS9  M3     Uc  Sn[        U5      eU$ N)chunkverbosez$No data received from Ollama stream.)_iterate_over_streamon_llm_new_tokenr   rR   	r   r   r   run_managerr   r   final_chunkr   rX   s	            rE   _chat_stream_with_aggregation(ChatOllama._chat_stream_with_aggregation  sy     ..xHHE"#${,,JJ# -  I 8CS/!rG   c                   #    S nU R                   " X40 UD6  S h  vN nUc  UnOXg-  nU(       d  M  UR                  UR                  UUS9I S h  vN   MA   N< N
 Uc  Sn[        U5      eU$ 7fr   )_aiterate_over_streamr   r   rR   r   s	            rE   _achat_stream_with_aggregation)ChatOllama._achat_stream_with_aggregation  s      55hOO 
	%"#${!22JJ# 3   
	 P 8CS/!s<   A0AAAA0A0AA0AA0A0c           	         U R                   " SSU0UD6n[        SU R                  SUR                  SU R                  5      S9nU=(       d%    UR                  SS5      =(       d    U R
                  =n(       a  XTS'   U$ )	z Get standard params for tracing.r   ollamar   r   )ls_providerls_model_namels_model_typels_temperatureNls_stoprs   )_get_invocation_paramsr   r   rC   r   r   )r   r   r   r   	ls_paramsr   s         rE   _get_ls_paramsChatOllama._get_ls_params  s{     ,,A$A&A# ** !::mT5E5EF	
	 Cfjj6C$))C7C#*i rG   c           
     J   U R                   " XU4SU R                  0UD6nUR                  n[        [	        UR
                  [        SUR                  5      R                  [        SUR                  5      R                  UR                  R                  S9US9n[        U/S9$ Nr   r   )r   usage_metadatari   additional_kwargsrh   rD   )generations)r   r   rD   r$   r   r   r   rh   r   ri   r   r&   r   r   r   r   r   r   rD   chat_generations           rE   	_generateChatOllama._generate  s     88K
15
AG
 &55(#((#$k&9&9  . 0+2E2EFQQ"-"5"5"G"G ,

 &788rG   c              +    #    UR                  SU R                  5      nU R                  " X40 UD6 GH;  n[        U[        5      (       a  M  SU;   a  SUS   ;   a  US   S   OSnUR                  S5      SL =(       a0    UR                  S5      S:H  =(       a    UR                  5       (       + nU(       a  [        R                  S	5        M  UR                  S5      SL a1  [        U5      nS
U;   a  US
   US'   SUS'   UR                  SS 5      n	OS n0 n
U(       a%  SU;   a  US   R                  S5      =n(       a  XS'   [        [        UU
[        U5      [        U5      S9US9nUv   GM>     g 7f)Nr   rh   r   r   doneTdone_reasonloadzOllama returned empty response with done_reason='load'.This typically indicates the model was loaded but no content was generated. Skipping this response.r   
model_namer   r   thinkingreasoning_contentr   r   r   ri   r  )rC   r   r   r\   r_   striplogwarningr]   r   r%   r   rF   rp   r   r   r   r   r   stream_respr   #is_load_response_with_empty_contentrD   _r   thinking_contentr   s                rE   r   ChatOllama._iterate_over_stream  s     JJ{DNN;	33HMfMKk3// !K/IYAW4W  	*95   OOF+t3 ,#6&@,#MMO+ 4 7KKA
 ??6*d2&*;&7O/18G8P58@O$45'++It<A&*O$&!![0-8-C-G-G
-SS)S=M&9:+* '*;'O'( $A#M %4
 k Ns   AE3D&E3c              +     #    U R                   " X40 UD6 H2  nU(       a$  UR                  UR                  U R                  S9  Uv   M4     g 7fN)r   )r   r   r   r   r   r   r   r   r   r   s         rE   _streamChatOllama._streamU  sM      ..xHHE,,JJ LL -  K Is   AAc                 #    UR                  SU R                  5      nU R                  " X40 UD6  S h  vN n[        U[        5      (       a  M   SU;   a  SUS   ;   a  US   S   OSnUR                  S5      SL =(       a0    UR                  S5      S:H  =(       a    UR                  5       (       + nU(       a  [        R                  S	5        M  UR                  S5      SL a1  [        U5      nS
U;   a  US
   US'   SUS'   UR                  SS 5      n	OS n0 n
U(       a%  SU;   a  US   R                  S5      =n(       a  XS'   [        [        UU
[        U5      [        U5      S9US9nU7v   GMD   GN@
 g 7f)Nr   rh   r   r   r  Tr	  r
  zOllama returned empty response with done_reason='load'. This typically indicates the model was loaded but no content was generated. Skipping this response.r   r  r   r   r  r  r  r  )rC   r   r   r\   r_   r  r  r  r]   r   r%   r   rF   rp   r  s                rE   r    ChatOllama._aiterate_over_streamd  s     JJ{DNN;	!%!:!:8!TV!T 5	+k3// !K/IYAW4W  	*95   OOF+t3 ,#6&@,#MMO+ 4 7KKA
 ??6*d2&*;&7O/18G8P58@O$45'++It<A&*O$&!![0-8-C-G-G
-SS)S=M&9:+* '*;'O'( $A#M %4
 k5	!Ts,   0E;E9E6E9E;D$E;6E99E;c                  #    U R                   " X40 UD6  S h  vN nU(       a,  UR                  UR                  U R                  S9I S h  vN   U7v   MC   N> N
 g 7fr  )r   r   r   r   r  s         rE   _astreamChatOllama._astream  sf       55hOO 	%!22JJ LL 3    K	 Ps7   AAAA/AAAAAAc           
     f  #    U R                   " XU4SU R                  0UD6I S h  vN nUR                  n[        [	        UR
                  [        SUR                  5      R                  [        SUR                  5      R                  UR                  R                  S9US9n[        U/S9$  N7fr   )r   r   rD   r$   r   r   r   rh   r   ri   r   r&   r  s           rE   
_agenerateChatOllama._agenerate  s      !??K
15
AG
 
 &55(#((#$k&9&9  . 0+2E2EFQQ"-"5"5"G"G ,

 &788
s   $B1B/B	B1c                    g)zReturn type of chat model.zchat-ollamars   )r   s    rE   	_llm_typeChatOllama._llm_type  s     rG   )tool_choicec               h   > U Vs/ s H  n[        U5      PM     nn[        TU ]  " SSU0UD6$ s  snf )a$  Bind tool-like objects to this chat model.

Assumes model is compatible with OpenAI tool-calling API.

Args:
    tools: A list of tool definitions to bind to this chat model.

        Supports any tool definition handled by [`convert_to_openai_tool`][langchain_core.utils.function_calling.convert_to_openai_tool].
    tool_choice: If provided, which tool for model to call. **This parameter
        is currently ignored as it is not supported by Ollama.**
    kwargs: Any additional parameters are passed directly to
        `self.bind(**kwargs)`.
r   rs   )r,   superbind)r   r   r(  r   r   formatted_tools	__class__s         rE   
bind_toolsChatOllama.bind_tools  s>    ( EJJED1$7EJw|</<V<< Ks   /json_schema)methodinclude_rawc               L   UR                  SS5      nU(       a  SU 3n[        U5      e[        U5      nUS:X  aZ  Uc  Sn[        U5      e[        U5      nUS   S   n	U R	                  U/U	SU0US	.S
9n
U(       a  [        U/SS9nGO>[        U	SS9nGO2US:X  a0  U R                  SSU0US	.S9n
U(       a	  [        US9O	[        5       nOUS:X  a  Uc  Sn[        U5      eU(       aa  [        SU5      n[        U[        5      (       a  UR                  5       nOUR                  5       nU R                  USU0US	.S9n
[        US9nO~[        U5      (       a1  [!        U5      nSU;  a  [#        US   R%                  5       5      US'   O[        SU5      nU R                  USU0US	.S9n
[        5       nOSU S3n[        U5      eU(       aT  [&        R(                  " [+        S5      U-  S S9n[&        R(                  " S S9nUR-                  U/SS9n[/        U
S 9U-  $ X-  $ )!a$  Model wrapper that returns outputs formatted to match the given schema.

Args:
    schema: The output schema. Can be passed in as:

        - An OpenAI function/tool schema.
        - A JSON Schema,
        - A `TypedDict` class,
        - Or a Pydantic class.

        If `schema` is a Pydantic class then the model output will be a
        Pydantic instance of that class, and the model-generated fields will be
        validated by the Pydantic class. Otherwise the model output will be a
        dict and will not be validated.

        See `langchain_core.utils.function_calling.convert_to_openai_tool` for
        more on how to properly specify types and descriptions of schema fields
        when specifying a Pydantic or `TypedDict` class.

    method: The method for steering model generation, one of:

        - `'json_schema'`:
            Uses Ollama's [structured output API](https://ollama.com/blog/structured-outputs)
        - `'function_calling'`:
            Uses Ollama's tool-calling API
        - `'json_mode'`:
            Specifies `format='json'`. Note that if using JSON mode then you
            must include instructions for formatting the output into the
            desired schema into the model call.

    include_raw:
        If `False` then only the parsed structured output is returned.

        If an error occurs during model output parsing it will be raised.

        If `True` then both the raw model response (a `BaseMessage`) and the
        parsed model response will be returned.

        If an error occurs during output parsing it will be caught and returned
        as well.

        The final output is always a `dict` with keys `'raw'`, `'parsed'`, and
        `'parsing_error'`.

    kwargs: Additional keyword args aren't supported.

Returns:
    A `Runnable` that takes same inputs as a
        `langchain_core.language_models.chat.BaseChatModel`. If `include_raw` is
        `False` and `schema` is a Pydantic class, `Runnable` outputs an instance
        of `schema` (i.e., a Pydantic object). Otherwise, if `include_raw` is
        `False` then `Runnable` outputs a `dict`.

        If `include_raw` is `True`, then `Runnable` outputs a `dict` with keys:

        - `'raw'`: `BaseMessage`
        - `'parsed'`: `None` if there was a parsing error, otherwise the type
            depends on the `schema` as described above.
        - `'parsing_error'`: `BaseException | None`

!!! warning "Behavior changed in `langchain-ollama` 0.2.2"

    Added support for structured output API via `format` parameter.

!!! warning "Behavior changed in `langchain-ollama` 0.3.0"

    Updated default `method` to `'json_schema'`.

??? note "Example: `schema=Pydantic` class, `method='json_schema'`, `include_raw=False`"

    ```python
    from typing import Optional

    from langchain_ollama import ChatOllama
    from pydantic import BaseModel, Field


    class AnswerWithJustification(BaseModel):
        '''An answer to the user question along with justification for the answer.'''

        answer: str
        justification: str | None = Field(
            default=...,
            description="A justification for the answer.",
        )


    model = ChatOllama(model="llama3.1", temperature=0)
    structured_model = model.with_structured_output(AnswerWithJustification)

    structured_model.invoke("What weighs more a pound of bricks or a pound of feathers")

    # -> AnswerWithJustification(
    #     answer='They weigh the same',
    #     justification='Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume or density of the objects may differ.'
    # )
    ```

??? note "Example: `schema=Pydantic` class, `method='json_schema'`, `include_raw=True`"

    ```python
    from langchain_ollama import ChatOllama
    from pydantic import BaseModel


    class AnswerWithJustification(BaseModel):
        '''An answer to the user question along with justification for the answer.'''

        answer: str
        justification: str


    model = ChatOllama(model="llama3.1", temperature=0)
    structured_model = model.with_structured_output(
        AnswerWithJustification,
        include_raw=True,
    )

    structured_model.invoke("What weighs more a pound of bricks or a pound of feathers")
    # -> {
    #     'raw': AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Ao02pnFYXD6GN1yzc0uXPsvF', 'function': {'arguments': '{"answer":"They weigh the same.","justification":"Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume or density of the objects may differ."}', 'name': 'AnswerWithJustification'}, 'type': 'function'}]}),
    #     'parsed': AnswerWithJustification(answer='They weigh the same.', justification='Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume or density of the objects may differ.'),
    #     'parsing_error': None
    # }
    ```

??? note "Example: `schema=Pydantic` class, `method='function_calling'`, `include_raw=False`"

    ```python
    from typing import Optional

    from langchain_ollama import ChatOllama
    from pydantic import BaseModel, Field


    class AnswerWithJustification(BaseModel):
        '''An answer to the user question along with justification for the answer.'''

        answer: str
        justification: str | None = Field(
            default=...,
            description="A justification for the answer.",
        )


    model = ChatOllama(model="llama3.1", temperature=0)
    structured_model = model.with_structured_output(
        AnswerWithJustification,
        method="function_calling",
    )

    structured_model.invoke("What weighs more a pound of bricks or a pound of feathers")

    # -> AnswerWithJustification(
    #     answer='They weigh the same',
    #     justification='Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume or density of the objects may differ.'
    # )
    ```

??? note "Example: `schema=TypedDict` class, `method='function_calling'`, `include_raw=False`"

    ```python
    from typing_extensions import Annotated, TypedDict

    from langchain_ollama import ChatOllama


    class AnswerWithJustification(TypedDict):
        '''An answer to the user question along with justification for the answer.'''

        answer: str
        justification: Annotated[str | None, None, "A justification for the answer."]


    model = ChatOllama(model="llama3.1", temperature=0)
    structured_model = model.with_structured_output(AnswerWithJustification)

    structured_model.invoke("What weighs more a pound of bricks or a pound of feathers")
    # -> {
    #     'answer': 'They weigh the same',
    #     'justification': 'Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume and density of the two substances differ.'
    # }
    ```

??? note "Example: `schema=OpenAI` function schema, `method='function_calling'`, `include_raw=False`"

    ```python
    from langchain_ollama import ChatOllama

    oai_schema = {
        'name': 'AnswerWithJustification',
        'description': 'An answer to the user question along with justification for the answer.',
        'parameters': {
            'type': 'object',
            'properties': {
                'answer': {'type': 'string'},
                'justification': {'description': 'A justification for the answer.', 'type': 'string'}
            },
            'required': ['answer']
        }

        model = ChatOllama(model="llama3.1", temperature=0)
        structured_model = model.with_structured_output(oai_schema)

        structured_model.invoke(
            "What weighs more a pound of bricks or a pound of feathers"
        )
        # -> {
        #     'answer': 'They weigh the same',
        #     'justification': 'Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume and density of the two substances differ.'
        # }
    ```

??? note "Example: `schema=Pydantic` class, `method='json_mode'`, `include_raw=True`"

    ```python
    from langchain_ollama import ChatOllama
    from pydantic import BaseModel


    class AnswerWithJustification(BaseModel):
        answer: str
        justification: str


    model = ChatOllama(model="llama3.1", temperature=0)
    structured_model = model.with_structured_output(
        AnswerWithJustification, method="json_mode", include_raw=True
    )

    structured_model.invoke(
        "Answer the following question. "
        "Make sure to return a JSON blob with keys 'answer' and 'justification'.\\n\\n"
        "What's heavier a pound of bricks or a pound of feathers?"
    )
    # -> {
    #     'raw': AIMessage(content='{\\n    "answer": "They are both the same weight.",\\n    "justification": "Both a pound of bricks and a pound of feathers weigh one pound. The difference lies in the volume and density of the materials, not the weight." \\n}'),
    #     'parsed': AnswerWithJustification(answer='They are both the same weight.', justification='Both a pound of bricks and a pound of feathers weigh one pound. The difference lies in the volume and density of the materials, not the weight.'),
    #     'parsing_error': None
    # }
    ```

r   NzReceived unsupported arguments function_callingzGschema must be specified when method is not 'json_mode'. Received None.rI   rJ   r1  )r   schema)r(  ls_structured_output_formatT)r   first_tool_only)key_namer7  	json_moderL   )r   r6  )pydantic_objectr0  r-   required
propertiesr]   zlUnrecognized method argument. Expected one of 'function_calling', 'json_schema', or 'json_mode'. Received: ''rawc                    g r   rs   r  s    rE   <lambda>3ChatOllama.with_structured_output.<locals>.<lambda><  s    RVrG   )parsedparsing_errorc                    g r   rs   r@  s    rE   rA  rB  >  s    drG   )rC  rD  )exception_key)r>  )r   rR   r   r,   r.  r#   r    r+  r"   r!   r   
issubclassBaseModelV1r5  model_json_schemar7   r+   r`   keysr)   assignr	   with_fallbacksr(   )r   r5  r1  r2  r   r  rX   is_pydantic_schemaformatted_tool	tool_namellmoutput_parserresponse_formatparser_assignparser_noneparser_with_fallbacks                   rE   with_structured_output!ChatOllama.with_structured_output  s   v JJx&3F8<CS/!/7''~%  !o%3F;N&z26:I//%'0,- " C "*=!($(+
 !9&! {"))'0$-  C & %V<%' 
 }$~%  !o%!ov6fk22&,mmoO&,&>&>&@Oii*#+V"4"(1    !5V L''&<V&DO!86:+L9>>@7
3
 '+66&:Oii*#+V"4"11    !1 2==CHAG  S/!/66!%(=8M .44NKK#0#?#?_ $@ $  3'*>>>""rG   )r   r   r   )r   list[BaseMessage]r   r   r   r
   returndict[str, Any])rY  r6   )r   rX  rY  zSequence[Message])r   rX  r   r   r   r
   rY  z&AsyncIterator[Mapping[str, Any] | str])r   rX  r   r   r   r
   rY  z!Iterator[Mapping[str, Any] | str])NNF)r   rX  r   r   r   CallbackManagerForLLMRun | Noner   r   r   r
   rY  r%   )r   rX  r   r   r   $AsyncCallbackManagerForLLMRun | Noner   r   r   r
   rY  r%   )r   r   r   r
   rY  r   )NN)
r   rX  r   r   r   r[  r   r
   rY  r&   )r   rX  r   r   r   r
   rY  Iterator[ChatGenerationChunk])
r   rX  r   r   r   r[  r   r
   rY  r]  )r   rX  r   r   r   r
   rY  "AsyncIterator[ChatGenerationChunk])
r   rX  r   r   r   r\  r   r
   rY  r^  )
r   rX  r   r   r   r\  r   r
   rY  r&   )rY  r_   )r   z5Sequence[dict[str, Any] | type | Callable | BaseTool]r(  z1dict | str | Literal['auto', 'any'] | bool | Noner   r
   rY  z'Runnable[LanguageModelInput, AIMessage])
r5  zdict | typer1  z7Literal['function_calling', 'json_mode', 'json_schema']r2  r   r   r
   rY  z.Runnable[LanguageModelInput, dict | BaseModel])5__name__
__module____qualname____firstlineno____doc____annotations__r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r3   r   r   r   r4   r   r   r   r   r   r   r   r  r   r  r   r   r#  propertyr&  r.  rV  __static_attributes____classcell__)r-  s   @rE   r   r     s!   EN J#'I '$ $)D(
  Hj
 "&L,% "&L,% GZ
 GZ
 "J
! #K"
 !%M:$
 $(NL' !%K$ D* "D
!&E< E: E< <@F8?L#'J ':Hj* "$M;# (*) ')( "mGV#0!,M;.6
 "&D#D D 	D
 
DL '" #*u)u	ut "&?#? ? 	?
 
0?" "&3#3 3 	3
 
+3" "&7;#  5	
   
: "&<@#  :	
   
6 (,$7:	$ "&7;	9#9 9 5	9
 9 
96 "&<#< < 	<
 
'<B "&7;	#  5	
  
'$ "&<#< < 	<
 
,<B "&<@	#  :	
  
,$ "&<@	9#9 9 :	9
 9 
90   JN	=D= G	=
 = 
1= =6 KX!^#^# H	^#
 ^# ^# 
8^# ^#rG   r   )rD   zMapping[str, Any] | NonerY  zUsageMetadata | None)rT   r_   rU   rZ  rV   r   rY  r
   )rU   rZ  rY  zdict[str, Any] | None)rm   zMapping[str, Any]rY  zlist[ToolCall])rt   r   rY  r]   )r{   r]   rY  r_   )r   r
   rY  r   )drc  
__future__r   rO   rL   loggingcollections.abcr   r   r   r   r   operatorr	   typingr
   r   r   uuidr   langchain_core.callbacksr    langchain_core.callbacks.managerr   langchain_core.exceptionsr   langchain_core.language_modelsr   *langchain_core.language_models.chat_modelsr   r   langchain_core.messagesr   r   r   r   r   r   r   r   r   r   typeslangchain_core.messages.air   langchain_core.messages.toolr   langchain_core.output_parsersr    r!   r"   r#   langchain_core.outputsr$   r%   r&   langchain_core.runnablesr'   r(   r)   langchain_core.toolsr*   %langchain_core.utils.function_callingr+   r,   langchain_core.utils.pydanticr-   r.   r   r/   r0   r1   pydanticr2   r3   r4   pydantic.json_schemar5   pydantic.v1rH  typing_extensionsr6   r7   langchain_ollama._compatr8   _utilsr:   r;   r<   	getLoggerr_  r  rF   rY   rf   rp   ru   r}   r   r   rs   rG   rE   <module>r     s3  'R # 
   P P  % %  = J ; = U
 
 
 5 4 2  S R O O ) O / / < < 0 0 0 ? K K!-"1010 "10 	10
 	10h"!""J*	$ @B# B#rG   