Skip to content

Commit 21502f4

Browse files
authored
fix: Use deepcopy with exceptions instead of deepcopy (#10029)
* Fix deepcopy issue * Update test * Fix spelling
1 parent b88493a commit 21502f4

File tree

4 files changed

+20
-9
lines changed

4 files changed

+20
-9
lines changed

haystack/core/pipeline/breakpoint.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
import json
6-
from copy import deepcopy
76
from dataclasses import replace
87
from datetime import datetime
98
from pathlib import Path
@@ -13,6 +12,7 @@
1312

1413
from haystack import logging
1514
from haystack.core.errors import BreakpointException, PipelineInvalidPipelineSnapshotError
15+
from haystack.core.pipeline.utils import _deepcopy_with_exceptions
1616
from haystack.dataclasses import ChatMessage
1717
from haystack.dataclasses.breakpoints import (
1818
AgentBreakpoint,
@@ -338,8 +338,10 @@ def _create_agent_snapshot(
338338
"""
339339
return AgentSnapshot(
340340
component_inputs={
341-
"chat_generator": _serialize_value_with_schema(deepcopy(component_inputs["chat_generator"])),
342-
"tool_invoker": _serialize_value_with_schema(deepcopy(component_inputs["tool_invoker"])),
341+
"chat_generator": _serialize_value_with_schema(
342+
_deepcopy_with_exceptions(component_inputs["chat_generator"])
343+
),
344+
"tool_invoker": _serialize_value_with_schema(_deepcopy_with_exceptions(component_inputs["tool_invoker"])),
343345
},
344346
component_visits=component_visits,
345347
break_point=agent_breakpoint,

haystack/core/pipeline/pipeline.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,8 @@ def run( # noqa: PLR0915, PLR0912, C901, pylint: disable=too-many-branches
361361
)
362362
if break_point and (component_break_point_triggered or agent_break_point_triggered):
363363
new_pipeline_snapshot = _create_pipeline_snapshot(
364-
inputs=deepcopy(inputs),
365-
component_inputs=deepcopy(component_inputs),
364+
inputs=_deepcopy_with_exceptions(inputs),
365+
component_inputs=_deepcopy_with_exceptions(component_inputs),
366366
break_point=break_point,
367367
component_visits=component_visits,
368368
original_input_data=data,
@@ -399,8 +399,8 @@ def run( # noqa: PLR0915, PLR0912, C901, pylint: disable=too-many-branches
399399

400400
# Create a snapshot of the state of the pipeline before the error occurred.
401401
pipeline_snapshot = _create_pipeline_snapshot(
402-
inputs=deepcopy(inputs),
403-
component_inputs=deepcopy(component_inputs),
402+
inputs=_deepcopy_with_exceptions(inputs),
403+
component_inputs=_deepcopy_with_exceptions(component_inputs),
404404
break_point=break_point,
405405
component_visits=component_visits,
406406
original_input_data=data,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
When creating a pipeline snapshot we make sure to use _deepcopy_with_exceptions when copying component inputs to avoid deep copies of items like components and tools since they often contain attributes that are not deep-copyable. For example, the LinkContentFetcher has httpx.Client as an attribute which throws an error if we try to deep copy it.

test/core/pipeline/test_pipeline_crash_agent_pipeline_snapshot_raised.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88

99
from haystack import Document, Pipeline, component
1010
from haystack.components.agents import Agent
11+
from haystack.components.fetchers import LinkContentFetcher
1112
from haystack.components.tools import ToolInvoker
1213
from haystack.components.writers import DocumentWriter
1314
from haystack.core.errors import PipelineRuntimeError
1415
from haystack.dataclasses import ChatMessage, ToolCall
1516
from haystack.document_stores.in_memory import InMemoryDocumentStore
1617
from haystack.document_stores.types import DuplicatePolicy
17-
from haystack.tools import Tool, Toolset, create_tool_from_function
18+
from haystack.tools import ComponentTool, Tool, Toolset, create_tool_from_function
1819

1920

2021
def calculate(expression: str) -> dict:
@@ -39,6 +40,8 @@ def factorial_failing(n: int) -> dict:
3940
function=calculate, name="calculator", outputs_to_state={"calc_result": {"source": "result"}}
4041
)
4142

43+
link_fetcher_tool = ComponentTool(component=LinkContentFetcher())
44+
4245

4346
@component
4447
class MockChatGenerator:
@@ -85,7 +88,9 @@ def test_pipeline_with_chat_generator_crash():
8588
"""Test pipeline crash handling when chat generator fails."""
8689
pipe = build_pipeline(
8790
agent=Agent(
88-
chat_generator=MockChatGenerator(True), tools=[calculator_tool], state_schema={"calc_result": {"type": int}}
91+
chat_generator=MockChatGenerator(True),
92+
tools=[calculator_tool, link_fetcher_tool],
93+
state_schema={"calc_result": {"type": int}},
8994
)
9095
)
9196

0 commit comments

Comments
 (0)