mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-25 04:46:03 -04:00
f63a7b56f9
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Everything below this line will be the GitHub release body._ --- ## [0.1.21](https://github.com/langchain-ai/deepagents/compare/deepagents-code==0.1.20...deepagents-code==0.1.21) (2026-06-23) ### Features * `dcode doctor` diagnostics command ([#4148](https://github.com/langchain-ai/deepagents/issues/4148)) ([8179731](https://github.com/langchain-ai/deepagents/commit/81797312c7d857e7d94d03c9c695cd3c8d88799a)) * Add structured TUI display for `js_eval` ([#4151](https://github.com/langchain-ai/deepagents/issues/4151)) ([91c0dae](https://github.com/langchain-ai/deepagents/commit/91c0dae3fe0253f02a5926fcd3c6f796cd8d11fe)) * Allow dependency updates without requiring release ([#4157](https://github.com/langchain-ai/deepagents/issues/4157)) ([7beb97a](https://github.com/langchain-ai/deepagents/commit/7beb97a2b02e2fd238baf3b6f05d43a4accf3f42)) * Clear chat input via `esc+esc`, add `[ X ]/[ COPY ]` buttons ([#4000](https://github.com/langchain-ai/deepagents/issues/4000)) ([c20546f](https://github.com/langchain-ai/deepagents/commit/c20546feac7876786e6816776d1ccfa5fcd4b2c8)) * Confirm "Launched" after auto-update restart ([#4098](https://github.com/langchain-ai/deepagents/issues/4098)) ([df8db8a](https://github.com/langchain-ai/deepagents/commit/df8db8af6a7cbfc2ab535020b951d73759da73dd)) * Surface tracing in `doctor` and `config show` ([#4163](https://github.com/langchain-ai/deepagents/issues/4163)) ([2bb3e44](https://github.com/langchain-ai/deepagents/commit/2bb3e44243553a5f2954a0f3ec42364563842a87)) ### Bug Fixes * Handle LangSmith project-not-found and default tracing project ([#4153](https://github.com/langchain-ai/deepagents/issues/4153)) ([e303ce9](https://github.com/langchain-ai/deepagents/commit/e303ce986a3595f0cf458e796d857f7c8f5f8b5c)) * Make `/timestamps` toggle instant via per-footer class ([#4095](https://github.com/langchain-ai/deepagents/issues/4095)) ([7ae32b0](https://github.com/langchain-ai/deepagents/commit/7ae32b0a606cc200d4311e11036a65f17e8282b3)) * Refocus `/mcp` filter input after in-place refresh ([#4080](https://github.com/langchain-ai/deepagents/issues/4080)) ([d79cd74](https://github.com/langchain-ai/deepagents/commit/d79cd74cb8a44c300c3bbad712fe77e709f9221a)) * Report same-version dependency updates ([#4146](https://github.com/langchain-ai/deepagents/issues/4146)) ([156e118](https://github.com/langchain-ai/deepagents/commit/156e1185242a19746f8c268904637c73f07b9a10)) * Show "Loading..." in `/threads` agent dropdown while loading ([#4101](https://github.com/langchain-ai/deepagents/issues/4101)) ([c2d949e](https://github.com/langchain-ai/deepagents/commit/c2d949e8765fbbbdb81e5a70125932842358099f)) * Skip tool interrupts once auto-approve is set ([#4092](https://github.com/langchain-ai/deepagents/issues/4092)) ([9e21c34](https://github.com/langchain-ai/deepagents/commit/9e21c346a6eb8ad25b9cc671f24527b07732e2b7)) * Word-delete backspace parity in ask-user text area ([#4079](https://github.com/langchain-ai/deepagents/issues/4079)) ([ed3c499](https://github.com/langchain-ai/deepagents/commit/ed3c499354467bc5e8476e5c7cdf0cd5f8b6aec1)) --- _Everything above this line will be the GitHub release body._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mason Daugherty <github@mdrxy.com>
145 lines
5.5 KiB
Python
145 lines
5.5 KiB
Python
"""Internal chat models used by local integration tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
|
|
from langchain_core.messages import AIMessage, BaseMessage
|
|
from langchain_core.outputs import ChatGeneration, ChatResult
|
|
from pydantic import Field
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable, Sequence
|
|
|
|
from langchain_core.callbacks import CallbackManagerForLLMRun
|
|
from langchain_core.language_models import LanguageModelInput
|
|
from langchain_core.runnables import Runnable
|
|
from langchain_core.tools import BaseTool
|
|
|
|
|
|
class DeterministicIntegrationChatModel(GenericFakeChatModel):
|
|
"""Deterministic chat model for integration tests.
|
|
|
|
This subclasses LangChain's `GenericFakeChatModel` so the implementation
|
|
stays aligned with the core fake-chat-model test surface, while overriding
|
|
generation to remain prompt-driven and restart-safe for real CLI server
|
|
integration tests.
|
|
|
|
Why the existing `langchain_core` fakes cannot be reused here:
|
|
|
|
1. Every core fake (`GenericFakeChatModel`, `FakeListChatModel`,
|
|
`FakeMessagesListChatModel`) pops from an iterator or cycles an index —
|
|
the actual prompt is ignored. App integration tests start and stop the
|
|
server process, which resets in-memory state. An iterator-based model
|
|
either raises `StopIteration` or replays from the beginning after a
|
|
restart, producing wrong or missing responses. This model derives output
|
|
solely from the prompt text, so identical input always produces
|
|
identical output regardless of process lifecycle.
|
|
|
|
2. The agent runtime calls `model.bind_tools(schemas)` during
|
|
initialization. None of the core fakes implement `bind_tools`, so they
|
|
raise `AttributeError` in any agent-loop context. This model provides a
|
|
no-op passthrough.
|
|
|
|
3. The app server reads `model.profile` for capability negotiation (e.g.
|
|
`tool_calling`, `max_input_tokens`). Core fakes have no such attribute,
|
|
causing `AttributeError` or silent misconfiguration at runtime.
|
|
|
|
Additionally, the compact middleware issues summarization prompts mid-
|
|
conversation. A list-based model cannot distinguish these from normal user
|
|
turns without pre-knowledge of exact call ordering, whereas this model
|
|
detects summary requests by inspecting the prompt content.
|
|
"""
|
|
|
|
model: str = "fake"
|
|
# Required by `GenericFakeChatModel`, but our override does not consume it.
|
|
messages: object = Field(default_factory=lambda: iter(()))
|
|
profile: dict[str, Any] | None = Field(
|
|
default_factory=lambda: {
|
|
"tool_calling": True,
|
|
"max_input_tokens": 8000,
|
|
}
|
|
)
|
|
|
|
def bind_tools(
|
|
self,
|
|
tools: Sequence[dict[str, Any] | type | Callable | BaseTool], # noqa: ARG002
|
|
*,
|
|
tool_choice: str | None = None, # noqa: ARG002
|
|
**kwargs: Any, # noqa: ARG002
|
|
) -> Runnable[LanguageModelInput, AIMessage]:
|
|
"""Return self so the agent can bind tool schemas during tests."""
|
|
return self
|
|
|
|
def _generate(
|
|
self,
|
|
messages: list[BaseMessage],
|
|
stop: list[str] | None = None, # noqa: ARG002
|
|
run_manager: CallbackManagerForLLMRun | None = None, # noqa: ARG002
|
|
**kwargs: Any, # noqa: ARG002
|
|
) -> ChatResult:
|
|
"""Produce a deterministic reply derived from the prompt text.
|
|
|
|
Returns:
|
|
A single-message `ChatResult` with deterministic content.
|
|
"""
|
|
prompt = "\n".join(
|
|
text
|
|
for message in messages
|
|
if (text := self._stringify_message(message)).strip()
|
|
)
|
|
if self._looks_like_summary_request(prompt):
|
|
content = "integration summary"
|
|
else:
|
|
excerpt = " ".join(prompt.split()[-18:])
|
|
if excerpt:
|
|
content = f"integration reply: {excerpt}"
|
|
else:
|
|
content = "integration reply"
|
|
|
|
return ChatResult(
|
|
generations=[ChatGeneration(message=AIMessage(content=content))]
|
|
)
|
|
|
|
@property
|
|
def _llm_type(self) -> str:
|
|
"""LangChain model type identifier."""
|
|
return "deterministic-integration"
|
|
|
|
@staticmethod
|
|
def _stringify_message(message: BaseMessage) -> str:
|
|
"""Flatten message content into plain text for deterministic responses.
|
|
|
|
Returns:
|
|
Plain-text content extracted from the message.
|
|
"""
|
|
content = message.content
|
|
if isinstance(content, str):
|
|
return content
|
|
if isinstance(content, list):
|
|
parts: list[str] = []
|
|
for block in content:
|
|
if isinstance(block, str):
|
|
parts.append(block)
|
|
elif isinstance(block, dict) and block.get("type") == "text":
|
|
text = block.get("text")
|
|
if isinstance(text, str):
|
|
parts.append(text)
|
|
return " ".join(parts)
|
|
return str(content)
|
|
|
|
@staticmethod
|
|
def _looks_like_summary_request(prompt: str) -> bool:
|
|
"""Detect the middleware's summary-generation prompt.
|
|
|
|
Returns:
|
|
`True` when the prompt appears to be a summarization request.
|
|
"""
|
|
lowered = prompt.lower()
|
|
return (
|
|
"messages to summarize" in lowered
|
|
or "condense the following conversation" in lowered
|
|
or "<summary>" in lowered
|
|
)
|