langchain-sofya 0.1.0__py3-none-any.whl

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,19 @@
1
+ """Sofya integration for LangChain: web search, fetch, extract and research."""
2
+
3
+ from langchain_sofya._client import SofyaAPIWrapper
4
+ from langchain_sofya._version import __version__
5
+ from langchain_sofya.tools import (
6
+ SofyaExtract,
7
+ SofyaFetch,
8
+ SofyaResearch,
9
+ SofyaSearch,
10
+ )
11
+
12
+ __all__ = [
13
+ "SofyaAPIWrapper",
14
+ "SofyaExtract",
15
+ "SofyaFetch",
16
+ "SofyaResearch",
17
+ "SofyaSearch",
18
+ "__version__",
19
+ ]
@@ -0,0 +1,111 @@
1
+ """HTTP client for the Sofya REST API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+ import httpx
9
+ from langchain_core.tools import ToolException
10
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
11
+
12
+ from langchain_sofya._version import __version__
13
+
14
+ DEFAULT_BASE_URL = "https://sofya.co"
15
+ # Research and AI answers can take up to a minute; leave headroom.
16
+ DEFAULT_TIMEOUT = 120.0
17
+
18
+
19
+ class SofyaAPIWrapper(BaseModel):
20
+ """Calls the Sofya API (https://sofya.co/docs).
21
+
22
+ The API key is read from the ``SOFYA_API_KEY`` environment variable unless
23
+ passed as ``api_key``. Create one at https://sofya.co/dashboard.
24
+ """
25
+
26
+ model_config = ConfigDict(extra="forbid")
27
+
28
+ api_key: SecretStr = Field(default=SecretStr(""))
29
+ base_url: str = Field(default=DEFAULT_BASE_URL)
30
+ timeout: float = Field(default=DEFAULT_TIMEOUT)
31
+
32
+ @model_validator(mode="before")
33
+ @classmethod
34
+ def _from_env(cls, values: Any) -> Any:
35
+ if not isinstance(values, dict):
36
+ return values
37
+ if not values.get("api_key"):
38
+ key = os.environ.get("SOFYA_API_KEY", "")
39
+ if not key:
40
+ raise ValueError(
41
+ "Sofya API key not found. Pass api_key or set the "
42
+ "SOFYA_API_KEY environment variable. Get a key at "
43
+ "https://sofya.co/dashboard"
44
+ )
45
+ values["api_key"] = key
46
+ if not values.get("base_url"):
47
+ values["base_url"] = os.environ.get("SOFYA_BASE_URL") or DEFAULT_BASE_URL
48
+ return values
49
+
50
+ def _headers(self) -> dict[str, str]:
51
+ return {
52
+ "Authorization": f"Bearer {self.api_key.get_secret_value()}",
53
+ "Content-Type": "application/json",
54
+ "User-Agent": f"langchain-sofya/{__version__}",
55
+ }
56
+
57
+ def _url(self, path: str) -> str:
58
+ return self.base_url.rstrip("/") + path
59
+
60
+ def post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
61
+ """POST a JSON body and return the JSON response."""
62
+ try:
63
+ with httpx.Client(timeout=self.timeout) as client:
64
+ resp = client.post(self._url(path), json=payload, headers=self._headers())
65
+ except httpx.HTTPError as exc:
66
+ raise ToolException(f"Sofya request failed: {exc}") from exc
67
+ return _handle(resp)
68
+
69
+ async def apost(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
70
+ """Async version of :meth:`post`."""
71
+ try:
72
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
73
+ resp = await client.post(self._url(path), json=payload, headers=self._headers())
74
+ except httpx.HTTPError as exc:
75
+ raise ToolException(f"Sofya request failed: {exc}") from exc
76
+ return _handle(resp)
77
+
78
+
79
+ def _handle(resp: httpx.Response) -> dict[str, Any]:
80
+ if resp.is_success:
81
+ data = resp.json()
82
+ if not isinstance(data, dict):
83
+ raise ToolException("Sofya returned an unexpected response.")
84
+ return data
85
+ raise ToolException(_error_message(resp))
86
+
87
+
88
+ def _error_message(resp: httpx.Response) -> str:
89
+ detail: Any = None
90
+ try:
91
+ body = resp.json()
92
+ detail = body.get("detail") if isinstance(body, dict) else body
93
+ if isinstance(detail, dict):
94
+ detail = detail.get("message") or detail.get("error") or detail
95
+ except ValueError:
96
+ detail = resp.text[:500] or None
97
+
98
+ status = resp.status_code
99
+ if status == 401:
100
+ prefix = "Sofya API key is missing or invalid."
101
+ elif status == 402:
102
+ prefix = "The Sofya account is out of credits. Top up at https://sofya.co/dashboard."
103
+ elif status == 429:
104
+ prefix = "Sofya rate limit reached; retry shortly."
105
+ elif status == 422:
106
+ prefix = "Sofya rejected the request parameters."
107
+ else:
108
+ prefix = f"Sofya API error (HTTP {status})."
109
+ if detail and not isinstance(detail, str):
110
+ detail = str(detail)
111
+ return f"{prefix} {detail}" if detail and detail not in prefix else prefix
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,350 @@
1
+ """LangChain tools for Sofya: web search, fetch, extract and research."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal
6
+
7
+ from langchain_core.callbacks import (
8
+ AsyncCallbackManagerForToolRun,
9
+ CallbackManagerForToolRun,
10
+ )
11
+ from langchain_core.tools import BaseTool
12
+ from pydantic import BaseModel, Field, model_validator
13
+
14
+ from langchain_sofya._client import SofyaAPIWrapper
15
+
16
+ _FRESHNESS = (
17
+ 'Only results from this period: "day", "week", "month", "year", '
18
+ 'or a date range "YYYY-MM-DD:YYYY-MM-DD".'
19
+ )
20
+ _TOPIC = '"general" for web search, "news" for news articles.'
21
+
22
+
23
+ class _SofyaTool(BaseTool):
24
+ """Shared setup: an API wrapper built from api_key / SOFYA_API_KEY."""
25
+
26
+ api_wrapper: SofyaAPIWrapper = Field(default=None) # type: ignore[assignment]
27
+ handle_tool_error: bool = True
28
+
29
+ @model_validator(mode="before")
30
+ @classmethod
31
+ def _build_wrapper(cls, values: Any) -> Any:
32
+ if isinstance(values, dict) and values.get("api_wrapper") is None:
33
+ values["api_wrapper"] = SofyaAPIWrapper(
34
+ **{k: values.pop(k) for k in ("api_key", "base_url", "timeout") if k in values}
35
+ )
36
+ return values
37
+
38
+ def _payload(self, given: dict[str, Any], keys: tuple[str, ...]) -> dict[str, Any]:
39
+ """Call arguments win; unset ones fall back to the tool's own settings."""
40
+ out: dict[str, Any] = {}
41
+ for k in keys:
42
+ v = given.get(k)
43
+ if v is None:
44
+ v = getattr(self, k, None)
45
+ if v is not None:
46
+ out[k] = v
47
+ return out
48
+
49
+
50
+ # --------------------------------------------------------------------- search
51
+
52
+
53
+ class SofyaSearchInput(BaseModel):
54
+ """Input for SofyaSearch."""
55
+
56
+ query: str = Field(description="What to search the web for.")
57
+ search_depth: Literal["basic", "snippets"] | None = Field(
58
+ default=None,
59
+ description='"basic" returns the text of each result page (2 credits); '
60
+ '"snippets" returns short snippets only (1 credit).',
61
+ )
62
+ max_results: int | None = Field(
63
+ default=None, ge=1, le=20, description="How many results to return, 1-20."
64
+ )
65
+ include_answer: bool | None = Field(
66
+ default=None,
67
+ description="Add a short AI answer written from the results (+2 credits).",
68
+ )
69
+ include_domains: list[str] | None = Field(
70
+ default=None,
71
+ description='Only return results from these domains, e.g. ["github.com"]. Up to 10.',
72
+ )
73
+ exclude_domains: list[str] | None = Field(
74
+ default=None,
75
+ description='Leave out results from these domains, e.g. ["pinterest.com"]. Up to 10.',
76
+ )
77
+ topic: Literal["general", "news"] | None = Field(default=None, description=_TOPIC)
78
+ freshness: str | None = Field(default=None, description=_FRESHNESS)
79
+
80
+
81
+ _SEARCH_KEYS = (
82
+ "query",
83
+ "search_depth",
84
+ "max_results",
85
+ "include_answer",
86
+ "include_domains",
87
+ "exclude_domains",
88
+ "topic",
89
+ "freshness",
90
+ )
91
+
92
+
93
+ class SofyaSearch(_SofyaTool): # type: ignore[override]
94
+ """Search the web with Sofya.
95
+
96
+ Returns ranked results with the text of each page (or short snippets), and
97
+ optionally a short AI answer.
98
+
99
+ Setup:
100
+ .. code-block:: bash
101
+
102
+ pip install -U langchain-sofya
103
+ export SOFYA_API_KEY="sofya_live_..."
104
+
105
+ Instantiate:
106
+ .. code-block:: python
107
+
108
+ from langchain_sofya import SofyaSearch
109
+
110
+ tool = SofyaSearch(max_results=5, include_answer=True)
111
+
112
+ Invoke:
113
+ .. code-block:: python
114
+
115
+ tool.invoke({"query": "latest stable Python release"})
116
+ """
117
+
118
+ name: str = "sofya_search"
119
+ description: str = (
120
+ "Search the web for current information. Returns ranked results with "
121
+ "title, URL and page text, and optionally a short AI answer. Input is a "
122
+ "search query; you can filter by domain, topic (general or news) and "
123
+ "freshness."
124
+ )
125
+ args_schema: type[BaseModel] = SofyaSearchInput
126
+
127
+ search_depth: Literal["basic", "snippets"] | None = None
128
+ """Default depth when the call does not set one (API default: basic)."""
129
+ max_results: int | None = None
130
+ """Default number of results, 1-20 (API default: 10)."""
131
+ include_answer: bool | None = None
132
+ """Add a short AI answer by default."""
133
+ include_domains: list[str] | None = None
134
+ """Domains to restrict results to by default."""
135
+ exclude_domains: list[str] | None = None
136
+ """Domains to leave out by default."""
137
+ topic: Literal["general", "news"] | None = None
138
+ """Default topic."""
139
+ freshness: str | None = None
140
+ """Default freshness filter."""
141
+
142
+ def _run(
143
+ self,
144
+ query: str,
145
+ run_manager: CallbackManagerForToolRun | None = None,
146
+ **kwargs: Any,
147
+ ) -> dict[str, Any]:
148
+ payload = self._payload({"query": query, **kwargs}, _SEARCH_KEYS)
149
+ return self.api_wrapper.post("/v1/search", payload)
150
+
151
+ async def _arun(
152
+ self,
153
+ query: str,
154
+ run_manager: AsyncCallbackManagerForToolRun | None = None,
155
+ **kwargs: Any,
156
+ ) -> dict[str, Any]:
157
+ payload = self._payload({"query": query, **kwargs}, _SEARCH_KEYS)
158
+ return await self.api_wrapper.apost("/v1/search", payload)
159
+
160
+
161
+ # ---------------------------------------------------------------------- fetch
162
+
163
+
164
+ class SofyaFetchInput(BaseModel):
165
+ """Input for SofyaFetch."""
166
+
167
+ urls: list[str] = Field(
168
+ min_length=1,
169
+ max_length=10,
170
+ description="URLs to read, up to 10. Web pages and documents (PDF, Word, "
171
+ "Excel, PowerPoint and more).",
172
+ )
173
+
174
+
175
+ class SofyaFetch(_SofyaTool): # type: ignore[override]
176
+ """Read web pages and documents as clean markdown with Sofya.
177
+
178
+ Handles HTML pages and documents such as PDF, DOCX, XLSX and PPTX. Costs
179
+ 1 credit per URL; URLs that fail are not charged.
180
+
181
+ Instantiate:
182
+ .. code-block:: python
183
+
184
+ from langchain_sofya import SofyaFetch
185
+
186
+ tool = SofyaFetch()
187
+
188
+ Invoke:
189
+ .. code-block:: python
190
+
191
+ tool.invoke({"urls": ["https://example.com"]})
192
+ """
193
+
194
+ name: str = "sofya_fetch"
195
+ description: str = (
196
+ "Read one or more web pages or documents (PDF, Word, Excel, PowerPoint) "
197
+ "and return their text as clean markdown. Input is a list of up to 10 URLs."
198
+ )
199
+ args_schema: type[BaseModel] = SofyaFetchInput
200
+
201
+ include_raw_html: bool = False
202
+ """Also return each page's raw HTML."""
203
+
204
+ def _run(
205
+ self,
206
+ urls: list[str],
207
+ run_manager: CallbackManagerForToolRun | None = None,
208
+ ) -> dict[str, Any]:
209
+ payload: dict[str, Any] = {"urls": urls}
210
+ if self.include_raw_html:
211
+ payload["include_raw_html"] = True
212
+ return self.api_wrapper.post("/v1/fetch", payload)
213
+
214
+ async def _arun(
215
+ self,
216
+ urls: list[str],
217
+ run_manager: AsyncCallbackManagerForToolRun | None = None,
218
+ ) -> dict[str, Any]:
219
+ payload: dict[str, Any] = {"urls": urls}
220
+ if self.include_raw_html:
221
+ payload["include_raw_html"] = True
222
+ return await self.api_wrapper.apost("/v1/fetch", payload)
223
+
224
+
225
+ # -------------------------------------------------------------------- extract
226
+
227
+
228
+ class SofyaExtractInput(BaseModel):
229
+ """Input for SofyaExtract."""
230
+
231
+ url: str = Field(description="The page or document to read.")
232
+ prompt: str = Field(
233
+ description='What to pull out of it, e.g. "list every pricing tier with its price".'
234
+ )
235
+
236
+
237
+ class SofyaExtract(_SofyaTool): # type: ignore[override]
238
+ """Answer a question about one page with Sofya.
239
+
240
+ Sofya reads the page and an AI model answers the prompt from its text.
241
+ Costs 10 credits (1 when the page has too little text to answer).
242
+
243
+ Instantiate:
244
+ .. code-block:: python
245
+
246
+ from langchain_sofya import SofyaExtract
247
+
248
+ tool = SofyaExtract()
249
+
250
+ Invoke:
251
+ .. code-block:: python
252
+
253
+ tool.invoke({
254
+ "url": "https://en.wikipedia.org/wiki/Eiffel_Tower",
255
+ "prompt": "How tall is it and when did it open?",
256
+ })
257
+ """
258
+
259
+ name: str = "sofya_extract"
260
+ description: str = (
261
+ "Read one web page or document and return just the information asked "
262
+ "for. Input is a URL and a prompt describing what to extract."
263
+ )
264
+ args_schema: type[BaseModel] = SofyaExtractInput
265
+
266
+ def _run(
267
+ self,
268
+ url: str,
269
+ prompt: str,
270
+ run_manager: CallbackManagerForToolRun | None = None,
271
+ ) -> dict[str, Any]:
272
+ return self.api_wrapper.post("/v1/extract", {"url": url, "prompt": prompt})
273
+
274
+ async def _arun(
275
+ self,
276
+ url: str,
277
+ prompt: str,
278
+ run_manager: AsyncCallbackManagerForToolRun | None = None,
279
+ ) -> dict[str, Any]:
280
+ return await self.api_wrapper.apost("/v1/extract", {"url": url, "prompt": prompt})
281
+
282
+
283
+ # ------------------------------------------------------------------- research
284
+
285
+
286
+ class SofyaResearchInput(BaseModel):
287
+ """Input for SofyaResearch."""
288
+
289
+ query: str = Field(description="The research question or topic.")
290
+ topic: Literal["general", "news"] | None = Field(default=None, description=_TOPIC)
291
+ freshness: str | None = Field(default=None, description=_FRESHNESS)
292
+ max_sources: int | None = Field(
293
+ default=None, ge=5, le=30, description="Most sources to read, 5-30."
294
+ )
295
+
296
+
297
+ _RESEARCH_KEYS = ("query", "topic", "freshness", "max_sources")
298
+
299
+
300
+ class SofyaResearch(_SofyaTool): # type: ignore[override]
301
+ """Research a question across many sources with Sofya.
302
+
303
+ Splits the question into several searches, reads the sources and writes a
304
+ report with citations. Costs 50 credits and usually takes 20-60 seconds.
305
+
306
+ Instantiate:
307
+ .. code-block:: python
308
+
309
+ from langchain_sofya import SofyaResearch
310
+
311
+ tool = SofyaResearch(max_sources=15)
312
+
313
+ Invoke:
314
+ .. code-block:: python
315
+
316
+ tool.invoke({"query": "How do heat pumps perform in cold climates?"})
317
+ """
318
+
319
+ name: str = "sofya_research"
320
+ description: str = (
321
+ "Research an open-ended question across many web sources and return a "
322
+ "written report with citations and the source list. Input is the "
323
+ "question."
324
+ )
325
+ args_schema: type[BaseModel] = SofyaResearchInput
326
+
327
+ topic: Literal["general", "news"] | None = None
328
+ """Default topic."""
329
+ freshness: str | None = None
330
+ """Default freshness filter."""
331
+ max_sources: int | None = None
332
+ """Default number of sources, 5-30 (API default: 20)."""
333
+
334
+ def _run(
335
+ self,
336
+ query: str,
337
+ run_manager: CallbackManagerForToolRun | None = None,
338
+ **kwargs: Any,
339
+ ) -> dict[str, Any]:
340
+ payload = self._payload({"query": query, **kwargs}, _RESEARCH_KEYS)
341
+ return self.api_wrapper.post("/v1/research", payload)
342
+
343
+ async def _arun(
344
+ self,
345
+ query: str,
346
+ run_manager: AsyncCallbackManagerForToolRun | None = None,
347
+ **kwargs: Any,
348
+ ) -> dict[str, Any]:
349
+ payload = self._payload({"query": query, **kwargs}, _RESEARCH_KEYS)
350
+ return await self.api_wrapper.apost("/v1/research", payload)
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-sofya
3
+ Version: 0.1.0
4
+ Summary: LangChain tools for Sofya: web search, page fetch, extraction and cited research for AI agents.
5
+ Project-URL: Homepage, https://sofya.co
6
+ Project-URL: Documentation, https://sofya.co/docs
7
+ Project-URL: Repository, https://github.com/sofya-co/langchain-sofya
8
+ Project-URL: Issues, https://github.com/sofya-co/langchain-sofya/issues
9
+ Author-email: "Sofya, Inc." <contact@sofya.co>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai agents,langchain,search api,sofya,tools,web scraping,web search
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx<1.0.0,>=0.27.0
25
+ Requires-Dist: langchain-core<2.0.0,>=0.3.15
26
+ Requires-Dist: pydantic<3.0.0,>=2.7.4
27
+ Description-Content-Type: text/markdown
28
+
29
+ # langchain-sofya
30
+
31
+ LangChain tools for [Sofya](https://sofya.co): live web search, any page or document as clean markdown, AI extraction from a page, and multi-source research with citations. One API key for all four.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install -U langchain-sofya
37
+ ```
38
+
39
+ Get an API key at [sofya.co/dashboard](https://sofya.co/dashboard) and set it:
40
+
41
+ ```bash
42
+ export SOFYA_API_KEY="sofya_live_..."
43
+ ```
44
+
45
+ Or pass `api_key="sofya_live_..."` to any tool.
46
+
47
+ ## Tools
48
+
49
+ | Tool | Name the model sees | What it does | Credits |
50
+ |---|---|---|---|
51
+ | `SofyaSearch` | `sofya_search` | Web search. Results with the text of each page, or snippets only, plus an optional short AI answer. Filter by domain, topic (general or news) and freshness. | 2 (basic), 1 (snippets), +2 for the answer |
52
+ | `SofyaFetch` | `sofya_fetch` | Up to 10 URLs as clean markdown. Web pages plus PDF, Word, Excel, PowerPoint, OpenDocument, EPUB and more. | 1 per URL, failed URLs free |
53
+ | `SofyaExtract` | `sofya_extract` | Reads one page and answers a prompt about it. | 10 |
54
+ | `SofyaResearch` | `sofya_research` | Splits a question into several searches, reads the sources and writes a report with citations. | 50 |
55
+
56
+ A credit is $0.0025. Pricing: [sofya.co/pricing](https://sofya.co/pricing).
57
+
58
+ ## Use
59
+
60
+ ```python
61
+ from langchain_sofya import SofyaSearch
62
+
63
+ search = SofyaSearch(max_results=5, include_answer=True)
64
+ result = search.invoke({"query": "latest stable Python release"})
65
+ print(result["answer"])
66
+ for r in result["results"]:
67
+ print(r["title"], r["url"])
68
+ ```
69
+
70
+ Arguments set on the tool are defaults; the model can override them per call (for example `max_results`, `include_domains` or `freshness`).
71
+
72
+ ```python
73
+ from langchain_sofya import SofyaExtract, SofyaFetch, SofyaResearch
74
+
75
+ SofyaFetch().invoke({"urls": ["https://example.com/report.pdf"]})
76
+
77
+ SofyaExtract().invoke({
78
+ "url": "https://en.wikipedia.org/wiki/Eiffel_Tower",
79
+ "prompt": "How tall is it and when did it open?",
80
+ })
81
+
82
+ SofyaResearch(max_sources=15).invoke({"query": "How do heat pumps perform in cold climates?"})
83
+ ```
84
+
85
+ Every tool also has an async version through `ainvoke`.
86
+
87
+ ### With an agent
88
+
89
+ ```python
90
+ from langchain.agents import create_agent
91
+ from langchain_sofya import SofyaExtract, SofyaFetch, SofyaSearch
92
+
93
+ agent = create_agent(
94
+ "anthropic:claude-sonnet-5-5",
95
+ tools=[SofyaSearch(), SofyaFetch(), SofyaExtract()],
96
+ )
97
+ agent.invoke({"messages": [{"role": "user", "content": "What changed in the latest Node.js LTS?"}]})
98
+ ```
99
+
100
+ Errors (invalid key, out of credits, rate limits) come back to the model as a tool message instead of raising, so an agent can tell the user what happened.
101
+
102
+ ## Settings
103
+
104
+ | Argument | Env var | Default |
105
+ |---|---|---|
106
+ | `api_key` | `SOFYA_API_KEY` | required |
107
+ | `base_url` | `SOFYA_BASE_URL` | `https://sofya.co` |
108
+ | `timeout` | - | 120 seconds (research can take up to a minute) |
109
+
110
+ ## Links
111
+
112
+ - API docs: [sofya.co/docs](https://sofya.co/docs)
113
+ - MCP server, for clients that speak MCP instead: `https://mcp.sofya.co/mcp`
114
+ - Support: support@sofya.co
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,9 @@
1
+ langchain_sofya/__init__.py,sha256=yDCixYxOqorjn6rzPi39LXeJa1JwaM-2DboC2ukR1us,425
2
+ langchain_sofya/_client.py,sha256=IsdAuihJdtS4FQadcQvk0hCbG5McK_MBDzT6ybKnPRE,4081
3
+ langchain_sofya/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
4
+ langchain_sofya/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ langchain_sofya/tools.py,sha256=amSSPFy5SCb-httudyXZqOgm6AiW0BfkVQKPzlwJslc,11165
6
+ langchain_sofya-0.1.0.dist-info/METADATA,sha256=veXS-8ke4pqyupzKrzPR4sb4iyZt6boXdYRo8rDqZns,4287
7
+ langchain_sofya-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
8
+ langchain_sofya-0.1.0.dist-info/licenses/LICENSE,sha256=ZG-YkZd8UposWuGcPaZ9D3OIjnqw-Ub4B0a0paKe7bg,1068
9
+ langchain_sofya-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sofya, Inc.
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.