mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-28 06:00:38 +00:00
57 lines
2.6 KiB
Python
57 lines
2.6 KiB
Python
"""Shared message plumbing for providers that preserve a reasoning string.
|
|
|
|
``patched_mimo`` and ``patched_stepfun`` both wrap ``ChatOpenAI`` so a provider's
|
|
``reasoning_content`` survives multi-turn tool calls, and each carried a
|
|
byte-identical copy of the two mechanical steps that job needs: attach a
|
|
reasoning string to a LangChain message without mutating the message the caller
|
|
still holds, and read ``choices[index].message`` out of a response object that
|
|
may not be shaped like that at all (dict payloads, SDK objects, or a chunk with
|
|
no choices).
|
|
|
|
How a provider *finds* its reasoning field stays local on purpose: MiMo reads
|
|
``reasoning_content`` through ``_extract_reasoning_content``, StepFun reads both
|
|
``reasoning`` and ``reasoning_content`` through ``_extract_reasoning``, and
|
|
``patched_minimax`` merges streamed deltas. Those are provider dialects, not this
|
|
rule. This module owns only what the two copies already agreed on, so a fix to
|
|
the attach-without-mutating rule lands once instead of per provider.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from langchain_core.messages import AIMessage, AIMessageChunk
|
|
|
|
|
|
def with_reasoning_content(message: AIMessage | AIMessageChunk, reasoning: str) -> AIMessage | AIMessageChunk:
|
|
"""Return a copy of ``message`` carrying ``reasoning`` in ``additional_kwargs``.
|
|
|
|
The incoming message is never modified: LangChain hands the same object to
|
|
callbacks, run journals and the caller's history, so an in-place write would
|
|
leak reasoning into a message that was already emitted. A copy is returned
|
|
whether or not the value changed, keeping the result's identity predictable
|
|
for callers that replay messages through ``model_copy``.
|
|
"""
|
|
additional_kwargs = dict(message.additional_kwargs)
|
|
if additional_kwargs.get("reasoning_content") != reasoning:
|
|
additional_kwargs["reasoning_content"] = reasoning
|
|
return message.model_copy(update={"additional_kwargs": additional_kwargs})
|
|
|
|
|
|
def typed_choice_message(response: Any, index: int) -> Any:
|
|
"""Return ``response.choices[index].message`` when that shape exists, else ``None``.
|
|
|
|
Non-streaming results arrive either as a dict payload (no attribute access)
|
|
or as an SDK object whose choice list may be shorter than the index the
|
|
caller is replaying. Neither case is an error for the callers here: they
|
|
fall back to the payload they already have, so this reports "nothing typed"
|
|
rather than raising.
|
|
"""
|
|
choices = getattr(response, "choices", None)
|
|
if choices is None:
|
|
return None
|
|
try:
|
|
return choices[index].message
|
|
except (AttributeError, IndexError, TypeError):
|
|
return None
|