langchain-channel3 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_channel3-0.1.0/.gitignore +6 -0
- langchain_channel3-0.1.0/LICENSE +21 -0
- langchain_channel3-0.1.0/PKG-INFO +110 -0
- langchain_channel3-0.1.0/README.md +91 -0
- langchain_channel3-0.1.0/langchain_channel3/__init__.py +28 -0
- langchain_channel3-0.1.0/langchain_channel3/_format.py +107 -0
- langchain_channel3-0.1.0/langchain_channel3/py.typed +0 -0
- langchain_channel3-0.1.0/langchain_channel3/toolkit.py +43 -0
- langchain_channel3-0.1.0/langchain_channel3/tools.py +306 -0
- langchain_channel3-0.1.0/pyproject.toml +54 -0
- langchain_channel3-0.1.0/tests/__init__.py +0 -0
- langchain_channel3-0.1.0/tests/integration_tests/__init__.py +0 -0
- langchain_channel3-0.1.0/tests/integration_tests/test_tools.py +37 -0
- langchain_channel3-0.1.0/tests/unit_tests/__init__.py +0 -0
- langchain_channel3-0.1.0/tests/unit_tests/test_tools.py +31 -0
- langchain_channel3-0.1.0/uv.lock +2178 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Channel3
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: langchain-channel3
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Channel3 product search tools for LangChain: search 100M+ products and compare live retailer offers.
|
|
5
|
+
Project-URL: Homepage, https://channel3.ai/docs/langchain
|
|
6
|
+
Project-URL: Documentation, https://channel3.ai/docs/langchain
|
|
7
|
+
Project-URL: Source Code, https://github.com/channel3-ai/langchain-channel3
|
|
8
|
+
Project-URL: repository, https://github.com/channel3-ai/langchain-channel3
|
|
9
|
+
Author-email: Channel3 <support@trychannel3.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agents,channel3,ecommerce,langchain,product-search,shopping,tools
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: <4.0,>=3.10
|
|
16
|
+
Requires-Dist: channel3-sdk<5.0.0,>=4.8.0
|
|
17
|
+
Requires-Dist: langchain-core<2.0.0,>=1.0.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# langchain-channel3
|
|
21
|
+
|
|
22
|
+
[Channel3](https://channel3.ai) product search tools for [LangChain](https://python.langchain.com). Give your agent search across 100M+ products from thousands of retailers, with every retailer's offer and live price.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install -U langchain-channel3
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Set `CHANNEL3_API_KEY` in your environment. [Create a free API key](https://channel3.ai/sign-up/developer): every account gets 1,000 free credits a month. A search uses one credit; product details by ID are free, and each retailer URL lookup uses one credit.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from langchain.agents import create_agent
|
|
36
|
+
from langchain_channel3 import channel3_tools
|
|
37
|
+
|
|
38
|
+
agent = create_agent(
|
|
39
|
+
model="anthropic:claude-sonnet-5-5",
|
|
40
|
+
tools=channel3_tools(),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
result = agent.invoke(
|
|
44
|
+
{
|
|
45
|
+
"messages": [
|
|
46
|
+
{
|
|
47
|
+
"role": "user",
|
|
48
|
+
"content": "Find me a leather golf glove under $40 and tell me which retailer has it cheapest.",
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
print(result["messages"][-1].content)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
### `Channel3SearchProducts` (`search_products`)
|
|
59
|
+
|
|
60
|
+
Searches for one product type and returns in-stock products, each with up to two images and every retailer's offer.
|
|
61
|
+
|
|
62
|
+
- `query`: one product type and its constraints in natural language, such as `"red leather jacket under $200"`.
|
|
63
|
+
- `image_url`: a public image URL for visual search. Combine it with `query` for text plus image.
|
|
64
|
+
|
|
65
|
+
At least one is required.
|
|
66
|
+
|
|
67
|
+
### `Channel3GetProducts` (`get_products`)
|
|
68
|
+
|
|
69
|
+
Gets full product data (description, attributes, variants, images and offers) for up to 40 products in parallel.
|
|
70
|
+
|
|
71
|
+
- `product_ids`: product IDs from `search_products`, or retailer product URLs.
|
|
72
|
+
|
|
73
|
+
Inputs that can't be resolved are listed in `unresolved`. The call fails only when none resolve.
|
|
74
|
+
|
|
75
|
+
Both tools use `response_format="content_and_artifact"`. For `search_products` the model sees one line per product with title, brand, best price and ID; for `get_products` it sees JSON. The full structured result is on the `ToolMessage` as `artifact`, so your UI can render product cards from it.
|
|
76
|
+
|
|
77
|
+
Offer URLs are Channel3 buy links, so purchases earn affiliate commission for the API key's account.
|
|
78
|
+
|
|
79
|
+
## Options
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from langchain_channel3 import Channel3Toolkit
|
|
83
|
+
|
|
84
|
+
tools = Channel3Toolkit(api_key="...", conversation_id=chat_id, limit=12).get_tools()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The tool classes take the same options individually, for example `Channel3SearchProducts(limit=12)`.
|
|
88
|
+
|
|
89
|
+
| Option | Description |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `api_key` | Channel3 API key. Defaults to `CHANNEL3_API_KEY`. |
|
|
92
|
+
| `conversation_id` | Groups one chat's calls together in your Channel3 analytics. |
|
|
93
|
+
| `limit` | Products per search, from 1 to 30. Defaults to 8. Search tool only. |
|
|
94
|
+
| `base_url` | Overrides the API base URL. |
|
|
95
|
+
|
|
96
|
+
`channel3_tools(**options)` is shorthand for `Channel3Toolkit(**options).get_tools()`.
|
|
97
|
+
|
|
98
|
+
## Errors
|
|
99
|
+
|
|
100
|
+
API errors go back to the model as the tool result, for example "Channel3 rejected the API key" or "The Channel3 account is out of credits", so the agent can explain the problem instead of crashing the run. Pass `handle_tool_error=False` to a tool to raise them as `ToolException` instead.
|
|
101
|
+
|
|
102
|
+
## Links
|
|
103
|
+
|
|
104
|
+
- [Channel3 docs](https://channel3.ai/docs)
|
|
105
|
+
- [TypeScript version](https://github.com/channel3-ai/agent-toolkit) for LangChain.js, the Vercel AI SDK and Mastra
|
|
106
|
+
- [Channel3 Python SDK](https://pypi.org/project/channel3-sdk/) for the rest of the API
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# langchain-channel3
|
|
2
|
+
|
|
3
|
+
[Channel3](https://channel3.ai) product search tools for [LangChain](https://python.langchain.com). Give your agent search across 100M+ products from thousands of retailers, with every retailer's offer and live price.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -U langchain-channel3
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Set `CHANNEL3_API_KEY` in your environment. [Create a free API key](https://channel3.ai/sign-up/developer): every account gets 1,000 free credits a month. A search uses one credit; product details by ID are free, and each retailer URL lookup uses one credit.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from langchain.agents import create_agent
|
|
17
|
+
from langchain_channel3 import channel3_tools
|
|
18
|
+
|
|
19
|
+
agent = create_agent(
|
|
20
|
+
model="anthropic:claude-sonnet-5-5",
|
|
21
|
+
tools=channel3_tools(),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
result = agent.invoke(
|
|
25
|
+
{
|
|
26
|
+
"messages": [
|
|
27
|
+
{
|
|
28
|
+
"role": "user",
|
|
29
|
+
"content": "Find me a leather golf glove under $40 and tell me which retailer has it cheapest.",
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
print(result["messages"][-1].content)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Tools
|
|
38
|
+
|
|
39
|
+
### `Channel3SearchProducts` (`search_products`)
|
|
40
|
+
|
|
41
|
+
Searches for one product type and returns in-stock products, each with up to two images and every retailer's offer.
|
|
42
|
+
|
|
43
|
+
- `query`: one product type and its constraints in natural language, such as `"red leather jacket under $200"`.
|
|
44
|
+
- `image_url`: a public image URL for visual search. Combine it with `query` for text plus image.
|
|
45
|
+
|
|
46
|
+
At least one is required.
|
|
47
|
+
|
|
48
|
+
### `Channel3GetProducts` (`get_products`)
|
|
49
|
+
|
|
50
|
+
Gets full product data (description, attributes, variants, images and offers) for up to 40 products in parallel.
|
|
51
|
+
|
|
52
|
+
- `product_ids`: product IDs from `search_products`, or retailer product URLs.
|
|
53
|
+
|
|
54
|
+
Inputs that can't be resolved are listed in `unresolved`. The call fails only when none resolve.
|
|
55
|
+
|
|
56
|
+
Both tools use `response_format="content_and_artifact"`. For `search_products` the model sees one line per product with title, brand, best price and ID; for `get_products` it sees JSON. The full structured result is on the `ToolMessage` as `artifact`, so your UI can render product cards from it.
|
|
57
|
+
|
|
58
|
+
Offer URLs are Channel3 buy links, so purchases earn affiliate commission for the API key's account.
|
|
59
|
+
|
|
60
|
+
## Options
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from langchain_channel3 import Channel3Toolkit
|
|
64
|
+
|
|
65
|
+
tools = Channel3Toolkit(api_key="...", conversation_id=chat_id, limit=12).get_tools()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The tool classes take the same options individually, for example `Channel3SearchProducts(limit=12)`.
|
|
69
|
+
|
|
70
|
+
| Option | Description |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `api_key` | Channel3 API key. Defaults to `CHANNEL3_API_KEY`. |
|
|
73
|
+
| `conversation_id` | Groups one chat's calls together in your Channel3 analytics. |
|
|
74
|
+
| `limit` | Products per search, from 1 to 30. Defaults to 8. Search tool only. |
|
|
75
|
+
| `base_url` | Overrides the API base URL. |
|
|
76
|
+
|
|
77
|
+
`channel3_tools(**options)` is shorthand for `Channel3Toolkit(**options).get_tools()`.
|
|
78
|
+
|
|
79
|
+
## Errors
|
|
80
|
+
|
|
81
|
+
API errors go back to the model as the tool result, for example "Channel3 rejected the API key" or "The Channel3 account is out of credits", so the agent can explain the problem instead of crashing the run. Pass `handle_tool_error=False` to a tool to raise them as `ToolException` instead.
|
|
82
|
+
|
|
83
|
+
## Links
|
|
84
|
+
|
|
85
|
+
- [Channel3 docs](https://channel3.ai/docs)
|
|
86
|
+
- [TypeScript version](https://github.com/channel3-ai/agent-toolkit) for LangChain.js, the Vercel AI SDK and Mastra
|
|
87
|
+
- [Channel3 Python SDK](https://pypi.org/project/channel3-sdk/) for the rest of the API
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Channel3 product search tools for LangChain."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from langchain_core.tools import BaseTool
|
|
6
|
+
|
|
7
|
+
from langchain_channel3.toolkit import Channel3Toolkit
|
|
8
|
+
from langchain_channel3.tools import (
|
|
9
|
+
Channel3GetProducts,
|
|
10
|
+
Channel3SearchProducts,
|
|
11
|
+
GetProductsInput,
|
|
12
|
+
SearchProductsInput,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def channel3_tools(**kwargs: Any) -> list[BaseTool]:
|
|
17
|
+
"""Both Channel3 tools, sharing the options ``Channel3Toolkit`` accepts."""
|
|
18
|
+
return Channel3Toolkit(**kwargs).get_tools()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Channel3GetProducts",
|
|
23
|
+
"Channel3SearchProducts",
|
|
24
|
+
"Channel3Toolkit",
|
|
25
|
+
"GetProductsInput",
|
|
26
|
+
"SearchProductsInput",
|
|
27
|
+
"channel3_tools",
|
|
28
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Shapes Channel3 SDK products into compact, JSON-safe dicts for agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from channel3_sdk.types.product import Product
|
|
8
|
+
|
|
9
|
+
CDN_IMAGE_PREFIX = "https://cdn.trychannel3.com/"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _cdn_images(product: Product) -> list[dict[str, Any]]:
|
|
13
|
+
return [
|
|
14
|
+
image.model_dump(exclude_none=True)
|
|
15
|
+
for image in product.images or []
|
|
16
|
+
if image.url.startswith(CDN_IMAGE_PREFIX)
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _with_cleaned_url(image: dict[str, Any]) -> dict[str, Any]:
|
|
21
|
+
cleaned = image.get("cleaned_url")
|
|
22
|
+
return {**image, "url": cleaned} if cleaned else image
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _summary_images(product: Product) -> list[dict[str, Any]]:
|
|
26
|
+
"""Up to two display images: the background-removed or main image first, then one other."""
|
|
27
|
+
images = _cdn_images(product)
|
|
28
|
+
primary_source = (
|
|
29
|
+
next((i for i in images if i.get("cleaned_url")), None)
|
|
30
|
+
or next((i for i in images if i.get("is_main_image")), None)
|
|
31
|
+
or (images[0] if images else None)
|
|
32
|
+
)
|
|
33
|
+
if primary_source is None:
|
|
34
|
+
return []
|
|
35
|
+
primary = _with_cleaned_url(primary_source)
|
|
36
|
+
second = next(
|
|
37
|
+
(
|
|
38
|
+
i
|
|
39
|
+
for i in images
|
|
40
|
+
if i is not primary_source
|
|
41
|
+
and i["url"] != primary["url"]
|
|
42
|
+
and i.get("cleaned_url") != primary["url"]
|
|
43
|
+
),
|
|
44
|
+
None,
|
|
45
|
+
)
|
|
46
|
+
return [primary, _with_cleaned_url(second)] if second else [primary]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _offers(product: Product) -> list[dict[str, Any]]:
|
|
50
|
+
return [
|
|
51
|
+
offer.model_dump(mode="json", exclude_none=True, exclude={"max_commission_rate"})
|
|
52
|
+
for offer in product.offers or []
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def product_summary(product: Product) -> dict[str, Any]:
|
|
57
|
+
return {
|
|
58
|
+
"id": product.id,
|
|
59
|
+
"title": product.title,
|
|
60
|
+
"brands": [
|
|
61
|
+
brand.model_dump(mode="json", exclude_none=True) for brand in product.brands or []
|
|
62
|
+
],
|
|
63
|
+
"images": _summary_images(product),
|
|
64
|
+
"offers": _offers(product),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def product_detail(product: Product) -> dict[str, Any]:
|
|
69
|
+
detail = product.model_dump(mode="json", exclude_none=True, exclude={"images", "offers"})
|
|
70
|
+
detail["images"] = _cdn_images(product)[:5]
|
|
71
|
+
detail["offers"] = _offers(product)
|
|
72
|
+
return detail
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _lead_offer(offers: list[dict[str, Any]]) -> dict[str, Any] | None:
|
|
76
|
+
"""The lowest-priced in-stock offer, or the lowest-priced offer when none are in stock."""
|
|
77
|
+
by_price = sorted(offers, key=lambda offer: offer["price"]["price"])
|
|
78
|
+
return next((o for o in by_price if o.get("availability") == "InStock"), None) or (
|
|
79
|
+
by_price[0] if by_price else None
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _format_price(amount: float, currency: str) -> str:
|
|
84
|
+
return f"${amount:,.2f}" if currency == "USD" else f"{currency} {amount:,.2f}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def product_line(product: dict[str, Any]) -> str:
|
|
88
|
+
"""One line for the model: title, brands, best price and retailer, and the ID."""
|
|
89
|
+
parts = [f'"{product["title"]}"']
|
|
90
|
+
brands = ", ".join(brand["name"] for brand in product.get("brands") or [])
|
|
91
|
+
if brands:
|
|
92
|
+
parts.append(brands)
|
|
93
|
+
best = _lead_offer(product.get("offers") or [])
|
|
94
|
+
if best:
|
|
95
|
+
parts.append(
|
|
96
|
+
f"{_format_price(best['price']['price'], best['price']['currency'])} at {best['domain']}"
|
|
97
|
+
)
|
|
98
|
+
return f"- {' — '.join(parts)} [id: {product['id']}]"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def summarize_search(
|
|
102
|
+
query: str | None, image_url: str | None, products: list[dict[str, Any]]
|
|
103
|
+
) -> str:
|
|
104
|
+
what = f' for "{query}"' if query else " for the image" if image_url else ""
|
|
105
|
+
return "\n".join(
|
|
106
|
+
[f"Found {len(products)} products{what}.", *(product_line(p) for p in products)]
|
|
107
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Both Channel3 tools with shared options."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from langchain_core.tools import BaseTool, BaseToolkit
|
|
8
|
+
from pydantic import Field, SecretStr
|
|
9
|
+
|
|
10
|
+
from langchain_channel3.tools import Channel3GetProducts, Channel3SearchProducts
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Channel3Toolkit(BaseToolkit):
|
|
14
|
+
"""Channel3 product search and product detail tools.
|
|
15
|
+
|
|
16
|
+
Setup:
|
|
17
|
+
Install ``langchain-channel3`` and set ``CHANNEL3_API_KEY``.
|
|
18
|
+
|
|
19
|
+
Instantiate:
|
|
20
|
+
.. code-block:: python
|
|
21
|
+
|
|
22
|
+
from langchain_channel3 import Channel3Toolkit
|
|
23
|
+
|
|
24
|
+
toolkit = Channel3Toolkit(conversation_id=chat_id)
|
|
25
|
+
tools = toolkit.get_tools()
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
api_key: Optional[SecretStr] = Field(default=None, exclude=True)
|
|
29
|
+
"""Channel3 API key. Defaults to the ``CHANNEL3_API_KEY`` environment variable."""
|
|
30
|
+
base_url: Optional[str] = None
|
|
31
|
+
"""Overrides the API base URL."""
|
|
32
|
+
conversation_id: Optional[str] = None
|
|
33
|
+
"""Groups one chat's calls together in your Channel3 analytics."""
|
|
34
|
+
limit: int = Field(default=8, ge=1, le=30)
|
|
35
|
+
"""Maximum products per search."""
|
|
36
|
+
|
|
37
|
+
def get_tools(self) -> list[BaseTool]:
|
|
38
|
+
shared = {
|
|
39
|
+
"api_key": self.api_key,
|
|
40
|
+
"base_url": self.base_url,
|
|
41
|
+
"conversation_id": self.conversation_id,
|
|
42
|
+
}
|
|
43
|
+
return [Channel3SearchProducts(**shared, limit=self.limit), Channel3GetProducts(**shared)]
|