langchain-asterwise 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,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Asterwise Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-asterwise
3
+ Version: 0.1.0
4
+ Summary: LangChain tools for the Asterwise astrology API: natal charts, Vedic matchmaking with Rajju and Vedha vetoes, panchanga, Western natal charts and numerology.
5
+ Author-email: Asterwise <support@asterwise.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://asterwise.com
8
+ Project-URL: Documentation, https://docs.asterwise.com
9
+ Project-URL: Accuracy, https://asterwise.com/accuracy/
10
+ Keywords: langchain,astrology,vedic-astrology,asterwise,tools,agents
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: langchain-core>=0.3
15
+ Requires-Dist: asterwise>=0.2.3
16
+ Requires-Dist: pydantic>=2
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=7; extra == "test"
19
+ Dynamic: license-file
20
+
21
+ # langchain-asterwise
22
+
23
+ LangChain tools for the [Asterwise](https://asterwise.com) astrology API. Five `StructuredTool`s over the official Python SDK:
24
+
25
+ | Tool | What it returns |
26
+ |---|---|
27
+ | `asterwise_natal_chart` | Vedic natal chart: planets by sign, degree, nakshatra and house; ascendant; Moon sign |
28
+ | `asterwise_western_natal_chart` | Western natal chart: planets, ascendant, midheaven; Placidus, Koch, Equal or Whole Sign |
29
+ | `asterwise_matchmaking` | Ashtakoot Guna Milan out of 36 with Rajju and Vedha as separate vetoes, Mangal dosha comparison, narrative |
30
+ | `asterwise_panchanga` | Tithi, nakshatra, yoga, karana, vara, sunrise and sunset, Rahu kaal for a date and place |
31
+ | `asterwise_numerology_profile` | Life path, expression, soul urge, personality and related numbers |
32
+
33
+ Outputs are slimmed by default to the fields a model needs to reason with. Pass `slim=False` for the full API payload. Birthplaces are plain text; the API geocodes them.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install langchain-asterwise
39
+ ```
40
+
41
+ Get a free key at [asterwise.com/dashboard](https://asterwise.com/dashboard): 500 calls a month, no card.
42
+
43
+ ## Use
44
+
45
+ ```python
46
+ import os
47
+ from asterwise_langchain import AsterwiseToolkit
48
+
49
+ tools = AsterwiseToolkit(api_key=os.environ["ASTERWISE_API_KEY"]).get_tools()
50
+ ```
51
+
52
+ With any LangChain chat model that supports tool calling:
53
+
54
+ ```python
55
+ from langchain.chat_models import init_chat_model
56
+
57
+ llm = init_chat_model("claude-sonnet-5").bind_tools(tools)
58
+ msg = llm.invoke("Cast a Vedic chart for 12 Nov 1985, 06:45, Mumbai and tell me the Moon nakshatra.")
59
+ print(msg.tool_calls)
60
+ ```
61
+
62
+ Or as a ReAct agent with LangGraph:
63
+
64
+ ```python
65
+ from langgraph.prebuilt import create_react_agent
66
+
67
+ agent = create_react_agent(init_chat_model("claude-sonnet-5"), tools)
68
+ result = agent.invoke({"messages": [("user", "Are these two compatible? Arjun 1990-05-14 07:20 Pune; Meera 1992-11-03 22:45 Jaipur.")]})
69
+ print(result["messages"][-1].content)
70
+ ```
71
+
72
+ The toolkit's methods also work without LangChain:
73
+
74
+ ```python
75
+ kit = AsterwiseToolkit(api_key="aw_...")
76
+ kit.matchmaking("1990-05-14", "Pune, India", "1992-11-03", "Jaipur, India")
77
+ ```
78
+
79
+ ## Why the matchmaking tool reports vetoes separately
80
+
81
+ Rajju and Vedha between the two Moon nakshatras are stops in the classical method, not point deductions. A 30 out of 36 with Rajju present is not a good match, so the tool returns `classical_vetoes` alongside `total_score` and the tool description tells the model so. Background: [Rajju and Vedha as hard vetoes](https://asterwise.com/blog/rajju-vedha-hard-vetoes/).
82
+
83
+ ## Accuracy
84
+
85
+ Every Asterwise position is computed with the Swiss Ephemeris and [checked against NASA JPL Horizons](https://asterwise.com/accuracy/): 80 positions from 1950 to 2050, median difference 0.046 arcseconds, raw data and script published.
86
+
87
+ ## Test
88
+
89
+ ```bash
90
+ pip install -e ".[test]" && pytest # offline, mocks the SDK
91
+ ASTERWISE_API_KEY=aw_... python scripts/smoke.py # one live call per tool
92
+ ```
93
+
94
+ MIT licensed.
@@ -0,0 +1,74 @@
1
+ # langchain-asterwise
2
+
3
+ LangChain tools for the [Asterwise](https://asterwise.com) astrology API. Five `StructuredTool`s over the official Python SDK:
4
+
5
+ | Tool | What it returns |
6
+ |---|---|
7
+ | `asterwise_natal_chart` | Vedic natal chart: planets by sign, degree, nakshatra and house; ascendant; Moon sign |
8
+ | `asterwise_western_natal_chart` | Western natal chart: planets, ascendant, midheaven; Placidus, Koch, Equal or Whole Sign |
9
+ | `asterwise_matchmaking` | Ashtakoot Guna Milan out of 36 with Rajju and Vedha as separate vetoes, Mangal dosha comparison, narrative |
10
+ | `asterwise_panchanga` | Tithi, nakshatra, yoga, karana, vara, sunrise and sunset, Rahu kaal for a date and place |
11
+ | `asterwise_numerology_profile` | Life path, expression, soul urge, personality and related numbers |
12
+
13
+ Outputs are slimmed by default to the fields a model needs to reason with. Pass `slim=False` for the full API payload. Birthplaces are plain text; the API geocodes them.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install langchain-asterwise
19
+ ```
20
+
21
+ Get a free key at [asterwise.com/dashboard](https://asterwise.com/dashboard): 500 calls a month, no card.
22
+
23
+ ## Use
24
+
25
+ ```python
26
+ import os
27
+ from asterwise_langchain import AsterwiseToolkit
28
+
29
+ tools = AsterwiseToolkit(api_key=os.environ["ASTERWISE_API_KEY"]).get_tools()
30
+ ```
31
+
32
+ With any LangChain chat model that supports tool calling:
33
+
34
+ ```python
35
+ from langchain.chat_models import init_chat_model
36
+
37
+ llm = init_chat_model("claude-sonnet-5").bind_tools(tools)
38
+ msg = llm.invoke("Cast a Vedic chart for 12 Nov 1985, 06:45, Mumbai and tell me the Moon nakshatra.")
39
+ print(msg.tool_calls)
40
+ ```
41
+
42
+ Or as a ReAct agent with LangGraph:
43
+
44
+ ```python
45
+ from langgraph.prebuilt import create_react_agent
46
+
47
+ agent = create_react_agent(init_chat_model("claude-sonnet-5"), tools)
48
+ result = agent.invoke({"messages": [("user", "Are these two compatible? Arjun 1990-05-14 07:20 Pune; Meera 1992-11-03 22:45 Jaipur.")]})
49
+ print(result["messages"][-1].content)
50
+ ```
51
+
52
+ The toolkit's methods also work without LangChain:
53
+
54
+ ```python
55
+ kit = AsterwiseToolkit(api_key="aw_...")
56
+ kit.matchmaking("1990-05-14", "Pune, India", "1992-11-03", "Jaipur, India")
57
+ ```
58
+
59
+ ## Why the matchmaking tool reports vetoes separately
60
+
61
+ Rajju and Vedha between the two Moon nakshatras are stops in the classical method, not point deductions. A 30 out of 36 with Rajju present is not a good match, so the tool returns `classical_vetoes` alongside `total_score` and the tool description tells the model so. Background: [Rajju and Vedha as hard vetoes](https://asterwise.com/blog/rajju-vedha-hard-vetoes/).
62
+
63
+ ## Accuracy
64
+
65
+ Every Asterwise position is computed with the Swiss Ephemeris and [checked against NASA JPL Horizons](https://asterwise.com/accuracy/): 80 positions from 1950 to 2050, median difference 0.046 arcseconds, raw data and script published.
66
+
67
+ ## Test
68
+
69
+ ```bash
70
+ pip install -e ".[test]" && pytest # offline, mocks the SDK
71
+ ASTERWISE_API_KEY=aw_... python scripts/smoke.py # one live call per tool
72
+ ```
73
+
74
+ MIT licensed.
@@ -0,0 +1,199 @@
1
+ """LangChain tools for the Asterwise astrology API.
2
+
3
+ from asterwise_langchain import AsterwiseToolkit
4
+ tools = AsterwiseToolkit(api_key="aw_...").get_tools()
5
+
6
+ Five StructuredTools, each a thin wrapper over the official ``asterwise`` SDK:
7
+ natal_chart, western_natal_chart, matchmaking, panchanga, numerology_profile.
8
+ By default outputs are slimmed to what a model needs to reason; pass
9
+ ``slim=False`` to return the full API payload.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from typing import Any, Callable, Optional
16
+
17
+ from langchain_core.tools import StructuredTool
18
+ from pydantic import BaseModel, Field
19
+
20
+ import asterwise
21
+ from asterwise.api.astrology_api import AstrologyApi
22
+ from asterwise.api.numerology_api import NumerologyApi
23
+ from asterwise.api.western_astrology_api import WesternAstrologyApi
24
+
25
+ __all__ = ["AsterwiseToolkit", "__version__"]
26
+ __version__ = "0.1.0"
27
+
28
+ _AYANAMSA = "Sidereal ayanamsa: lahiri (default), raman, kp, or tropical."
29
+
30
+
31
+ class BirthArgs(BaseModel):
32
+ """Birth data. Give a birthplace as text; the API geocodes it and resolves the time zone."""
33
+
34
+ date: str = Field(description="Date of birth, YYYY-MM-DD.")
35
+ time: Optional[str] = Field(default=None, description="Time of birth, HH:MM 24-hour. Omit if unknown; a sunrise chart is used.")
36
+ location: str = Field(description="Birthplace, e.g. 'Mumbai, India'.")
37
+ name: Optional[str] = Field(default=None, description="Person's name, optional.")
38
+ ayanamsa: Optional[str] = Field(default=None, description=_AYANAMSA)
39
+
40
+
41
+ class WesternBirthArgs(BaseModel):
42
+ date: str = Field(description="Date of birth, YYYY-MM-DD.")
43
+ time: Optional[str] = Field(default=None, description="Time of birth, HH:MM 24-hour. Omit if unknown.")
44
+ location: str = Field(description="Birthplace, e.g. 'Austin, Texas'.")
45
+ name: Optional[str] = Field(default=None, description="Person's name, optional.")
46
+ house_system: Optional[str] = Field(default=None, description="placidus (default), koch, equal, or whole_sign.")
47
+
48
+
49
+ class MatchArgs(BaseModel):
50
+ """Two people. person1 is the groom and person2 the bride in the classical Ashtakoot method."""
51
+
52
+ person1_date: str = Field(description="First person's date of birth, YYYY-MM-DD.")
53
+ person1_location: str = Field(description="First person's birthplace.")
54
+ person1_time: Optional[str] = Field(default=None, description="First person's time of birth, HH:MM, optional.")
55
+ person1_name: Optional[str] = Field(default=None)
56
+ person2_date: str = Field(description="Second person's date of birth, YYYY-MM-DD.")
57
+ person2_location: str = Field(description="Second person's birthplace.")
58
+ person2_time: Optional[str] = Field(default=None, description="Second person's time of birth, HH:MM, optional.")
59
+ person2_name: Optional[str] = Field(default=None)
60
+
61
+
62
+ class PanchangaArgs(BaseModel):
63
+ date: str = Field(description="Civil date, YYYY-MM-DD.")
64
+ location: str = Field(description="Place, e.g. 'New Delhi, India'. Sunrise and the day's elements depend on it.")
65
+
66
+
67
+ class NumerologyArgs(BaseModel):
68
+ name: str = Field(description="Full name as commonly written.")
69
+ date: str = Field(description="Date of birth, YYYY-MM-DD.")
70
+
71
+
72
+ def _drop_none(d: dict[str, Any]) -> dict[str, Any]:
73
+ return {k: v for k, v in d.items() if v is not None}
74
+
75
+
76
+ def _slim_natal(d: dict[str, Any]) -> dict[str, Any]:
77
+ keep = {}
78
+ planets = d.get("planets")
79
+ if isinstance(planets, list):
80
+ keep["planets"] = [
81
+ {k: p.get(k) for k in ("name", "sign", "degree", "nakshatra", "pada", "house", "retrograde") if k in p}
82
+ for p in planets
83
+ if isinstance(p, dict)
84
+ ]
85
+ for k in ("ascendant", "ascendant_sign", "moon_sign", "moon_nakshatra", "ayanamsa_used", "ayanamsa_value", "birth_time_provided"):
86
+ if k in d:
87
+ keep[k] = d[k]
88
+ return keep or d
89
+
90
+
91
+ def _slim_western(d: dict[str, Any]) -> dict[str, Any]:
92
+ keep = {}
93
+ planets = d.get("planets")
94
+ if isinstance(planets, list):
95
+ keep["planets"] = [
96
+ {k: p.get(k) for k in ("name", "sign", "degree_in_sign", "house", "is_retrograde", "dignity") if k in p}
97
+ for p in planets
98
+ if isinstance(p, dict)
99
+ ]
100
+ for k in ("ascendant", "mc", "house_system", "birth_time_provided"):
101
+ if k in d:
102
+ keep[k] = d[k]
103
+ return keep or d
104
+
105
+
106
+ def _slim_match(d: dict[str, Any]) -> dict[str, Any]:
107
+ keep = {k: d[k] for k in ("total_score", "compatibility_level", "breakdown", "classical_vetoes", "doshas", "dosha_cancellations", "mangal_compatibility", "compatibility_narrative", "birth_time_provided") if k in d}
108
+ return keep or d
109
+
110
+
111
+ def _slim_panchanga(d: dict[str, Any]) -> dict[str, Any]:
112
+ keep = {k: d[k] for k in ("date", "vara", "tithi", "nakshatra", "yoga", "karana", "sunrise", "sunset", "moonrise", "moonset", "rahu_kaal", "location") if k in d}
113
+ return keep or d
114
+
115
+
116
+ def _to_dict(result: Any) -> dict[str, Any]:
117
+ """Unwrap the SDK's {success, message, data} envelope to the data dict."""
118
+ if hasattr(result, "to_dict"):
119
+ result = result.to_dict()
120
+ if isinstance(result, dict) and "data" in result and isinstance(result["data"], dict):
121
+ return result["data"]
122
+ return result
123
+
124
+
125
+ class AsterwiseToolkit:
126
+ """Builds LangChain tools bound to one Asterwise API key.
127
+
128
+ Args:
129
+ api_key: an Asterwise API key (free tier at https://asterwise.com/dashboard).
130
+ slim: when True (default) each tool returns only the fields a model needs;
131
+ when False the full API payload is returned.
132
+ host: API base URL, normally left as the default.
133
+ """
134
+
135
+ def __init__(self, api_key: str, *, slim: bool = True, host: str = "https://api.asterwise.com") -> None:
136
+ if not api_key:
137
+ raise ValueError("api_key is required; get one at https://asterwise.com/dashboard")
138
+ self._config = asterwise.Configuration(host=host, access_token=api_key)
139
+ self._client = asterwise.ApiClient(self._config)
140
+ self.slim = slim
141
+ self.astrology = AstrologyApi(self._client)
142
+ self.western = WesternAstrologyApi(self._client)
143
+ self.numerology = NumerologyApi(self._client)
144
+
145
+ # ---- individual calls (plain functions so they are easy to test and reuse) ----
146
+
147
+ def natal_chart(self, date: str, location: str, time: Optional[str] = None, name: Optional[str] = None, ayanamsa: Optional[str] = None) -> dict[str, Any]:
148
+ req = asterwise.NatalRequest(**_drop_none({"date": date, "time": time, "location": location, "name": name, "ayanamsa": ayanamsa}))
149
+ d = _to_dict(self.astrology.natal_chart(req))
150
+ return _slim_natal(d) if self.slim else d
151
+
152
+ def western_natal_chart(self, date: str, location: str, time: Optional[str] = None, name: Optional[str] = None, house_system: Optional[str] = None) -> dict[str, Any]:
153
+ req = asterwise.WesternNatalRequest(**_drop_none({"date": date, "time": time, "location": location, "name": name, "house_system": house_system}))
154
+ d = _to_dict(self.western.western_natal_chart(req))
155
+ return _slim_western(d) if self.slim else d
156
+
157
+ def matchmaking(self, person1_date: str, person1_location: str, person2_date: str, person2_location: str, person1_time: Optional[str] = None, person1_name: Optional[str] = None, person2_time: Optional[str] = None, person2_name: Optional[str] = None) -> dict[str, Any]:
158
+ p1 = asterwise.BirthInput(**_drop_none({"date": person1_date, "time": person1_time, "location": person1_location, "name": person1_name}))
159
+ p2 = asterwise.BirthInput(**_drop_none({"date": person2_date, "time": person2_time, "location": person2_location, "name": person2_name}))
160
+ d = _to_dict(self.astrology.matchmaking(asterwise.MatchmakingRequest(person1=p1, person2=p2)))
161
+ return _slim_match(d) if self.slim else d
162
+
163
+ def panchanga(self, date: str, location: str) -> dict[str, Any]:
164
+ d = _to_dict(self.astrology.panchanga(asterwise.PanchangaRequest(date=date, location=location)))
165
+ return _slim_panchanga(d) if self.slim else d
166
+
167
+ def numerology_profile(self, name: str, date: str) -> dict[str, Any]:
168
+ return _to_dict(self.numerology.numerology_profile(asterwise.NumerologyRequest(name=name, date=date)))
169
+
170
+ # ---- LangChain tools ----
171
+
172
+ def get_tools(self) -> list[StructuredTool]:
173
+ def wrap(fn: Callable[..., dict[str, Any]]) -> Callable[..., str]:
174
+ def run(**kwargs: Any) -> str:
175
+ return json.dumps(fn(**kwargs), ensure_ascii=False, default=str)
176
+ return run
177
+
178
+ return [
179
+ StructuredTool.from_function(
180
+ func=wrap(self.natal_chart), name="asterwise_natal_chart", args_schema=BirthArgs,
181
+ description="Vedic (sidereal) natal chart: planets by sign, degree, nakshatra and house, plus the ascendant and Moon sign. Use for any Jyotish question about a person's birth chart.",
182
+ ),
183
+ StructuredTool.from_function(
184
+ func=wrap(self.western_natal_chart), name="asterwise_western_natal_chart", args_schema=WesternBirthArgs,
185
+ description="Western (tropical) natal chart: planets by sign and house, ascendant and midheaven, Placidus by default.",
186
+ ),
187
+ StructuredTool.from_function(
188
+ func=wrap(self.matchmaking), name="asterwise_matchmaking", args_schema=MatchArgs,
189
+ description="Vedic marriage compatibility (Ashtakoot Guna Milan out of 36) for two people, with Rajju and Vedha reported as separate classical vetoes, Mangal dosha comparison, and a narrative. A match with a veto is not a good match regardless of score.",
190
+ ),
191
+ StructuredTool.from_function(
192
+ func=wrap(self.panchanga), name="asterwise_panchanga", args_schema=PanchangaArgs,
193
+ description="Daily panchanga for a date and place: tithi, nakshatra, yoga, karana, vara, sunrise and sunset, Rahu kaal.",
194
+ ),
195
+ StructuredTool.from_function(
196
+ func=wrap(self.numerology_profile), name="asterwise_numerology_profile", args_schema=NumerologyArgs,
197
+ description="Pythagorean numerology profile from a name and date of birth: life path, expression, soul urge, personality and related numbers.",
198
+ ),
199
+ ]
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-asterwise
3
+ Version: 0.1.0
4
+ Summary: LangChain tools for the Asterwise astrology API: natal charts, Vedic matchmaking with Rajju and Vedha vetoes, panchanga, Western natal charts and numerology.
5
+ Author-email: Asterwise <support@asterwise.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://asterwise.com
8
+ Project-URL: Documentation, https://docs.asterwise.com
9
+ Project-URL: Accuracy, https://asterwise.com/accuracy/
10
+ Keywords: langchain,astrology,vedic-astrology,asterwise,tools,agents
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: langchain-core>=0.3
15
+ Requires-Dist: asterwise>=0.2.3
16
+ Requires-Dist: pydantic>=2
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=7; extra == "test"
19
+ Dynamic: license-file
20
+
21
+ # langchain-asterwise
22
+
23
+ LangChain tools for the [Asterwise](https://asterwise.com) astrology API. Five `StructuredTool`s over the official Python SDK:
24
+
25
+ | Tool | What it returns |
26
+ |---|---|
27
+ | `asterwise_natal_chart` | Vedic natal chart: planets by sign, degree, nakshatra and house; ascendant; Moon sign |
28
+ | `asterwise_western_natal_chart` | Western natal chart: planets, ascendant, midheaven; Placidus, Koch, Equal or Whole Sign |
29
+ | `asterwise_matchmaking` | Ashtakoot Guna Milan out of 36 with Rajju and Vedha as separate vetoes, Mangal dosha comparison, narrative |
30
+ | `asterwise_panchanga` | Tithi, nakshatra, yoga, karana, vara, sunrise and sunset, Rahu kaal for a date and place |
31
+ | `asterwise_numerology_profile` | Life path, expression, soul urge, personality and related numbers |
32
+
33
+ Outputs are slimmed by default to the fields a model needs to reason with. Pass `slim=False` for the full API payload. Birthplaces are plain text; the API geocodes them.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install langchain-asterwise
39
+ ```
40
+
41
+ Get a free key at [asterwise.com/dashboard](https://asterwise.com/dashboard): 500 calls a month, no card.
42
+
43
+ ## Use
44
+
45
+ ```python
46
+ import os
47
+ from asterwise_langchain import AsterwiseToolkit
48
+
49
+ tools = AsterwiseToolkit(api_key=os.environ["ASTERWISE_API_KEY"]).get_tools()
50
+ ```
51
+
52
+ With any LangChain chat model that supports tool calling:
53
+
54
+ ```python
55
+ from langchain.chat_models import init_chat_model
56
+
57
+ llm = init_chat_model("claude-sonnet-5").bind_tools(tools)
58
+ msg = llm.invoke("Cast a Vedic chart for 12 Nov 1985, 06:45, Mumbai and tell me the Moon nakshatra.")
59
+ print(msg.tool_calls)
60
+ ```
61
+
62
+ Or as a ReAct agent with LangGraph:
63
+
64
+ ```python
65
+ from langgraph.prebuilt import create_react_agent
66
+
67
+ agent = create_react_agent(init_chat_model("claude-sonnet-5"), tools)
68
+ result = agent.invoke({"messages": [("user", "Are these two compatible? Arjun 1990-05-14 07:20 Pune; Meera 1992-11-03 22:45 Jaipur.")]})
69
+ print(result["messages"][-1].content)
70
+ ```
71
+
72
+ The toolkit's methods also work without LangChain:
73
+
74
+ ```python
75
+ kit = AsterwiseToolkit(api_key="aw_...")
76
+ kit.matchmaking("1990-05-14", "Pune, India", "1992-11-03", "Jaipur, India")
77
+ ```
78
+
79
+ ## Why the matchmaking tool reports vetoes separately
80
+
81
+ Rajju and Vedha between the two Moon nakshatras are stops in the classical method, not point deductions. A 30 out of 36 with Rajju present is not a good match, so the tool returns `classical_vetoes` alongside `total_score` and the tool description tells the model so. Background: [Rajju and Vedha as hard vetoes](https://asterwise.com/blog/rajju-vedha-hard-vetoes/).
82
+
83
+ ## Accuracy
84
+
85
+ Every Asterwise position is computed with the Swiss Ephemeris and [checked against NASA JPL Horizons](https://asterwise.com/accuracy/): 80 positions from 1950 to 2050, median difference 0.046 arcseconds, raw data and script published.
86
+
87
+ ## Test
88
+
89
+ ```bash
90
+ pip install -e ".[test]" && pytest # offline, mocks the SDK
91
+ ASTERWISE_API_KEY=aw_... python scripts/smoke.py # one live call per tool
92
+ ```
93
+
94
+ MIT licensed.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ asterwise_langchain/__init__.py
5
+ langchain_asterwise.egg-info/PKG-INFO
6
+ langchain_asterwise.egg-info/SOURCES.txt
7
+ langchain_asterwise.egg-info/dependency_links.txt
8
+ langchain_asterwise.egg-info/requires.txt
9
+ langchain_asterwise.egg-info/top_level.txt
10
+ tests/test_tools.py
@@ -0,0 +1,6 @@
1
+ langchain-core>=0.3
2
+ asterwise>=0.2.3
3
+ pydantic>=2
4
+
5
+ [test]
6
+ pytest>=7
@@ -0,0 +1 @@
1
+ asterwise_langchain
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "langchain-asterwise"
7
+ version = "0.1.0"
8
+ description = "LangChain tools for the Asterwise astrology API: natal charts, Vedic matchmaking with Rajju and Vedha vetoes, panchanga, Western natal charts and numerology."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Asterwise", email = "support@asterwise.com" }]
13
+ keywords = ["langchain", "astrology", "vedic-astrology", "asterwise", "tools", "agents"]
14
+ dependencies = ["langchain-core>=0.3", "asterwise>=0.2.3", "pydantic>=2"]
15
+
16
+ [project.optional-dependencies]
17
+ test = ["pytest>=7"]
18
+
19
+ [project.urls]
20
+ Homepage = "https://asterwise.com"
21
+ Documentation = "https://docs.asterwise.com"
22
+ Accuracy = "https://asterwise.com/accuracy/"
23
+
24
+ [tool.setuptools.packages.find]
25
+ include = ["asterwise_langchain*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,91 @@
1
+ import json
2
+ from types import SimpleNamespace
3
+
4
+ import pytest
5
+
6
+ from asterwise_langchain import AsterwiseToolkit
7
+
8
+
9
+ class _Resp:
10
+ def __init__(self, data):
11
+ self._d = {"success": True, "message": "success", "data": data}
12
+
13
+ def to_dict(self):
14
+ return self._d
15
+
16
+
17
+ @pytest.fixture
18
+ def kit(monkeypatch):
19
+ kit = AsterwiseToolkit(api_key="aw_test")
20
+ calls = {}
21
+
22
+ def natal(req):
23
+ calls["natal"] = req.to_dict()
24
+ return _Resp({"planets": [{"name": "Sun", "sign": "Libra", "degree": 25.94, "nakshatra": "Vishakha", "house": 1, "extra": "drop me"}], "ascendant_sign": "Libra", "moon_sign": "Libra", "interpretation": "long text"})
25
+
26
+ def match(req):
27
+ calls["match"] = req.to_dict()
28
+ return _Resp({"total_score": 16.5, "compatibility_level": "Average", "breakdown": {"Nadi": 0}, "classical_vetoes": {"has_veto": False}, "analysis": {"drop": 1}})
29
+
30
+ def panch(req):
31
+ calls["panchanga"] = req.to_dict()
32
+ return _Resp({"tithi": {"name": "Krishna Ashtami"}, "nakshatra": {"name": "Rohini"}, "vara": "Friday", "muhurta_table": [1, 2, 3]})
33
+
34
+ def western(req):
35
+ calls["western"] = req.to_dict()
36
+ return _Resp({"planets": [{"name": "Sun", "sign": "Scorpio", "degree_in_sign": 19.6, "house": 1, "longitude": 229.6}], "ascendant": {"sign": "Scorpio"}, "mc": {"sign": "Leo"}, "aspects": []})
37
+
38
+ def numer(req):
39
+ calls["numerology"] = req.to_dict()
40
+ return _Resp({"life_path": 7})
41
+
42
+ monkeypatch.setattr(kit.astrology, "natal_chart", natal)
43
+ monkeypatch.setattr(kit.astrology, "matchmaking", match)
44
+ monkeypatch.setattr(kit.astrology, "panchanga", panch)
45
+ monkeypatch.setattr(kit.western, "western_natal_chart", western)
46
+ monkeypatch.setattr(kit.numerology, "numerology_profile", numer)
47
+ kit._calls = calls
48
+ return kit
49
+
50
+
51
+ def test_toolkit_exposes_five_named_tools(kit):
52
+ tools = kit.get_tools()
53
+ assert [t.name for t in tools] == ["asterwise_natal_chart", "asterwise_western_natal_chart", "asterwise_matchmaking", "asterwise_panchanga", "asterwise_numerology_profile"]
54
+ for t in tools:
55
+ assert t.args_schema is not None and t.description
56
+
57
+
58
+ def test_natal_tool_maps_args_and_slims_output(kit):
59
+ tool = kit.get_tools()[0]
60
+ out = json.loads(tool.invoke({"date": "1985-11-12", "time": "06:45", "location": "Mumbai, India"}))
61
+ assert kit._calls["natal"]["date"] == "1985-11-12" and kit._calls["natal"]["location"] == "Mumbai, India"
62
+ assert out["planets"][0] == {"name": "Sun", "sign": "Libra", "degree": 25.94, "nakshatra": "Vishakha", "house": 1}
63
+ assert "interpretation" not in out and out["moon_sign"] == "Libra"
64
+
65
+
66
+ def test_matchmaking_tool_keeps_vetoes_and_drops_analysis(kit):
67
+ tool = kit.get_tools()[2]
68
+ out = json.loads(tool.invoke({"person1_date": "1990-05-14", "person1_location": "Pune, India", "person2_date": "1992-11-03", "person2_location": "Jaipur, India"}))
69
+ sent = kit._calls["match"]
70
+ assert sent["person1"]["date"] == "1990-05-14" and sent["person2"]["location"] == "Jaipur, India"
71
+ assert out["total_score"] == 16.5 and out["classical_vetoes"] == {"has_veto": False} and "analysis" not in out
72
+
73
+
74
+ def test_panchanga_and_western_slim(kit):
75
+ tools = {t.name: t for t in kit.get_tools()}
76
+ p = json.loads(tools["asterwise_panchanga"].invoke({"date": "2026-09-04", "location": "Delhi, India"}))
77
+ assert p["nakshatra"] == {"name": "Rohini"} and "muhurta_table" not in p
78
+ w = json.loads(tools["asterwise_western_natal_chart"].invoke({"date": "1985-11-12", "location": "Mumbai, India", "house_system": "koch"}))
79
+ assert kit._calls["western"]["house_system"] == "koch"
80
+ assert w["planets"][0]["degree_in_sign"] == 19.6 and "longitude" not in w["planets"][0] and "aspects" not in w
81
+
82
+
83
+ def test_full_payload_when_slim_disabled(monkeypatch):
84
+ kit = AsterwiseToolkit(api_key="aw_test", slim=False)
85
+ monkeypatch.setattr(kit.numerology, "numerology_profile", lambda req: _Resp({"life_path": 7, "extra": True}))
86
+ assert kit.numerology_profile("Arjun", "1990-05-14") == {"life_path": 7, "extra": True}
87
+
88
+
89
+ def test_missing_key_is_rejected():
90
+ with pytest.raises(ValueError):
91
+ AsterwiseToolkit(api_key="")