Skip to content

Commit 6524e9f

Browse files
authored
feat: use memory with pipeline template tools (#112)
1 parent 7706aca commit 6524e9f

8 files changed

Lines changed: 185 additions & 116 deletions

File tree

src/deepset_mcp/api/pipeline_template/models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,26 @@ class PipelineTemplate(BaseModel):
3131
yaml_config: str | None = Field(None, alias="query_yaml")
3232
tags: list[PipelineTemplateTag]
3333
pipeline_type: PipelineType
34+
35+
36+
class PipelineTemplateList(BaseModel):
37+
"""Response model for listing pipeline templates."""
38+
39+
data: list[PipelineTemplate]
40+
has_more: bool
41+
total: int
42+
43+
44+
class PipelineTemplateSearchResult(BaseModel):
45+
"""Model representing a search result for pipeline templates."""
46+
47+
template: PipelineTemplate
48+
similarity_score: float
49+
50+
51+
class PipelineTemplateSearchResults(BaseModel):
52+
"""Response model for pipeline template search results."""
53+
54+
results: list[PipelineTemplateSearchResult]
55+
query: str
56+
total_found: int

src/deepset_mcp/api/pipeline_template/resource.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Any
22

33
from deepset_mcp.api.exceptions import UnexpectedAPIError
4-
from deepset_mcp.api.pipeline_template.models import PipelineTemplate
4+
from deepset_mcp.api.pipeline_template.models import PipelineTemplate, PipelineTemplateList
55
from deepset_mcp.api.protocols import AsyncClientProtocol
66
from deepset_mcp.api.transport import raise_for_status
77

@@ -43,7 +43,7 @@ async def get_template(self, template_name: str) -> PipelineTemplate:
4343

4444
async def list_templates(
4545
self, limit: int = 100, field: str = "created_at", order: str = "DESC", filter: str | None = None
46-
) -> list[PipelineTemplate]:
46+
) -> PipelineTemplateList:
4747
"""List pipeline templates in the configured workspace.
4848
4949
Parameters
@@ -59,8 +59,8 @@ async def list_templates(
5959
6060
Returns
6161
-------
62-
list[PipelineTemplate]
63-
List of pipeline templates
62+
PipelineTemplateList
63+
List of pipeline templates with metadata
6464
"""
6565
params = {"limit": limit, "page_number": 1, "field": field, "order": order}
6666

@@ -80,4 +80,8 @@ async def list_templates(
8080

8181
response_data: dict[str, Any] = response.json
8282

83-
return [PipelineTemplate.model_validate(template) for template in response_data["data"]]
83+
return PipelineTemplateList(
84+
data=[PipelineTemplate.model_validate(template) for template in response_data["data"]],
85+
has_more=response_data.get("has_more", False),
86+
total=response_data.get("total", len(response_data["data"])),
87+
)

src/deepset_mcp/api/protocols.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
PipelineLogList,
1414
PipelineValidationResult,
1515
)
16-
from deepset_mcp.api.pipeline_template.models import PipelineTemplate
16+
from deepset_mcp.api.pipeline_template.models import PipelineTemplate, PipelineTemplateList
1717
from deepset_mcp.api.secrets.models import Secret, SecretList
1818
from deepset_mcp.api.shared_models import DeepsetUser, NoContentResponse
1919
from deepset_mcp.api.transport import StreamingResponse, TransportResponse
@@ -237,7 +237,7 @@ async def get_template(self, template_name: str) -> PipelineTemplate:
237237

238238
async def list_templates(
239239
self, limit: int = 100, field: str = "created_at", order: str = "DESC", filter: str | None = None
240-
) -> list[PipelineTemplate]:
240+
) -> PipelineTemplateList:
241241
"""List pipeline templates in the configured workspace."""
242242
...
243243

src/deepset_mcp/tool_factory.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ async def search_component_definitions_wrapper(client: Any, query: str, top_k: i
7575

7676

7777
# Special wrapper for search_pipeline_templates that needs the model
78-
async def search_pipeline_templates_wrapper(client: Any, workspace: str, query: str, top_k: int = 10) -> str:
78+
async def search_pipeline_templates_wrapper(client: Any, workspace: str, query: str, top_k: int = 10) -> Any:
7979
"""Searches for pipeline templates based on name or description using semantic similarity.
8080
8181
Args:
@@ -85,7 +85,7 @@ async def search_pipeline_templates_wrapper(client: Any, workspace: str, query:
8585
top_k: Maximum number of results to return (default: 10)
8686
8787
Returns:
88-
A formatted string containing the matched pipeline template definitions
88+
Search results with similarity scores or error message
8989
"""
9090
model = get_initialized_model()
9191
return await search_pipeline_templates_tool(client, query, model, workspace, top_k)
@@ -156,11 +156,17 @@ def get_workspace_from_env() -> str:
156156
deploy_index_tool,
157157
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
158158
),
159-
"list_pipeline_templates": (list_pipeline_templates_tool, ToolConfig(needs_client=True, needs_workspace=True)),
160-
"get_pipeline_template": (get_pipeline_template_tool, ToolConfig(needs_client=True, needs_workspace=True)),
159+
"list_pipeline_templates": (
160+
list_pipeline_templates_tool,
161+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
162+
),
163+
"get_pipeline_template": (
164+
get_pipeline_template_tool,
165+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
166+
),
161167
"search_pipeline_templates": (
162168
search_pipeline_templates_wrapper,
163-
ToolConfig(needs_client=True, needs_workspace=True),
169+
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
164170
),
165171
"list_custom_component_installations": (
166172
list_custom_component_installations_tool,
Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import numpy as np
22

33
from deepset_mcp.api.exceptions import ResourceNotFoundError, UnexpectedAPIError
4+
from deepset_mcp.api.pipeline_template.models import (
5+
PipelineTemplate,
6+
PipelineTemplateList,
7+
PipelineTemplateSearchResult,
8+
PipelineTemplateSearchResults,
9+
)
410
from deepset_mcp.api.protocols import AsyncClientProtocol
5-
from deepset_mcp.tools.formatting_utils import pipeline_template_to_llm_readable_string
611
from deepset_mcp.tools.model_protocol import ModelProtocol
712

813

@@ -13,7 +18,7 @@ async def list_pipeline_templates(
1318
field: str = "created_at",
1419
order: str = "DESC",
1520
filter: str | None = None,
16-
) -> str:
21+
) -> PipelineTemplateList | str:
1722
"""Retrieves a list of all available pipeline templates.
1823
1924
:param client: The async client for API requests.
@@ -23,25 +28,31 @@ async def list_pipeline_templates(
2328
:param order: Sort order, either "ASC" or "DESC" (default: "DESC").
2429
:param filter: OData filter expression to filter templates by criteria.
2530
26-
:returns: Formatted string with template information.
31+
:returns: List of pipeline templates or error message.
2732
"""
2833
try:
29-
response = await client.pipeline_templates(workspace=workspace).list_templates(
34+
return await client.pipeline_templates(workspace=workspace).list_templates(
3035
limit=limit, field=field, order=order, filter=filter
3136
)
32-
formatted_templates = [pipeline_template_to_llm_readable_string(t) for t in response]
33-
return "\n\n".join(formatted_templates)
3437
except ResourceNotFoundError:
3538
return f"There is no workspace named '{workspace}'. Did you mean to configure it?"
3639
except UnexpectedAPIError as e:
3740
return f"Failed to list pipeline templates: {e}"
3841

3942

40-
async def get_pipeline_template(client: AsyncClientProtocol, workspace: str, template_name: str) -> str:
41-
"""Fetches detailed information for a specific pipeline template, identified by its `template_name`."""
43+
async def get_pipeline_template(
44+
client: AsyncClientProtocol, workspace: str, template_name: str
45+
) -> PipelineTemplate | str:
46+
"""Fetches detailed information for a specific pipeline template, identified by its `template_name`.
47+
48+
:param client: The async client for API requests.
49+
:param workspace: The workspace to fetch template from.
50+
:param template_name: The name of the template to fetch.
51+
52+
:returns: Pipeline template details or error message.
53+
"""
4254
try:
43-
response = await client.pipeline_templates(workspace=workspace).get_template(template_name)
44-
return pipeline_template_to_llm_readable_string(response, include_yaml=True)
55+
return await client.pipeline_templates(workspace=workspace).get_template(template_name)
4556
except ResourceNotFoundError:
4657
return f"There is no pipeline template named '{template_name}' in workspace '{workspace}'."
4758
except UnexpectedAPIError as e:
@@ -50,18 +61,16 @@ async def get_pipeline_template(client: AsyncClientProtocol, workspace: str, tem
5061

5162
async def search_pipeline_templates(
5263
client: AsyncClientProtocol, query: str, model: ModelProtocol, workspace: str, top_k: int = 10
53-
) -> str:
64+
) -> PipelineTemplateSearchResults | str:
5465
"""Searches for pipeline templates based on name or description using semantic similarity.
5566
56-
Args:
57-
client: The API client to use
58-
query: The search query
59-
model: The model to use for computing embeddings
60-
workspace: The workspace to search templates from
61-
top_k: Maximum number of results to return (default: 5)
67+
:param client: The API client to use.
68+
:param query: The search query.
69+
:param model: The model to use for computing embeddings.
70+
:param workspace: The workspace to search templates from.
71+
:param top_k: Maximum number of results to return (default: 10).
6272
63-
Returns:
64-
A formatted string containing the matched pipeline template definitions
73+
:returns: Search results with similarity scores or error message.
6574
"""
6675
try:
6776
response = await client.pipeline_templates(workspace=workspace).list_templates(
@@ -70,12 +79,12 @@ async def search_pipeline_templates(
7079
except UnexpectedAPIError as e:
7180
return f"Failed to retrieve pipeline templates: {e}"
7281

73-
if not response:
74-
return "No pipeline templates found"
82+
if not response.data:
83+
return PipelineTemplateSearchResults(results=[], query=query, total_found=0)
7584

7685
# Extract text for embedding from all templates
7786
template_texts: list[tuple[str, str]] = [
78-
(template.template_name, f"{template.template_name} {template.description}") for template in response
87+
(template.template_name, f"{template.template_name} {template.description}") for template in response.data
7988
]
8089
template_names: list[str] = [t[0] for t in template_texts]
8190

@@ -96,12 +105,11 @@ async def search_pipeline_templates(
96105
template_similarities.sort(key=lambda x: x[1], reverse=True)
97106

98107
top_templates = template_similarities[:top_k]
99-
results = []
108+
search_results = []
100109
for template_name, sim in top_templates:
101110
# Find the template object by name
102-
template = next((t for t in response if t.template_name == template_name), None)
111+
template = next((t for t in response.data if t.template_name == template_name), None)
103112
if template:
104-
template_str = pipeline_template_to_llm_readable_string(template)
105-
results.append(f"Similarity Score: {sim:.3f}\n{template_str}\n{'-' * 80}\n")
113+
search_results.append(PipelineTemplateSearchResult(template=template, similarity_score=float(sim)))
106114

107-
return "\n".join(results)
115+
return PipelineTemplateSearchResults(results=search_results, query=query, total_found=len(search_results))

test/integration/test_integration_pipeline_template_resource.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from deepset_mcp.api.client import AsyncDeepsetClient
44
from deepset_mcp.api.exceptions import ResourceNotFoundError
5-
from deepset_mcp.api.pipeline_template.models import PipelineTemplate
5+
from deepset_mcp.api.pipeline_template.models import PipelineTemplate, PipelineTemplateList
66
from deepset_mcp.api.pipeline_template.resource import PipelineTemplateResource
77

88
pytestmark = pytest.mark.integration
@@ -26,14 +26,14 @@ async def test_get_template(
2626
First lists all templates, then gets the first one by name.
2727
"""
2828
# Get all templates to find an existing one
29-
templates = await template_resource.list_templates()
29+
templates_list = await template_resource.list_templates()
3030

3131
# Skip if no templates are available
32-
if not templates:
32+
if not templates_list.data:
3333
pytest.skip("No templates available in the test environment")
3434

3535
# Get the first template's name
36-
template_name = templates[0].template_name
36+
template_name = templates_list.data[0].template_name
3737

3838
# Now get that specific template
3939
template = await template_resource.get_template(template_name=template_name)
@@ -64,17 +64,18 @@ async def test_list_templates(
6464
) -> None:
6565
"""Test listing templates."""
6666
# Test listing templates with default limit
67-
templates = await template_resource.list_templates()
67+
templates_list = await template_resource.list_templates()
6868

69-
# Verify that the templates are returned as a list
70-
assert isinstance(templates, list)
69+
# Verify that the templates are returned as a PipelineTemplateList
70+
assert isinstance(templates_list, PipelineTemplateList)
71+
assert isinstance(templates_list.data, list)
7172

7273
# Skip further checks if no templates are available
73-
if not templates:
74+
if not templates_list.data:
7475
pytest.skip("No templates available in the test environment")
7576

7677
# Verify the first template has the expected structure
77-
template = templates[0]
78+
template = templates_list.data[0]
7879
assert isinstance(template, PipelineTemplate)
7980
assert template.template_name is not None
8081
assert template.author is not None
@@ -89,10 +90,11 @@ async def test_list_templates_with_limit(
8990
"""Test listing templates with a specific limit."""
9091
# Test with a small limit
9192
limit = 1
92-
templates = await template_resource.list_templates(limit=limit)
93+
templates_list = await template_resource.list_templates(limit=limit)
9394

9495
# Verify that the number of templates is not more than the limit
95-
assert len(templates) <= limit
96+
assert isinstance(templates_list, PipelineTemplateList)
97+
assert len(templates_list.data) <= limit
9698

9799

98100
@pytest.mark.asyncio
@@ -101,24 +103,26 @@ async def test_list_templates_with_filter(
101103
) -> None:
102104
"""Test listing templates with a pipeline type filter."""
103105
# Test filtering by QUERY pipeline type
104-
query_templates = await template_resource.list_templates(filter="pipeline_type eq 'QUERY'")
106+
query_templates_list = await template_resource.list_templates(filter="pipeline_type eq 'QUERY'")
105107

106108
# Verify that all returned templates are QUERY type
107-
assert isinstance(query_templates, list)
109+
assert isinstance(query_templates_list, PipelineTemplateList)
110+
assert isinstance(query_templates_list.data, list)
108111

109112
# If templates are available, verify they are all QUERY type
110-
for template in query_templates:
113+
for template in query_templates_list.data:
111114
assert isinstance(template, PipelineTemplate)
112115
assert template.pipeline_type == "query"
113116

114117
# Test filtering by INDEXING pipeline type
115-
indexing_templates = await template_resource.list_templates(filter="pipeline_type eq 'INDEXING'")
118+
indexing_templates_list = await template_resource.list_templates(filter="pipeline_type eq 'INDEXING'")
116119

117120
# Verify that all returned templates are INDEXING type
118-
assert isinstance(indexing_templates, list)
121+
assert isinstance(indexing_templates_list, PipelineTemplateList)
122+
assert isinstance(indexing_templates_list.data, list)
119123

120124
# If templates are available, verify they are all INDEXING type
121-
for template in indexing_templates:
125+
for template in indexing_templates_list.data:
122126
assert isinstance(template, PipelineTemplate)
123127
assert template.pipeline_type == "indexing"
124128

@@ -129,12 +133,13 @@ async def test_list_templates_with_custom_sorting(
129133
) -> None:
130134
"""Test listing templates with custom sorting."""
131135
# Test sorting by name in ascending order
132-
templates = await template_resource.list_templates(field="name", order="ASC", limit=5)
136+
templates_list = await template_resource.list_templates(field="name", order="ASC", limit=5)
133137

134-
# Verify that the templates are returned as a list
135-
assert isinstance(templates, list)
138+
# Verify that the templates are returned as a PipelineTemplateList
139+
assert isinstance(templates_list, PipelineTemplateList)
140+
assert isinstance(templates_list.data, list)
136141

137142
# If we have multiple templates, verify they are sorted correctly
138-
if len(templates) > 1:
139-
for i in range(len(templates) - 1):
140-
assert templates[i].display_name <= templates[i + 1].display_name
143+
if len(templates_list.data) > 1:
144+
for i in range(len(templates_list.data) - 1):
145+
assert templates_list.data[i].display_name <= templates_list.data[i + 1].display_name

0 commit comments

Comments
 (0)