from langchain.tools import StructuredTool
from pydantic import BaseModel, Field
import requests
API_KEY = "ds_xxxxx"
HEADERS = {"X-API-Key": API_KEY}
BASE_URL = "https://api.debtstack.ai/v1"
# === Schemas ===
class SearchCompaniesInput(BaseModel):
ticker: str = Field(default=None, description="Comma-separated tickers (e.g., 'AAPL,MSFT')")
sector: str = Field(default=None, description="Business sector (e.g., 'Technology')")
min_leverage: float = Field(default=None, description="Minimum leverage ratio")
max_leverage: float = Field(default=None, description="Maximum leverage ratio")
has_structural_sub: bool = Field(default=None, description="Filter for structural subordination")
fields: str = Field(default="ticker,name,sector,net_leverage_ratio,total_debt")
class SearchBondsInput(BaseModel):
ticker: str = Field(default=None, description="Company ticker")
min_ytm: float = Field(default=None, description="Minimum YTM (%)")
seniority: str = Field(default=None, description="senior_secured, senior_unsecured, subordinated")
maturity_before: str = Field(default=None, description="Maturity before date (YYYY-MM-DD)")
fields: str = Field(default="name,cusip,company_ticker,coupon_rate,maturity_date,seniority,pricing")
class ResolveBondInput(BaseModel):
query: str = Field(description="Bond description (e.g., 'RIG 8% 2027') or CUSIP")
class TraverseEntitiesInput(BaseModel):
start_type: str = Field(description="Start node type: 'bond', 'company', or 'entity'")
start_id: str = Field(description="Start node ID: CUSIP, ticker, or entity UUID")
relationships: str = Field(default="guarantees", description="Relationship type: guarantees, subsidiaries, parent")
direction: str = Field(default="inbound", description="Traversal direction: inbound, outbound, both")
class SearchPricingInput(BaseModel):
ticker: str = Field(default=None, description="Company ticker")
cusip: str = Field(default=None, description="Bond CUSIP")
min_ytm: float = Field(default=None, description="Minimum YTM (%)")
class SearchDocumentsInput(BaseModel):
query: str = Field(description="Search terms (e.g., 'leverage covenant')")
ticker: str = Field(default=None, description="Company ticker to filter by")
section_type: str = Field(default=None, description="debt_footnote, credit_agreement, indenture, covenants, mda_liquidity")
class GetChangesInput(BaseModel):
ticker: str = Field(description="Company ticker")
since: str = Field(description="Date to compare from (YYYY-MM-DD)")
# === Functions ===
def search_companies(**kwargs):
params = {k: v for k, v in kwargs.items() if v is not None}
return requests.get(f"{BASE_URL}/companies", params=params, headers=HEADERS).json()["data"]
def search_bonds(**kwargs):
params = {k: v for k, v in kwargs.items() if v is not None}
if params.get("min_ytm"):
params["has_pricing"] = True
return requests.get(f"{BASE_URL}/bonds", params=params, headers=HEADERS).json()["data"]
def resolve_bond(query: str):
matches = requests.get(f"{BASE_URL}/bonds/resolve", params={"q": query}, headers=HEADERS).json()["data"]["matches"]
return matches[0]["bond"] if matches else {"error": "No match found"}
def traverse_entities(start_type: str, start_id: str, relationships: str = "guarantees", direction: str = "inbound"):
return requests.post(f"{BASE_URL}/entities/traverse", headers={**HEADERS, "Content-Type": "application/json"},
json={"start": {"type": start_type, "id": start_id}, "relationships": [relationships], "direction": direction}
).json()["data"]
def search_pricing(**kwargs):
params = {k: v for k, v in kwargs.items() if v is not None}
params["has_pricing"] = True
return requests.get(f"{BASE_URL}/bonds", params=params, headers=HEADERS).json()["data"]
def search_documents(query: str, ticker: str = None, section_type: str = None):
params = {"q": query}
if ticker: params["ticker"] = ticker
if section_type: params["section_type"] = section_type
return requests.get(f"{BASE_URL}/documents/search", params=params, headers=HEADERS).json()["data"]
def get_changes(ticker: str, since: str):
return requests.get(f"{BASE_URL}/companies/{ticker}/changes", params={"since": since}, headers=HEADERS).json()["data"]
# === Create Tools ===
debtstack_tools = [
StructuredTool.from_function(func=search_companies, name="debtstack_search_companies",
description="Search companies by leverage, sector, debt metrics. Use for company screening and comparisons.",
args_schema=SearchCompaniesInput),
StructuredTool.from_function(func=search_bonds, name="debtstack_search_bonds",
description="Search bonds by yield, seniority, maturity. Use for bond screening and yield hunting.",
args_schema=SearchBondsInput),
StructuredTool.from_function(func=resolve_bond, name="debtstack_resolve_bond",
description="Convert bond description to CUSIP and details. Use when user mentions a bond by name.",
args_schema=ResolveBondInput),
StructuredTool.from_function(func=traverse_entities, name="debtstack_traverse_entities",
description="Follow guarantor chains and map corporate structure. Use for structural subordination analysis.",
args_schema=TraverseEntitiesInput),
StructuredTool.from_function(func=search_pricing, name="debtstack_search_pricing",
description="Get FINRA TRACE bond prices, YTM, and spreads. Use for relative value and distressed analysis.",
args_schema=SearchPricingInput),
StructuredTool.from_function(func=search_documents, name="debtstack_search_documents",
description="Search SEC filings for covenant language, credit agreement terms, debt descriptions.",
args_schema=SearchDocumentsInput),
StructuredTool.from_function(func=get_changes, name="debtstack_get_changes",
description="Track changes in debt structure since a date. Use for monitoring issuances and maturities.",
args_schema=GetChangesInput),
]