kensho-kfinance 1.2.2__py3-none-any.whl → 2.0.1__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.

Potentially problematic release.


This version of kensho-kfinance might be problematic. Click here for more details.

Files changed (35) hide show
  1. {kensho_kfinance-1.2.2.dist-info → kensho_kfinance-2.0.1.dist-info}/METADATA +5 -3
  2. kensho_kfinance-2.0.1.dist-info/RECORD +40 -0
  3. {kensho_kfinance-1.2.2.dist-info → kensho_kfinance-2.0.1.dist-info}/WHEEL +1 -1
  4. kfinance/CHANGELOG.md +6 -0
  5. kfinance/constants.py +40 -8
  6. kfinance/fetch.py +15 -9
  7. kfinance/kfinance.py +128 -38
  8. kfinance/meta_classes.py +3 -9
  9. kfinance/tests/test_fetch.py +7 -6
  10. kfinance/tests/test_tools.py +430 -0
  11. kfinance/tool_calling/README.md +38 -0
  12. kfinance/tool_calling/__init__.py +47 -0
  13. kfinance/tool_calling/get_business_relationship_from_identifier.py +28 -0
  14. kfinance/tool_calling/get_capitalization_from_identifier.py +35 -0
  15. kfinance/tool_calling/get_company_id_from_identifier.py +14 -0
  16. kfinance/tool_calling/get_cusip_from_ticker.py +18 -0
  17. kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +17 -0
  18. kfinance/tool_calling/get_financial_line_item_from_identifier.py +45 -0
  19. kfinance/tool_calling/get_financial_statement_from_identifier.py +41 -0
  20. kfinance/tool_calling/get_history_metadata_from_identifier.py +15 -0
  21. kfinance/tool_calling/get_info_from_identifier.py +14 -0
  22. kfinance/tool_calling/get_isin_from_ticker.py +18 -0
  23. kfinance/tool_calling/get_latest.py +21 -0
  24. kfinance/tool_calling/get_n_quarters_ago.py +21 -0
  25. kfinance/tool_calling/get_prices_from_identifier.py +44 -0
  26. kfinance/tool_calling/get_security_id_from_identifier.py +14 -0
  27. kfinance/tool_calling/get_trading_item_id_from_identifier.py +14 -0
  28. kfinance/tool_calling/shared_models.py +53 -0
  29. kfinance/version.py +2 -2
  30. kensho_kfinance-1.2.2.dist-info/RECORD +0 -23
  31. kfinance/llm_tools.py +0 -747
  32. kfinance/tool_schemas.py +0 -148
  33. {kensho_kfinance-1.2.2.dist-info → kensho_kfinance-2.0.1.dist-info}/licenses/AUTHORS.md +0 -0
  34. {kensho_kfinance-1.2.2.dist-info → kensho_kfinance-2.0.1.dist-info}/licenses/LICENSE +0 -0
  35. {kensho_kfinance-1.2.2.dist-info → kensho_kfinance-2.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,430 @@
1
+ from datetime import date, datetime
2
+
3
+ from langchain_core.utils.function_calling import convert_to_openai_tool
4
+ import pytest
5
+ from requests_mock import Mocker
6
+ import time_machine
7
+
8
+ from kfinance.constants import BusinessRelationshipType, Capitalization, StatementType
9
+ from kfinance.kfinance import Client
10
+ from kfinance.tool_calling import (
11
+ GetCompanyIdFromIdentifier,
12
+ GetEarningsCallDatetimesFromIdentifier,
13
+ GetFinancialLineItemFromIdentifier,
14
+ GetFinancialStatementFromIdentifier,
15
+ GetHistoryMetadataFromIdentifier,
16
+ GetInfoFromIdentifier,
17
+ GetIsinFromTicker,
18
+ GetLatest,
19
+ GetNQuartersAgo,
20
+ GetPricesFromIdentifier,
21
+ GetSecurityIdFromIdentifier,
22
+ GetTradingItemIdFromIdentifier,
23
+ )
24
+ from kfinance.tool_calling.get_business_relationship_from_identifier import (
25
+ GetBusinessRelationshipFromIdentifier,
26
+ GetBusinessRelationshipFromIdentifierArgs,
27
+ )
28
+ from kfinance.tool_calling.get_capitalization_from_identifier import (
29
+ GetCapitalizationFromIdentifier,
30
+ GetCapitalizationFromIdentifierArgs,
31
+ )
32
+ from kfinance.tool_calling.get_cusip_from_ticker import GetCusipFromTicker, GetCusipFromTickerArgs
33
+ from kfinance.tool_calling.get_financial_line_item_from_identifier import (
34
+ GetFinancialLineItemFromIdentifierArgs,
35
+ )
36
+ from kfinance.tool_calling.get_financial_statement_from_identifier import (
37
+ GetFinancialStatementFromIdentifierArgs,
38
+ )
39
+ from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTickerArgs
40
+ from kfinance.tool_calling.get_latest import GetLatestArgs
41
+ from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgoArgs
42
+ from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifierArgs
43
+ from kfinance.tool_calling.shared_models import ToolArgsWithIdentifier
44
+
45
+
46
+ SPGI_COMPANY_ID = 21719
47
+ SPGI_SECURITY_ID = 2629107
48
+ SPGI_TRADING_ITEM_ID = 2629108
49
+
50
+
51
+ @pytest.fixture
52
+ def mock_client(requests_mock: Mocker) -> Client:
53
+ """Create a KFinanceApiClient with a mock response for the SPGI id triple."""
54
+
55
+ client = Client(refresh_token="foo")
56
+ # Set access token so that the client doesn't try to fetch it.
57
+ client.kfinance_api_client._access_token = "foo" # noqa: SLF001
58
+ client.kfinance_api_client._access_token_expiry = datetime(2100, 1, 1).timestamp() # noqa: SLF001
59
+
60
+ # Create a mock for the SPGI id triple.
61
+ requests_mock.get(
62
+ url="https://kfinance.kensho.com/api/v1/id/SPGI",
63
+ json={
64
+ "trading_item_id": SPGI_TRADING_ITEM_ID,
65
+ "security_id": SPGI_SECURITY_ID,
66
+ "company_id": SPGI_COMPANY_ID,
67
+ },
68
+ )
69
+ return client
70
+
71
+
72
+ class TestGetBusinessRelationshipFromIdentifier:
73
+ def test_get_business_relationship_from_identifier(
74
+ self, requests_mock: Mocker, mock_client: Client
75
+ ):
76
+ """
77
+ GIVEN the GetBusinessRelationshipFromIdentifier tool
78
+ WHEN we request SPGI suppliers
79
+ THEN we get back the SPGI suppliers
80
+ """
81
+ supplier_resp = {"current": [883103], "previous": [472898, 8182358]}
82
+
83
+ requests_mock.get(
84
+ url=f"https://kfinance.kensho.com/api/v1/relationship/{SPGI_COMPANY_ID}/supplier",
85
+ json=supplier_resp,
86
+ )
87
+
88
+ tool = GetBusinessRelationshipFromIdentifier(kfinance_client=mock_client)
89
+ args = GetBusinessRelationshipFromIdentifierArgs(
90
+ identifier="SPGI", business_relationship=BusinessRelationshipType.supplier
91
+ )
92
+ resp = tool.run(args.model_dump(mode="json"))
93
+ # Companies is a set, so we have to sort the result
94
+ resp["previous"].sort()
95
+ assert resp == supplier_resp
96
+
97
+
98
+ class TestGetCapitalizationFromIdentifier:
99
+ def test_get_capitalization_from_identifier(self, requests_mock: Mocker, mock_client: Client):
100
+ """
101
+ GIVEN the GetCapitalizationFromIdentifier tool
102
+ WHEN we request the SPGI market cap
103
+ THEN we get back the SPGI market cap
104
+ """
105
+
106
+ requests_mock.get(
107
+ url=f"https://kfinance.kensho.com/api/v1/market_cap/{SPGI_COMPANY_ID}/none/none",
108
+ json={
109
+ "market_caps": [
110
+ {
111
+ "date": "2024-04-10",
112
+ "market_cap": "132766738270.000000",
113
+ "tev": "147455738270.000000",
114
+ "shares_outstanding": 313099562,
115
+ },
116
+ {
117
+ "date": "2024-04-11",
118
+ "market_cap": "132416066761.000000",
119
+ "tev": "147105066761.000000",
120
+ "shares_outstanding": 313099562,
121
+ },
122
+ ]
123
+ },
124
+ )
125
+
126
+ expected_response = "| date | market_cap |\n|:-----------|-------------:|\n| 2024-04-10 | 1.32767e+11 |\n| 2024-04-11 | 1.32416e+11 |"
127
+
128
+ tool = GetCapitalizationFromIdentifier(kfinance_client=mock_client)
129
+ args = GetCapitalizationFromIdentifierArgs(
130
+ identifier="SPGI", capitalization=Capitalization.market_cap
131
+ )
132
+ response = tool.run(args.model_dump(mode="json"))
133
+ assert response == expected_response
134
+
135
+
136
+ class TestGetCompanyIdFromIdentifier:
137
+ def test_get_company_id_from_identifier(self, mock_client: Client):
138
+ """
139
+ GIVEN the GetCompanyIdFromIdentifier tool
140
+ WHEN request the company id for SPGI
141
+ THEN we get back the SPGI company id
142
+ """
143
+ tool = GetCompanyIdFromIdentifier(kfinance_client=mock_client)
144
+ resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
145
+ assert resp == SPGI_COMPANY_ID
146
+
147
+
148
+ class TestGetCusipFromTicker:
149
+ def test_get_cusip_from_ticker(self, requests_mock: Mocker, mock_client: Client):
150
+ """
151
+ GIVEN the GetCusipFromTicker tool
152
+ WHEN we pass args with the SPGI ticker
153
+ THEN we get back the SPGI cusip
154
+ """
155
+
156
+ spgi_cusip = "78409V104"
157
+ requests_mock.get(
158
+ url=f"https://kfinance.kensho.com/api/v1/cusip/{SPGI_SECURITY_ID}",
159
+ json={"cusip": spgi_cusip},
160
+ )
161
+ tool = GetCusipFromTicker(kfinance_client=mock_client)
162
+ resp = tool.run(GetCusipFromTickerArgs(ticker_str="SPGI").model_dump(mode="json"))
163
+ assert resp == spgi_cusip
164
+
165
+
166
+ class TestGetEarningsCallDatetimesFromTicker:
167
+ def test_get_earnings_call_datetimes_from_ticker(
168
+ self, requests_mock: Mocker, mock_client: Client
169
+ ):
170
+ """
171
+ GIVEN the GetEarningsCallDatetimesFromIdentifier tool
172
+ WHEN we request earnings call datetimes for SPGI
173
+ THEN we get back the expected SPGI earnings call datetimes
174
+ """
175
+
176
+ requests_mock.get(
177
+ url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}/dates",
178
+ json={"earnings": ["2025-04-29T12:30:00", "2025-02-11T13:30:00"]},
179
+ )
180
+ expected_response = '["2025-04-29T12:30:00+00:00", "2025-02-11T13:30:00+00:00"]'
181
+
182
+ tool = GetEarningsCallDatetimesFromIdentifier(kfinance_client=mock_client)
183
+ response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
184
+ assert response == expected_response
185
+
186
+
187
+ class TestGetFinancialLineItemFromIdentifier:
188
+ def test_get_financial_line_item_from_identifier(
189
+ self, mock_client: Client, requests_mock: Mocker
190
+ ):
191
+ """
192
+ GIVEN the GetFinancialLineItemFromIdentifier tool
193
+ WHEN we request SPGI revenue
194
+ THEN we get back the SPGI revenue
195
+ """
196
+
197
+ requests_mock.get(
198
+ url=f"https://kfinance.kensho.com/api/v1/line_item/{SPGI_COMPANY_ID}/revenue/none/none/none/none/none",
199
+ json={
200
+ "line_item": {
201
+ "2020": "7442000000.000000",
202
+ "2021": "8297000000.000000",
203
+ "2022": "11181000000.000000",
204
+ "2023": "12497000000.000000",
205
+ "2024": "14208000000.000000",
206
+ }
207
+ },
208
+ )
209
+ expected_response = "| | 2020 | 2021 | 2022 | 2023 | 2024 |\n|:--------|----------:|----------:|-----------:|-----------:|-----------:|\n| revenue | 7.442e+09 | 8.297e+09 | 1.1181e+10 | 1.2497e+10 | 1.4208e+10 |"
210
+
211
+ tool = GetFinancialLineItemFromIdentifier(kfinance_client=mock_client)
212
+ args = GetFinancialLineItemFromIdentifierArgs(identifier="SPGI", line_item="revenue")
213
+ response = tool.run(args.model_dump(mode="json"))
214
+ assert response == expected_response
215
+
216
+ def test_line_items_and_aliases_included_in_schema(self, mock_client: Client):
217
+ """
218
+ GIVEN a GetFinancialLineItemFromIdentifier tool
219
+ WHEN we generate an openai schema from the tool
220
+ THEN all line items and aliases are included in the line item enum
221
+ """
222
+ tool = GetFinancialLineItemFromIdentifier(kfinance_client=mock_client)
223
+ oai_schema = convert_to_openai_tool(tool)
224
+ line_items = oai_schema["function"]["parameters"]["properties"]["line_item"]["enum"]
225
+ # revenue is a line item
226
+ assert "revenue" in line_items
227
+ # normal_revenue is an alias for revenue
228
+ assert "normal_revenue" in line_items
229
+
230
+
231
+ class TestGetFinancialStatementFromIdentifier:
232
+ def test_get_financial_statement_from_identifier(
233
+ self, mock_client: Client, requests_mock: Mocker
234
+ ):
235
+ """
236
+ GIVEN the GetFinancialLineItemFromIdentifier tool
237
+ WHEN we request the SPGI income statement
238
+ THEN we get back the SPGI income statement
239
+ """
240
+
241
+ requests_mock.get(
242
+ url=f"https://kfinance.kensho.com/api/v1/statements/{SPGI_COMPANY_ID}/income_statement/none/none/none/none/none",
243
+ # truncated from the original API response
244
+ json={
245
+ "statements": {
246
+ "2020": {"Revenues": "7442000000.000000", "Total Revenues": "7442000000.000000"}
247
+ }
248
+ },
249
+ )
250
+ expected_response = "| | 2020 |\n|:---------------|----------:|\n| Revenues | 7.442e+09 |\n| Total Revenues | 7.442e+09 |"
251
+
252
+ tool = GetFinancialStatementFromIdentifier(kfinance_client=mock_client)
253
+ args = GetFinancialStatementFromIdentifierArgs(
254
+ identifier="SPGI", statement=StatementType.income_statement
255
+ )
256
+ response = tool.run(args.model_dump(mode="json"))
257
+ assert response == expected_response
258
+
259
+
260
+ class TestGetHistoryMetadataFromIdentifier:
261
+ def test_get_history_metadata_from_identifier(self, mock_client: Client, requests_mock: Mocker):
262
+ """
263
+ GIVEN the GetHistoryMetadataFromIdentifier tool
264
+ WHEN request history metadata for SPGI
265
+ THEN we get back the SPGI history metadata
266
+ """
267
+
268
+ metadata_resp = {
269
+ "currency": "USD",
270
+ "exchange_name": "NYSE",
271
+ "first_trade_date": "1968-01-02",
272
+ "instrument_type": "Equity",
273
+ "symbol": "SPGI",
274
+ }
275
+ expected_resp = {
276
+ "currency": "USD",
277
+ "exchange_name": "NYSE",
278
+ "first_trade_date": date(1968, 1, 2),
279
+ "instrument_type": "Equity",
280
+ "symbol": "SPGI",
281
+ }
282
+ requests_mock.get(
283
+ url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/metadata",
284
+ json=metadata_resp,
285
+ )
286
+
287
+ tool = GetHistoryMetadataFromIdentifier(kfinance_client=mock_client)
288
+ resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
289
+ assert resp == expected_resp
290
+
291
+
292
+ class TestGetInfoFromIdentifier:
293
+ def test_get_info_from_identifier(self, mock_client: Client, requests_mock: Mocker):
294
+ """
295
+ GIVEN the GetInfoFromIdentifier tool
296
+ WHEN request info for SPGI
297
+ THEN we get back info for SPGI
298
+ """
299
+
300
+ # truncated from the original
301
+ info_resp = {"name": "S&P Global Inc.", "status": "Operating"}
302
+ requests_mock.get(
303
+ url=f"https://kfinance.kensho.com/api/v1/info/{SPGI_COMPANY_ID}",
304
+ json=info_resp,
305
+ )
306
+
307
+ tool = GetInfoFromIdentifier(kfinance_client=mock_client)
308
+ resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
309
+ assert resp == str(info_resp)
310
+
311
+
312
+ class TestGetIsinFromTicker:
313
+ def test_get_isin_from_ticker(self, requests_mock: Mocker, mock_client: Client):
314
+ """
315
+ GIVEN the GetIsinFromTicker tool
316
+ WHEN we pass args with the SPGI ticker
317
+ THEN we get back the SPGI isin
318
+ """
319
+
320
+ spgi_isin = "US78409V1044"
321
+ requests_mock.get(
322
+ url=f"https://kfinance.kensho.com/api/v1/isin/{SPGI_SECURITY_ID}",
323
+ json={"isin": spgi_isin},
324
+ )
325
+
326
+ tool = GetIsinFromTicker(kfinance_client=mock_client)
327
+ resp = tool.run(GetIsinFromTickerArgs(ticker_str="SPGI").model_dump(mode="json"))
328
+ assert resp == spgi_isin
329
+
330
+
331
+ class TestGetLatest:
332
+ @time_machine.travel(datetime(2025, 1, 1, 12, tzinfo=datetime.now().astimezone().tzinfo))
333
+ def test_get_latest(self, mock_client: Client):
334
+ """
335
+ GIVEN the GetLatest tool
336
+ WHEN request latest info
337
+ THEN we get back latest info
338
+ """
339
+
340
+ expected_resp = {
341
+ "annual": {"latest_year": 2024},
342
+ "now": {
343
+ "current_date": "2025-01-01",
344
+ "current_month": 1,
345
+ "current_quarter": 1,
346
+ "current_year": 2025,
347
+ },
348
+ "quarterly": {"latest_quarter": 4, "latest_year": 2024},
349
+ }
350
+ tool = GetLatest(kfinance_client=mock_client)
351
+ resp = tool.run(GetLatestArgs().model_dump(mode="json"))
352
+ assert resp == expected_resp
353
+
354
+
355
+ class TestGetNQuartersAgo:
356
+ @time_machine.travel(datetime(2025, 1, 1, 12, tzinfo=datetime.now().astimezone().tzinfo))
357
+ def test_get_n_quarters_ago(self, mock_client: Client):
358
+ """
359
+ GIVEN the GetNQuartersAgo tool
360
+ WHEN we request 3 quarters ago
361
+ THEN we get back 3 quarters ago
362
+ """
363
+
364
+ expected_resp = {"quarter": 2, "year": 2024}
365
+ tool = GetNQuartersAgo(kfinance_client=mock_client)
366
+ resp = tool.run(GetNQuartersAgoArgs(n=3).model_dump(mode="json"))
367
+ assert resp == expected_resp
368
+
369
+
370
+ class TestPricesFromIdentifier:
371
+ def test_get_prices_from_identifier(self, mock_client: Client, requests_mock: Mocker):
372
+ """
373
+ GIVEN the GetPricesFromIdentifier tool
374
+ WHEN we request prices for SPGI
375
+ THEN we get back prices for SPGI
376
+ """
377
+
378
+ requests_mock.get(
379
+ url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/none/none/day/adjusted",
380
+ # truncated response
381
+ json={
382
+ "prices": [
383
+ {
384
+ "date": "2024-04-11",
385
+ "open": "424.260000",
386
+ "high": "425.990000",
387
+ "low": "422.040000",
388
+ "close": "422.920000",
389
+ "volume": "1129158",
390
+ },
391
+ {
392
+ "date": "2024-04-12",
393
+ "open": "419.230000",
394
+ "high": "421.940000",
395
+ "low": "416.450000",
396
+ "close": "417.810000",
397
+ "volume": "1182229",
398
+ },
399
+ ]
400
+ },
401
+ )
402
+ expected_response = "| date | open | high | low | close | volume |\n|:-----------|-------:|-------:|-------:|--------:|------------:|\n| 2024-04-11 | 424.26 | 425.99 | 422.04 | 422.92 | 1.12916e+06 |\n| 2024-04-12 | 419.23 | 421.94 | 416.45 | 417.81 | 1.18223e+06 |"
403
+
404
+ tool = GetPricesFromIdentifier(kfinance_client=mock_client)
405
+ response = tool.run(GetPricesFromIdentifierArgs(identifier="SPGI").model_dump(mode="json"))
406
+ assert response == expected_response
407
+
408
+
409
+ class TestGetSecurityIdFromIdentifier:
410
+ def test_get_security_id_from_identifier(self, mock_client: Client):
411
+ """
412
+ GIVEN the GetSecurityIdFromIdentifier tool
413
+ WHEN we request the security id for SPGI
414
+ THEN we get back the SPGI primary security id
415
+ """
416
+ tool = GetSecurityIdFromIdentifier(kfinance_client=mock_client)
417
+ resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
418
+ assert resp == SPGI_SECURITY_ID
419
+
420
+
421
+ class TestGetTradingItemIdFromIdentifier:
422
+ def test_get_security_id_from_identifier(self, mock_client: Client):
423
+ """
424
+ GIVEN the GetTradingItemIdFromIdentifier tool
425
+ WHEN we request the trading item id for SPGI
426
+ THEN we get back the SPGI primary trading item id
427
+ """
428
+ tool = GetTradingItemIdFromIdentifier(kfinance_client=mock_client)
429
+ resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
430
+ assert resp == SPGI_TRADING_ITEM_ID
@@ -0,0 +1,38 @@
1
+ # Tool Calling
2
+
3
+ The tools defined in this directory are intended for tool calling.
4
+
5
+ Each tool is a subclass of [KfinanceTool](shared_models.py), which is
6
+ turn a subclass of `BaseTool` from langchain. We use langchain to convert these tools
7
+ into LLM-specific tool descriptions.
8
+
9
+ ### KfinanceTool
10
+ Each `KfinanceTool` requires the following attributes to be defined:
11
+ - name: the function name
12
+ - description: the description of the function passed to an LLM. This should not include a description
13
+ of the arguments.
14
+ - args_schema: A pydantic model defining the input schema for the function (more on that below)
15
+ - _run: the source code for the tool.
16
+
17
+ All new tools have to be added to the `ALL_TOOLS` [list](__init__.py).
18
+
19
+
20
+ When initializing a `KfinanceTool`, it's always required to pass in an initialized Kfinance
21
+ `Client`, which can be accessed from within the tool as `self.kfinance_client`. This allows us to
22
+ make kfinance api calls without needing to get a kfinance client passed in with every call.
23
+
24
+ If tools are called from langchain, then langchain handles the deserialization of arguments before
25
+ calling the tools. If tools are called without langchain, then we have to handle that
26
+ deserialization ourselves. The deserialization step is handled by
27
+ `KfinanceTool.run_without_langchain`.
28
+
29
+ ### args_schema Pydantic Model
30
+ Each `KfinanceTool` has a corresponding pydantic model that defines the call arguments for the tool.
31
+ We use langchain to convert these pydantic models into llm-specific argument schemas.
32
+ - Each field should contain a `description` and, where feasible, a `default`.
33
+ - Use of python objects is preferred over string types. For example enums, dates, and datetimes all
34
+ work.
35
+ - The order, type, and default of arguments in the `tool_args` has to match the order, type, and
36
+ default of arguments in the `_run` function.
37
+ - If your tool takes an `identifier` as its first argument, subclass `ToolArgsWithIdentifier` to
38
+ ensure that the description of the identifier remains consistent across tools.
@@ -0,0 +1,47 @@
1
+ from typing import Type
2
+
3
+ from kfinance.tool_calling.get_business_relationship_from_identifier import (
4
+ GetBusinessRelationshipFromIdentifier,
5
+ )
6
+ from kfinance.tool_calling.get_capitalization_from_identifier import GetCapitalizationFromIdentifier
7
+ from kfinance.tool_calling.get_company_id_from_identifier import GetCompanyIdFromIdentifier
8
+ from kfinance.tool_calling.get_cusip_from_ticker import GetCusipFromTicker
9
+ from kfinance.tool_calling.get_earnings_call_datetimes_from_identifier import (
10
+ GetEarningsCallDatetimesFromIdentifier,
11
+ )
12
+ from kfinance.tool_calling.get_financial_line_item_from_identifier import (
13
+ GetFinancialLineItemFromIdentifier,
14
+ )
15
+ from kfinance.tool_calling.get_financial_statement_from_identifier import (
16
+ GetFinancialStatementFromIdentifier,
17
+ )
18
+ from kfinance.tool_calling.get_history_metadata_from_identifier import (
19
+ GetHistoryMetadataFromIdentifier,
20
+ )
21
+ from kfinance.tool_calling.get_info_from_identifier import GetInfoFromIdentifier
22
+ from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTicker
23
+ from kfinance.tool_calling.get_latest import GetLatest
24
+ from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgo
25
+ from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifier
26
+ from kfinance.tool_calling.get_security_id_from_identifier import GetSecurityIdFromIdentifier
27
+ from kfinance.tool_calling.get_trading_item_id_from_identifier import GetTradingItemIdFromIdentifier
28
+ from kfinance.tool_calling.shared_models import KfinanceTool
29
+
30
+
31
+ ALL_TOOLS: list[Type[KfinanceTool]] = [
32
+ GetLatest,
33
+ GetNQuartersAgo,
34
+ GetCompanyIdFromIdentifier,
35
+ GetSecurityIdFromIdentifier,
36
+ GetTradingItemIdFromIdentifier,
37
+ GetIsinFromTicker,
38
+ GetCusipFromTicker,
39
+ GetInfoFromIdentifier,
40
+ GetEarningsCallDatetimesFromIdentifier,
41
+ GetHistoryMetadataFromIdentifier,
42
+ GetPricesFromIdentifier,
43
+ GetCapitalizationFromIdentifier,
44
+ GetFinancialStatementFromIdentifier,
45
+ GetFinancialLineItemFromIdentifier,
46
+ GetBusinessRelationshipFromIdentifier,
47
+ ]
@@ -0,0 +1,28 @@
1
+ from typing import Type
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from kfinance.constants import BusinessRelationshipType
6
+ from kfinance.kfinance import BusinessRelationships
7
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
8
+
9
+
10
+ class GetBusinessRelationshipFromIdentifierArgs(ToolArgsWithIdentifier):
11
+ # no description because the description for enum fields comes from the enum docstring.
12
+ business_relationship: BusinessRelationshipType
13
+
14
+
15
+ class GetBusinessRelationshipFromIdentifier(KfinanceTool):
16
+ name: str = "get_business_relationship_from_identifier"
17
+ description: str = 'Get the current and previous company IDs that are relationship_type of a given identifier. For example, "What are the current distributors of SPGI?" or "What are the previous borrowers of JPM?"'
18
+ args_schema: Type[BaseModel] = GetBusinessRelationshipFromIdentifierArgs
19
+
20
+ def _run(self, identifier: str, business_relationship: BusinessRelationshipType) -> dict:
21
+ ticker = self.kfinance_client.ticker(identifier)
22
+ business_relationship_obj: BusinessRelationships = getattr(
23
+ ticker, business_relationship.value
24
+ )
25
+ return {
26
+ "current": [company.company_id for company in business_relationship_obj.current],
27
+ "previous": [company.company_id for company in business_relationship_obj.previous],
28
+ }
@@ -0,0 +1,35 @@
1
+ from datetime import date
2
+
3
+ from pydantic import Field
4
+
5
+ from kfinance.constants import Capitalization
6
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
7
+
8
+
9
+ class GetCapitalizationFromIdentifierArgs(ToolArgsWithIdentifier):
10
+ # no description because the description for enum fields comes from the enum docstring.
11
+ capitalization: Capitalization
12
+ start_date: date | None = Field(
13
+ description="The start date for historical capitalization retrieval", default=None
14
+ )
15
+ end_date: date | None = Field(
16
+ description="The end date for historical capitalization retrieval", default=None
17
+ )
18
+
19
+
20
+ class GetCapitalizationFromIdentifier(KfinanceTool):
21
+ name: str = "get_capitalization_from_identifier"
22
+ description: str = "Get the historical market cap, tev (Total Enterprise Value), or shares outstanding of an identifier between inclusive start_date and inclusive end date. When requesting the most recent values, leave start_date and end_date empty."
23
+ args_schema = GetCapitalizationFromIdentifierArgs
24
+
25
+ def _run(
26
+ self,
27
+ identifier: str,
28
+ capitalization: Capitalization,
29
+ start_date: str | None = None,
30
+ end_date: str | None = None,
31
+ ) -> str:
32
+ ticker = self.kfinance_client.ticker(identifier)
33
+ return getattr(ticker, capitalization.value)(
34
+ start_date=start_date, end_date=end_date
35
+ ).to_markdown()
@@ -0,0 +1,14 @@
1
+ from typing import Type
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
6
+
7
+
8
+ class GetCompanyIdFromIdentifier(KfinanceTool):
9
+ name: str = "get_company_id_from_identifier"
10
+ description: str = "Get the company id associated with an identifier."
11
+ args_schema: Type[BaseModel] = ToolArgsWithIdentifier
12
+
13
+ def _run(self, identifier: str) -> int:
14
+ return self.kfinance_client.ticker(identifier).company_id
@@ -0,0 +1,18 @@
1
+ from typing import Type
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from kfinance.tool_calling.shared_models import KfinanceTool
6
+
7
+
8
+ class GetCusipFromTickerArgs(BaseModel):
9
+ ticker_str: str = Field(description="The ticker")
10
+
11
+
12
+ class GetCusipFromTicker(KfinanceTool):
13
+ name: str = "get_cusip_from_ticker"
14
+ description: str = "Get the CUSIP associated with a ticker."
15
+ args_schema: Type[BaseModel] = GetCusipFromTickerArgs
16
+
17
+ def _run(self, ticker_str: str) -> str:
18
+ return self.kfinance_client.ticker(ticker_str).cusip
@@ -0,0 +1,17 @@
1
+ import json
2
+ from typing import Type
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
7
+
8
+
9
+ class GetEarningsCallDatetimesFromIdentifier(KfinanceTool):
10
+ name: str = "get_earnings_call_datetimes_from_identifier"
11
+ description: str = "Get earnings call datetimes associated with an identifier."
12
+ args_schema: Type[BaseModel] = ToolArgsWithIdentifier
13
+
14
+ def _run(self, identifier: str) -> str:
15
+ ticker = self.kfinance_client.ticker(identifier)
16
+ earnings_call_datetimes = ticker.earnings_call_datetimes
17
+ return json.dumps([dt.isoformat() for dt in earnings_call_datetimes])
@@ -0,0 +1,45 @@
1
+ from typing import Literal, Type
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from kfinance.constants import LINE_ITEM_NAMES_AND_ALIASES, PeriodType
6
+ from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
7
+
8
+
9
+ class GetFinancialLineItemFromIdentifierArgs(ToolArgsWithIdentifier):
10
+ # Note: mypy will not enforce this literal because of the type: ignore.
11
+ # But pydantic still uses the literal to check for allowed values and only includes
12
+ # allowed values in generated schemas.
13
+ line_item: Literal[tuple(LINE_ITEM_NAMES_AND_ALIASES)] = Field( # type: ignore[valid-type]
14
+ description="The type of financial line_item requested"
15
+ )
16
+ period_type: PeriodType | None = Field(default=None, description="The period type")
17
+ start_year: int | None = Field(default=None, description="The starting year for the data range")
18
+ end_year: int | None = Field(default=None, description="The ending year for the data range")
19
+ start_quarter: Literal[1, 2, 3, 4] | None = Field(default=None, description="Starting quarter")
20
+ end_quarter: Literal[1, 2, 3, 4] | None = Field(default=None, description="Ending quarter")
21
+
22
+
23
+ class GetFinancialLineItemFromIdentifier(KfinanceTool):
24
+ name: str = "get_financial_line_item_from_identifier"
25
+ description: str = "Get the financial line item associated with an identifier."
26
+ args_schema: Type[BaseModel] = GetFinancialLineItemFromIdentifierArgs
27
+
28
+ def _run(
29
+ self,
30
+ identifier: str,
31
+ line_item: str,
32
+ period_type: PeriodType | None = None,
33
+ start_year: int | None = None,
34
+ end_year: int | None = None,
35
+ start_quarter: Literal[1, 2, 3, 4] | None = None,
36
+ end_quarter: Literal[1, 2, 3, 4] | None = None,
37
+ ) -> str:
38
+ ticker = self.kfinance_client.ticker(identifier)
39
+ return getattr(ticker, line_item)(
40
+ period_type=period_type,
41
+ start_year=start_year,
42
+ end_year=end_year,
43
+ start_quarter=start_quarter,
44
+ end_quarter=end_quarter,
45
+ ).to_markdown()