Files
deepagents/libs/code/deepagents_code/_testing_models.py
T
github-actions[bot] f63a7b56f9 release(deepagents-code): 0.1.21 (#4091)
> [!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>
2026-06-23 02:08:21 -04:00

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
)