lean-explore 0.2.2__tar.gz → 0.3.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.
Files changed (34) hide show
  1. {lean_explore-0.2.2 → lean_explore-0.3.0}/PKG-INFO +25 -3
  2. {lean_explore-0.2.2 → lean_explore-0.3.0}/README.md +23 -1
  3. {lean_explore-0.2.2 → lean_explore-0.3.0}/pyproject.toml +2 -2
  4. lean_explore-0.3.0/src/lean_explore/api/client.py +216 -0
  5. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/cli/agent.py +19 -12
  6. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/local/service.py +192 -105
  7. lean_explore-0.3.0/src/lean_explore/mcp/tools.py +270 -0
  8. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore.egg-info/PKG-INFO +25 -3
  9. lean_explore-0.2.2/src/lean_explore/api/client.py +0 -124
  10. lean_explore-0.2.2/src/lean_explore/mcp/tools.py +0 -242
  11. {lean_explore-0.2.2 → lean_explore-0.3.0}/LICENSE +0 -0
  12. {lean_explore-0.2.2 → lean_explore-0.3.0}/setup.cfg +0 -0
  13. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/__init__.py +0 -0
  14. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/api/__init__.py +0 -0
  15. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/cli/__init__.py +0 -0
  16. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/cli/config_utils.py +0 -0
  17. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/cli/data_commands.py +0 -0
  18. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/cli/main.py +0 -0
  19. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/defaults.py +0 -0
  20. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/local/__init__.py +0 -0
  21. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/local/search.py +0 -0
  22. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/mcp/__init__.py +0 -0
  23. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/mcp/app.py +0 -0
  24. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/mcp/server.py +0 -0
  25. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/shared/__init__.py +0 -0
  26. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/shared/models/__init__.py +0 -0
  27. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/shared/models/api.py +0 -0
  28. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore/shared/models/db.py +0 -0
  29. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore.egg-info/SOURCES.txt +0 -0
  30. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore.egg-info/dependency_links.txt +0 -0
  31. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore.egg-info/entry_points.txt +0 -0
  32. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore.egg-info/requires.txt +0 -0
  33. {lean_explore-0.2.2 → lean_explore-0.3.0}/src/lean_explore.egg-info/top_level.txt +0 -0
  34. {lean_explore-0.2.2 → lean_explore-0.3.0}/tests/test_defaults.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-explore
3
- Version: 0.2.2
4
- Summary: A project to explore and rank Lean mathematical declarations.
3
+ Version: 0.3.0
4
+ Summary: A search engine for Lean 4 declarations.
5
5
  Author-email: Justin Asher <justinchadwickasher@gmail.com>
6
6
  License: Apache License
7
7
  Version 2.0, January 2004
@@ -239,7 +239,28 @@ Requires-Dist: tqdm>=4.60
239
239
  Requires-Dist: requests>=2.25.0
240
240
  Dynamic: license-file
241
241
 
242
- # LeanExplore
242
+ <h1 align="center">
243
+ LeanExplore
244
+ </h1>
245
+
246
+ <h3 align="center">
247
+ A search engine for Lean 4 declarations
248
+ </h3>
249
+
250
+ <p align="center">
251
+ <a href="https://pypi.org/project/lean-explore/">
252
+ <img src="https://img.shields.io/pypi/v/lean-explore.svg" alt="PyPI version" />
253
+ </a>
254
+ <a href="https://github.com/justincasher/lean-explore/blob/main/LeanExplore.pdf">
255
+ <img src="https://img.shields.io/badge/Paper-PDF-blue.svg" alt="Read the Paper" />
256
+ </a>
257
+ <a href="https://github.com/justincasher/lean-explore/commits/main">
258
+ <img src="https://img.shields.io/github/last-commit/justincasher/lean-explore" alt="last update" />
259
+ </a>
260
+ <a href="https://github.com/justincasher/lean-explore/blob/main/LICENSE">
261
+ <img src="https://img.shields.io/github/license/justincasher/lean-explore.svg" alt="license" />
262
+ </a>
263
+ </p>
243
264
 
244
265
  A search engine for Lean 4 declarations. This project provides tools and resources for exploring the Lean 4 ecosystem.
245
266
 
@@ -249,6 +270,7 @@ The current indexed projects include:
249
270
 
250
271
  * Batteries
251
272
  * Lean
273
+ * Init
252
274
  * Mathlib
253
275
  * PhysLean
254
276
  * Std
@@ -1,4 +1,25 @@
1
- # LeanExplore
1
+ <h1 align="center">
2
+ LeanExplore
3
+ </h1>
4
+
5
+ <h3 align="center">
6
+ A search engine for Lean 4 declarations
7
+ </h3>
8
+
9
+ <p align="center">
10
+ <a href="https://pypi.org/project/lean-explore/">
11
+ <img src="https://img.shields.io/pypi/v/lean-explore.svg" alt="PyPI version" />
12
+ </a>
13
+ <a href="https://github.com/justincasher/lean-explore/blob/main/LeanExplore.pdf">
14
+ <img src="https://img.shields.io/badge/Paper-PDF-blue.svg" alt="Read the Paper" />
15
+ </a>
16
+ <a href="https://github.com/justincasher/lean-explore/commits/main">
17
+ <img src="https://img.shields.io/github/last-commit/justincasher/lean-explore" alt="last update" />
18
+ </a>
19
+ <a href="https://github.com/justincasher/lean-explore/blob/main/LICENSE">
20
+ <img src="https://img.shields.io/github/license/justincasher/lean-explore.svg" alt="license" />
21
+ </a>
22
+ </p>
2
23
 
3
24
  A search engine for Lean 4 declarations. This project provides tools and resources for exploring the Lean 4 ecosystem.
4
25
 
@@ -8,6 +29,7 @@ The current indexed projects include:
8
29
 
9
30
  * Batteries
10
31
  * Lean
32
+ * Init
11
33
  * Mathlib
12
34
  * PhysLean
13
35
  * Std
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lean-explore"
7
- version = "0.2.2"
7
+ version = "0.3.0"
8
8
  authors = [
9
9
  { name = "Justin Asher", email = "justinchadwickasher@gmail.com" },
10
10
  ]
11
- description = "A project to explore and rank Lean mathematical declarations."
11
+ description = "A search engine for Lean 4 declarations."
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
14
14
  license = { file = "LICENSE" }
@@ -0,0 +1,216 @@
1
+ # src/lean_explore/api/client.py
2
+
3
+ """Provides a client for interacting with the remote Lean Explore API.
4
+
5
+ This module contains the Client class, which facilitates
6
+ communication with the backend Lean Explore search engine API for
7
+ performing searches and retrieving detailed information.
8
+ """
9
+
10
+ import asyncio
11
+ from typing import List, Optional, Union, overload
12
+
13
+ import httpx
14
+
15
+ from lean_explore.shared.models.api import (
16
+ APICitationsResponse,
17
+ APISearchResponse,
18
+ APISearchResultItem,
19
+ )
20
+
21
+ _DEFAULT_API_BASE_URL = "https://www.leanexplore.com/api/v1"
22
+
23
+
24
+ class Client:
25
+ """An asynchronous client for the Lean Explore backend API.
26
+
27
+ This client handles making HTTP requests to the production API base URL,
28
+ authenticating with an API key, and parsing responses into Pydantic models.
29
+
30
+ Attributes:
31
+ api_key: The API key used for authenticating requests.
32
+ timeout: The timeout for HTTP requests in seconds.
33
+ base_url: The hardcoded base URL for the API.
34
+ """
35
+
36
+ def __init__(self, api_key: str, timeout: float = 10.0):
37
+ """Initializes the API Client.
38
+
39
+ Args:
40
+ api_key: The API key for authentication.
41
+ timeout: Default timeout for HTTP requests in seconds.
42
+ """
43
+ self.base_url: str = _DEFAULT_API_BASE_URL
44
+ self.api_key: str = api_key
45
+ self.timeout: float = timeout
46
+ self._headers: dict = {"Authorization": f"Bearer {self.api_key}"}
47
+
48
+ async def _fetch_one_search(
49
+ self,
50
+ client: httpx.AsyncClient,
51
+ query: str,
52
+ package_filters: Optional[List[str]],
53
+ ) -> APISearchResponse:
54
+ """Coroutine to fetch a single search result.
55
+
56
+ Args:
57
+ client: An active httpx.AsyncClient instance.
58
+ query: The search query string.
59
+ package_filters: An optional list of package names.
60
+
61
+ Returns:
62
+ An APISearchResponse object.
63
+ """
64
+ endpoint = f"{self.base_url}/search"
65
+ params = {"q": query}
66
+ if package_filters:
67
+ params["pkg"] = package_filters
68
+
69
+ response = await client.get(endpoint, params=params, headers=self._headers)
70
+ response.raise_for_status()
71
+ return APISearchResponse(**response.json())
72
+
73
+ @overload
74
+ async def search(
75
+ self, query: str, package_filters: Optional[List[str]] = None
76
+ ) -> APISearchResponse: ...
77
+
78
+ @overload
79
+ async def search(
80
+ self, query: List[str], package_filters: Optional[List[str]] = None
81
+ ) -> List[APISearchResponse]: ...
82
+
83
+ async def search(
84
+ self,
85
+ query: Union[str, List[str]],
86
+ package_filters: Optional[List[str]] = None,
87
+ ) -> Union[APISearchResponse, List[APISearchResponse]]:
88
+ """Performs a search for statement groups via the API.
89
+
90
+ This method can handle a single query string or a list of query strings.
91
+ When a list is provided, requests are sent concurrently.
92
+
93
+ Args:
94
+ query: The search query string or a list of query strings.
95
+ package_filters: An optional list of package names to filter the
96
+ search by. This filter is applied to all queries.
97
+
98
+ Returns:
99
+ An APISearchResponse object if a single query was provided, or a
100
+ list of APISearchResponse objects if a list of queries was provided.
101
+
102
+ Raises:
103
+ httpx.HTTPStatusError: If the API returns an HTTP error status (4xx or 5xx).
104
+ httpx.RequestError: For network-related issues or other request errors.
105
+ """
106
+ was_single_query = isinstance(query, str)
107
+ queries = [query] if was_single_query else query
108
+
109
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
110
+ tasks = [
111
+ self._fetch_one_search(client, q, package_filters) for q in queries
112
+ ]
113
+ results = await asyncio.gather(*tasks)
114
+
115
+ if was_single_query:
116
+ return results[0]
117
+ return results
118
+
119
+ async def _fetch_one_by_id(
120
+ self, client: httpx.AsyncClient, group_id: int
121
+ ) -> Optional[APISearchResultItem]:
122
+ endpoint = f"{self.base_url}/statement_groups/{group_id}"
123
+ response = await client.get(endpoint, headers=self._headers)
124
+ if response.status_code == 404:
125
+ return None
126
+ response.raise_for_status()
127
+ return APISearchResultItem(**response.json())
128
+
129
+ @overload
130
+ async def get_by_id(self, group_id: int) -> Optional[APISearchResultItem]: ...
131
+
132
+ @overload
133
+ async def get_by_id(
134
+ self, group_id: List[int]
135
+ ) -> List[Optional[APISearchResultItem]]: ...
136
+
137
+ async def get_by_id(
138
+ self, group_id: Union[int, List[int]]
139
+ ) -> Union[Optional[APISearchResultItem], List[Optional[APISearchResultItem]]]:
140
+ """Retrieves a specific statement group by its unique ID via the API.
141
+
142
+ Args:
143
+ group_id: The unique identifier of the statement group, or a list of IDs.
144
+
145
+ Returns:
146
+ An APISearchResultItem object if a single ID was found, None if it was
147
+ not found. A list of Optional[APISearchResultItem] if a list of
148
+ IDs was provided.
149
+
150
+ Raises:
151
+ httpx.HTTPStatusError: If the API returns an HTTP error status
152
+ other than 404 (e.g., 401, 403, 5xx).
153
+ httpx.RequestError: For network-related issues or other request errors.
154
+ """
155
+ was_single_id = isinstance(group_id, int)
156
+ group_ids = [group_id] if was_single_id else group_id
157
+
158
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
159
+ tasks = [self._fetch_one_by_id(client, g_id) for g_id in group_ids]
160
+ results = await asyncio.gather(*tasks)
161
+
162
+ if was_single_id:
163
+ return results[0]
164
+ return results
165
+
166
+ async def _fetch_one_dependencies(
167
+ self, client: httpx.AsyncClient, group_id: int
168
+ ) -> Optional[APICitationsResponse]:
169
+ endpoint = f"{self.base_url}/statement_groups/{group_id}/dependencies"
170
+ response = await client.get(endpoint, headers=self._headers)
171
+ if response.status_code == 404:
172
+ return None
173
+ response.raise_for_status()
174
+ return APICitationsResponse(**response.json())
175
+
176
+ @overload
177
+ async def get_dependencies(
178
+ self, group_id: int
179
+ ) -> Optional[APICitationsResponse]: ...
180
+
181
+ @overload
182
+ async def get_dependencies(
183
+ self, group_id: List[int]
184
+ ) -> List[Optional[APICitationsResponse]]: ...
185
+
186
+ async def get_dependencies(
187
+ self, group_id: Union[int, List[int]]
188
+ ) -> Union[Optional[APICitationsResponse], List[Optional[APICitationsResponse]]]:
189
+ """Retrieves the dependencies (citations) for a specific statement group.
190
+
191
+ This method fetches the statement groups that the specified 'group_id'(s)
192
+ depend on (i.e., cite).
193
+
194
+ Args:
195
+ group_id: The unique identifier of the statement group, or a list of IDs.
196
+
197
+ Returns:
198
+ An APICitationsResponse object if a single ID was provided. A list
199
+ of Optional[APICitationsResponse] if a list of IDs was provided.
200
+ None is returned for IDs that are not found.
201
+
202
+ Raises:
203
+ httpx.HTTPStatusError: If the API returns an HTTP error status
204
+ other than 404 (e.g., 401, 403, 5xx).
205
+ httpx.RequestError: For network-related issues or other request errors.
206
+ """
207
+ was_single_id = isinstance(group_id, int)
208
+ group_ids = [group_id] if was_single_id else group_id
209
+
210
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
211
+ tasks = [self._fetch_one_dependencies(client, g_id) for g_id in group_ids]
212
+ results = await asyncio.gather(*tasks)
213
+
214
+ if was_single_id:
215
+ return results[0]
216
+ return results
@@ -520,19 +520,27 @@ async def _run_agent_session(
520
520
  "to the user.\n"
521
521
  "**Output:** CLI-friendly (plain text, simple lists). "
522
522
  "NO complex Markdown/LaTeX.\n\n"
523
+ "**Tool Usage & Efficiency:**\n"
524
+ "* The `search`, `get_by_id`, and `get_dependencies` tools can "
525
+ "all accept a list of inputs (queries or integer IDs) to "
526
+ "perform batch operations. This is highly efficient. For "
527
+ "example, `search(query=['query 1', 'query 2'])` or "
528
+ "`get_by_id(group_id=[123, 456])`.\n"
529
+ "* Always prefer making one batch call over multiple single "
530
+ "calls.\n\n"
523
531
  "**Packages:** Use exact top-level names for filters (Batteries, "
524
532
  "Init, Lean, Mathlib, PhysLean, Std). Map subpackage mentions "
525
533
  "to top-level (e.g., 'Mathlib.Analysis' -> 'Mathlib').\n\n"
526
534
  "**Core Workflow:**\n"
527
535
  "1. **Search & Analyze:**\n"
528
536
  " * Execute multiple distinct `search` queries for each user "
529
- "request (e.g., using full statements, rephrasing). Set `limit` "
537
+ "request by passing a list of queries to the tool. Set `limit` "
530
538
  ">= 10 for each search.\n"
531
539
  " * From all search results, select the statement(s) most "
532
540
  "helpful to the user.\n"
533
- " * For each selected statement, **MUST** use "
534
- "`get_dependencies` to understand its context before "
535
- "explaining.\n\n"
541
+ " * For each selected statement, use `get_dependencies` to "
542
+ "understand its context. Do this efficiently by collecting all "
543
+ "relevant IDs and passing them in a single list call.\n\n"
536
544
  "2. **Explain Results (Conversational & CLI-Friendly):**\n"
537
545
  " * Briefly state your search approach (e.g., 'I looked into X "
538
546
  "in Mathlib...').\n"
@@ -543,15 +551,14 @@ async def _run_agent_session(
543
551
  "`informal_description`, `statement_text`).\n"
544
552
  " * Provide the full Lean code (`statement_text`).\n"
545
553
  " * Explain key dependencies (what they are, their role, "
546
- "using `statement_text` or `display_statement_text` from "
547
- "`get_dependencies` output).\n"
554
+ "using `statement_text` from `get_dependencies` output).\n"
548
555
  "3. **Specific User Follow-ups (If Asked):**\n"
549
- " * **`get_by_id`:** For a specific ID, provide: ID, Lean name, "
550
- "statement text, source/line, docstring, informal description "
551
- "(structured CLI format).\n"
552
- " * **`get_dependencies` (Direct Request):** For all "
553
- "dependencies of an ID, list: ID, Lean name, statement "
554
- "text/summary. State total count.\n\n"
556
+ " * **`get_by_id`:** For one or more IDs, provide: ID, "
557
+ "Lean name, statement text, source/line, docstring, informal "
558
+ "description (structured CLI format).\n"
559
+ " * **`get_dependencies` (Direct Request):** For one or more "
560
+ "IDs, list dependencies for each: ID, Lean name, statement "
561
+ "text/summary. State total count per ID.\n\n"
555
562
  "Always be concise, helpful, and clear."
556
563
  ),
557
564
  mcp_servers=[server_instance],