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.
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ dist/
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .ruff_cache/
@@ -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)]