langchain-animeapi 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,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .venv/
7
+ .env
8
+ .ruff_cache/
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-11-12)
4
+
5
+ - Initial release: `AnimeAPIGenerateImage`, a LangChain tool (sync and async) that returns the image URL, price and balance.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AnimeAPI
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,117 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-animeapi
3
+ Version: 0.1.0
4
+ Summary: AnimeAPI tool for LangChain and LangGraph agents: anime image generation at $0.003 per image.
5
+ Project-URL: Homepage, https://animeapi.ai
6
+ Project-URL: Documentation, https://animeapi.ai/docs
7
+ Project-URL: Repository, https://github.com/animeapi-ai/langchain-animeapi
8
+ Project-URL: Issues, https://github.com/animeapi-ai/langchain-animeapi/issues
9
+ Author-email: AnimeAPI <contact@animeapi.ai>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,ai,anime,animeapi,image-generation,langchain,langgraph,text-to-image,tool
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Multimedia :: Graphics
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: animeapi-sdk<1,>=0.1.2
22
+ Requires-Dist: langchain-core<2,>=0.3.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ # langchain-animeapi
26
+
27
+ [AnimeAPI](https://animeapi.ai) tool for [LangChain](https://github.com/langchain-ai/langchain) and LangGraph agents: anime image generation at **$0.003 per image**, one of the cheapest anime image APIs. Prepaid balance from $5 that never expires, no subscription; new accounts get 3 free images without a card.
28
+
29
+ ```bash
30
+ pip install langchain-animeapi
31
+ ```
32
+
33
+ Create a key (it starts with `ank_`) at <https://animeapi.ai/dashboard/api-keys> and set it as `ANIMEAPI_API_KEY`.
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ from langchain_animeapi import AnimeAPIGenerateImage
39
+
40
+ tool = AnimeAPIGenerateImage() # reads ANIMEAPI_API_KEY
41
+
42
+ print(tool.invoke({"prompt": "a shrine maiden sweeping autumn leaves", "orientation": "landscape"}))
43
+ # Image generated: https://animeapi.ai/i/….webp
44
+ # Model anime, landscape, $0.003. Balance left: $4.997.
45
+ # The link is not permanent storage; download the image to keep it.
46
+ ```
47
+
48
+ ## With an agent
49
+
50
+ ```python
51
+ from langchain.agents import create_agent
52
+ from langchain_animeapi import AnimeAPIGenerateImage
53
+
54
+ agent = create_agent(model, tools=[AnimeAPIGenerateImage()]) # model: your chat model
55
+ result = agent.invoke({"messages": [{"role": "user", "content": "Draw a chibi cat barista, square."}]})
56
+ ```
57
+
58
+ Any chat model that supports tool calling works: `model.bind_tools([AnimeAPIGenerateImage()])`. The model writes the prompt and picks the shape. When it calls the tool, the `ToolMessage` carries a short text for the model and the details as `artifact`:
59
+
60
+ ```python
61
+ msg = tool.invoke({
62
+ "type": "tool_call", "id": "call_1", "name": "animeapi_generate_image",
63
+ "args": {"prompt": "a chibi cat barista holding a latte", "orientation": "square"},
64
+ })
65
+ msg.artifact["image_url"] # https://animeapi.ai/i/….webp
66
+ msg.artifact["enhanced_prompt"] # the tags the model received
67
+ msg.artifact["price_usd"] # 0.003
68
+ msg.artifact["balance_usd"] # balance after this image
69
+ ```
70
+
71
+ `ainvoke` works the same way for async agents.
72
+
73
+ ## Options
74
+
75
+ | Option | Default | What it does |
76
+ | --- | --- | --- |
77
+ | `api_key` | `ANIMEAPI_API_KEY` | Your key. Stored as a secret, so it does not show up in `repr()` or traces. |
78
+ | `model` | `"anime"` | `"anime"` $0.003, `"ultra"` $0.01, `"ultra-max"` $0.02 per image. Set by you, not by the language model, so an agent cannot pick a more expensive one. |
79
+ | `enhance` | `True` | Rewrites plain words into tags for the anime model. |
80
+ | `timeout` | `120` | Seconds to wait for one request. |
81
+ | `max_retries` | `2` | Retries for failures the API marks as retryable. |
82
+
83
+ What the language model can set per call: `prompt` (up to 1000 characters) and `orientation` (`portrait`, `square` or `landscape`; same price).
84
+
85
+ AnimeAPI does not generate adult content. Explicit prompts are refused and not charged.
86
+
87
+ ## Errors
88
+
89
+ A failed call does not raise inside your agent: the tool answers with an error `ToolMessage` carrying AnimeAPI's own message, so the model can tell the user what happened (prompt refused, wrong key, empty balance). Set `handle_tool_error=False` to get a `ToolException` instead.
90
+
91
+ ## Keeping the image
92
+
93
+ Image URLs are not permanent storage. Download what you want to keep, for example with the SDK this package is built on:
94
+
95
+ ```python
96
+ from animeapi import AnimeAPI
97
+
98
+ AnimeAPI().download(msg.artifact["image_url"]).save("cat.webp")
99
+ ```
100
+
101
+ ## Prices
102
+
103
+ | Model | Price per image | Use it for |
104
+ | --- | --- | --- |
105
+ | `anime` | **$0.003** | Characters, portraits, avatars, scenes. About 4 seconds. |
106
+ | `ultra` | $0.01 | Long, detailed prompts; legible text in the image. |
107
+ | `ultra-max` | $0.02 | Highest detail. |
108
+
109
+ ## Tested
110
+
111
+ Run against the live API with `langchain-core` 1.8.0 on 2026-11-12: a direct call, a tool call (returns a `ToolMessage` with the artifact), an async tool call, a refused prompt and a wrong key (both come back as error messages, uncharged).
112
+
113
+ ## Links
114
+
115
+ - AnimeAPI: <https://animeapi.ai> · [Docs](https://animeapi.ai/docs) · [API reference](https://animeapi.ai/docs/api-reference)
116
+ - Python SDK: [animeapi-sdk](https://github.com/animeapi-ai/animeapi-python) · MCP server for agents: <https://animeapi.ai/mcp>
117
+ - Licence: [MIT](LICENSE)
@@ -0,0 +1,93 @@
1
+ # langchain-animeapi
2
+
3
+ [AnimeAPI](https://animeapi.ai) tool for [LangChain](https://github.com/langchain-ai/langchain) and LangGraph agents: anime image generation at **$0.003 per image**, one of the cheapest anime image APIs. Prepaid balance from $5 that never expires, no subscription; new accounts get 3 free images without a card.
4
+
5
+ ```bash
6
+ pip install langchain-animeapi
7
+ ```
8
+
9
+ Create a key (it starts with `ank_`) at <https://animeapi.ai/dashboard/api-keys> and set it as `ANIMEAPI_API_KEY`.
10
+
11
+ ## Quick start
12
+
13
+ ```python
14
+ from langchain_animeapi import AnimeAPIGenerateImage
15
+
16
+ tool = AnimeAPIGenerateImage() # reads ANIMEAPI_API_KEY
17
+
18
+ print(tool.invoke({"prompt": "a shrine maiden sweeping autumn leaves", "orientation": "landscape"}))
19
+ # Image generated: https://animeapi.ai/i/….webp
20
+ # Model anime, landscape, $0.003. Balance left: $4.997.
21
+ # The link is not permanent storage; download the image to keep it.
22
+ ```
23
+
24
+ ## With an agent
25
+
26
+ ```python
27
+ from langchain.agents import create_agent
28
+ from langchain_animeapi import AnimeAPIGenerateImage
29
+
30
+ agent = create_agent(model, tools=[AnimeAPIGenerateImage()]) # model: your chat model
31
+ result = agent.invoke({"messages": [{"role": "user", "content": "Draw a chibi cat barista, square."}]})
32
+ ```
33
+
34
+ Any chat model that supports tool calling works: `model.bind_tools([AnimeAPIGenerateImage()])`. The model writes the prompt and picks the shape. When it calls the tool, the `ToolMessage` carries a short text for the model and the details as `artifact`:
35
+
36
+ ```python
37
+ msg = tool.invoke({
38
+ "type": "tool_call", "id": "call_1", "name": "animeapi_generate_image",
39
+ "args": {"prompt": "a chibi cat barista holding a latte", "orientation": "square"},
40
+ })
41
+ msg.artifact["image_url"] # https://animeapi.ai/i/….webp
42
+ msg.artifact["enhanced_prompt"] # the tags the model received
43
+ msg.artifact["price_usd"] # 0.003
44
+ msg.artifact["balance_usd"] # balance after this image
45
+ ```
46
+
47
+ `ainvoke` works the same way for async agents.
48
+
49
+ ## Options
50
+
51
+ | Option | Default | What it does |
52
+ | --- | --- | --- |
53
+ | `api_key` | `ANIMEAPI_API_KEY` | Your key. Stored as a secret, so it does not show up in `repr()` or traces. |
54
+ | `model` | `"anime"` | `"anime"` $0.003, `"ultra"` $0.01, `"ultra-max"` $0.02 per image. Set by you, not by the language model, so an agent cannot pick a more expensive one. |
55
+ | `enhance` | `True` | Rewrites plain words into tags for the anime model. |
56
+ | `timeout` | `120` | Seconds to wait for one request. |
57
+ | `max_retries` | `2` | Retries for failures the API marks as retryable. |
58
+
59
+ What the language model can set per call: `prompt` (up to 1000 characters) and `orientation` (`portrait`, `square` or `landscape`; same price).
60
+
61
+ AnimeAPI does not generate adult content. Explicit prompts are refused and not charged.
62
+
63
+ ## Errors
64
+
65
+ A failed call does not raise inside your agent: the tool answers with an error `ToolMessage` carrying AnimeAPI's own message, so the model can tell the user what happened (prompt refused, wrong key, empty balance). Set `handle_tool_error=False` to get a `ToolException` instead.
66
+
67
+ ## Keeping the image
68
+
69
+ Image URLs are not permanent storage. Download what you want to keep, for example with the SDK this package is built on:
70
+
71
+ ```python
72
+ from animeapi import AnimeAPI
73
+
74
+ AnimeAPI().download(msg.artifact["image_url"]).save("cat.webp")
75
+ ```
76
+
77
+ ## Prices
78
+
79
+ | Model | Price per image | Use it for |
80
+ | --- | --- | --- |
81
+ | `anime` | **$0.003** | Characters, portraits, avatars, scenes. About 4 seconds. |
82
+ | `ultra` | $0.01 | Long, detailed prompts; legible text in the image. |
83
+ | `ultra-max` | $0.02 | Highest detail. |
84
+
85
+ ## Tested
86
+
87
+ Run against the live API with `langchain-core` 1.8.0 on 2026-11-12: a direct call, a tool call (returns a `ToolMessage` with the artifact), an async tool call, a refused prompt and a wrong key (both come back as error messages, uncharged).
88
+
89
+ ## Links
90
+
91
+ - AnimeAPI: <https://animeapi.ai> · [Docs](https://animeapi.ai/docs) · [API reference](https://animeapi.ai/docs/api-reference)
92
+ - Python SDK: [animeapi-sdk](https://github.com/animeapi-ai/animeapi-python) · MCP server for agents: <https://animeapi.ai/mcp>
93
+ - Licence: [MIT](LICENSE)
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langchain-animeapi"
7
+ dynamic = ["version"]
8
+ description = "AnimeAPI tool for LangChain and LangGraph agents: anime image generation at $0.003 per image."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ authors = [{ name = "AnimeAPI", email = "contact@animeapi.ai" }]
14
+ keywords = ["langchain", "langgraph", "tool", "agent", "anime", "image-generation", "text-to-image", "animeapi", "ai"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Multimedia :: Graphics",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = ["langchain-core>=0.3.0,<2", "animeapi-sdk>=0.1.2,<1"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://animeapi.ai"
28
+ Documentation = "https://animeapi.ai/docs"
29
+ Repository = "https://github.com/animeapi-ai/langchain-animeapi"
30
+ Issues = "https://github.com/animeapi-ai/langchain-animeapi/issues"
31
+
32
+ [tool.hatch.version]
33
+ path = "src/langchain_animeapi/_version.py"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/langchain_animeapi"]
37
+
38
+ [tool.hatch.build.targets.sdist]
39
+ include = ["src", "README.md", "LICENSE", "CHANGELOG.md", "pyproject.toml"]
40
+
41
+ [tool.ruff]
42
+ line-length = 110
43
+ target-version = "py39"
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "I", "B"]
@@ -0,0 +1,14 @@
1
+ """AnimeAPI tool for LangChain and LangGraph agents (https://animeapi.ai).
2
+
3
+ Anime image generation at $0.003 per image.
4
+
5
+ from langchain_animeapi import AnimeAPIGenerateImage
6
+
7
+ tool = AnimeAPIGenerateImage() # reads ANIMEAPI_API_KEY
8
+ print(tool.invoke({"prompt": "a silver-haired girl reading under a cherry tree"}))
9
+ """
10
+
11
+ from ._version import __version__
12
+ from .tools import AnimeAPIGenerateImage, AnimeAPIGenerateImageInput
13
+
14
+ __all__ = ["AnimeAPIGenerateImage", "AnimeAPIGenerateImageInput", "__version__"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Literal, Optional, Tuple, Type
4
+
5
+ from animeapi import AnimeAPI, AnimeAPIError, AsyncAnimeAPI
6
+ from langchain_core.callbacks import AsyncCallbackManagerForToolRun, CallbackManagerForToolRun
7
+ from langchain_core.tools import BaseTool, ToolException
8
+ from pydantic import BaseModel, Field, SecretStr
9
+
10
+ PRICES_USD = {"anime": 0.003, "ultra": 0.01, "ultra-max": 0.02}
11
+
12
+
13
+ class AnimeAPIGenerateImageInput(BaseModel):
14
+ """What the model fills in when it calls the tool."""
15
+
16
+ prompt: str = Field(
17
+ description=(
18
+ "What to draw: subject, appearance, pose, setting, lighting. "
19
+ "Plain English works best. At most 1000 characters."
20
+ ),
21
+ max_length=1000,
22
+ )
23
+ orientation: Literal["portrait", "square", "landscape"] = Field(
24
+ default="portrait",
25
+ description=(
26
+ "Shape of the image: portrait (characters, full body), square (avatars, icons) "
27
+ "or landscape (scenery, wide scenes). Same price for every shape."
28
+ ),
29
+ )
30
+
31
+
32
+ class AnimeAPIGenerateImage(BaseTool):
33
+ """Generate an anime image with AnimeAPI: $0.003 per image on the default model.
34
+
35
+ Setup:
36
+ ``pip install langchain-animeapi`` and set ``ANIMEAPI_API_KEY`` (keys start with
37
+ ``ank_``; create one at https://animeapi.ai/dashboard/api-keys).
38
+
39
+ The model chooses the prompt and the shape. You choose the image model here, so an
40
+ agent cannot switch to a more expensive one:
41
+
42
+ tool = AnimeAPIGenerateImage(model="anime") # $0.003 per image (default)
43
+ tool = AnimeAPIGenerateImage(model="ultra") # $0.01 per image
44
+
45
+ The tool returns a short text for the model (the image URL, price and balance left)
46
+ and, as the artifact, a dict with ``image_url``, ``enhanced_prompt``, ``model``,
47
+ ``orientation``, ``price_usd``, ``balance_usd`` and ``generation_time_ms``.
48
+ """
49
+
50
+ name: str = "animeapi_generate_image"
51
+ description: str = (
52
+ "Generate one anime-style image from a text description and return its URL. "
53
+ "Costs $0.003 per image on the default model. Describe the subject, appearance, pose, "
54
+ "setting and lighting in plain English. "
55
+ "Adult content is not available; explicit prompts are refused and not charged."
56
+ )
57
+ args_schema: Type[BaseModel] = AnimeAPIGenerateImageInput
58
+ response_format: Literal["content", "content_and_artifact"] = "content_and_artifact"
59
+ handle_tool_error: bool = True
60
+
61
+ api_key: Optional[SecretStr] = Field(default=None, description="Defaults to ANIMEAPI_API_KEY.")
62
+ model: Literal["anime", "ultra", "ultra-max"] = "anime"
63
+ enhance: bool = True
64
+ timeout: float = 120.0
65
+ max_retries: int = 2
66
+
67
+ def _client_kwargs(self) -> Dict[str, Any]:
68
+ return {
69
+ "api_key": self.api_key.get_secret_value() if self.api_key else None,
70
+ "timeout": self.timeout,
71
+ "max_retries": self.max_retries,
72
+ }
73
+
74
+ def _result(self, image: Any, orientation: str) -> Tuple[str, Dict[str, Any]]:
75
+ price = PRICES_USD[self.model]
76
+ content = (
77
+ f"Image generated: {image.image_url}\n"
78
+ f"Model {self.model}, {orientation}, ${price}. Balance left: ${image.balance_usd}.\n"
79
+ "The link is not permanent storage; download the image to keep it."
80
+ )
81
+ artifact = {
82
+ "image_url": image.image_url,
83
+ "enhanced_prompt": image.enhanced_prompt,
84
+ "model": self.model,
85
+ "orientation": orientation,
86
+ "price_usd": price,
87
+ "balance_usd": image.balance_usd,
88
+ "generation_time_ms": image.generation_time_ms,
89
+ }
90
+ return content, artifact
91
+
92
+ @staticmethod
93
+ def _tool_error(e: AnimeAPIError) -> ToolException:
94
+ # The API's own message, e.g. why a prompt was refused or that the balance is empty.
95
+ return ToolException(f"AnimeAPI: {e.message} ({e.code or e.status})")
96
+
97
+ def _run(
98
+ self,
99
+ prompt: str,
100
+ orientation: str = "portrait",
101
+ run_manager: Optional[CallbackManagerForToolRun] = None,
102
+ ) -> Tuple[str, Dict[str, Any]]:
103
+ try:
104
+ with AnimeAPI(**self._client_kwargs()) as client:
105
+ image = client.images.generate(
106
+ prompt, model=self.model, orientation=orientation, enhance=self.enhance # type: ignore[arg-type]
107
+ )
108
+ except AnimeAPIError as e:
109
+ raise self._tool_error(e) from e
110
+ return self._result(image, orientation)
111
+
112
+ async def _arun(
113
+ self,
114
+ prompt: str,
115
+ orientation: str = "portrait",
116
+ run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
117
+ ) -> Tuple[str, Dict[str, Any]]:
118
+ try:
119
+ async with AsyncAnimeAPI(**self._client_kwargs()) as client:
120
+ image = await client.images.generate(
121
+ prompt, model=self.model, orientation=orientation, enhance=self.enhance # type: ignore[arg-type]
122
+ )
123
+ except AnimeAPIError as e:
124
+ raise self._tool_error(e) from e
125
+ return self._result(image, orientation)