Skip to main content

Overview

DebtStack provides a native LangChain toolkit with 7 tools covering all credit data primitives. Build agents that can autonomously search companies, analyze bonds, traverse corporate structures, and research SEC filings.

Installation

pip install debtstack-ai[langchain] langchain-openai

Quick Start

The SDK includes a ready-to-use DebtStackToolkit — no manual tool definitions needed:
from debtstack.langchain import DebtStackToolkit
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_openai import ChatOpenAI
from langchain import hub

# Initialize toolkit with your API key
toolkit = DebtStackToolkit(api_key="ds_xxxxx")
tools = toolkit.get_tools()

# Create an agent with GPT-4 (or any LangChain-compatible LLM)
llm = ChatOpenAI(temperature=0, model="gpt-4")
prompt = hub.pull("hwchase17/openai-functions-agent")
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# Ask natural language questions about corporate credit
result = agent_executor.invoke({
    "input": "Which MAG7 company has the highest leverage?"
})
print(result["output"])

Available Tools

The toolkit provides 7 tools covering all DebtStack API primitives:
ToolDescription
debtstack_search_companiesScreen companies by leverage, sector, coverage ratios, and risk flags
debtstack_search_bondsFilter bonds by yield, spread, seniority, maturity, and pricing
debtstack_resolve_bondLook up bonds by CUSIP, ISIN, or description (e.g., “RIG 8% 2027”)
debtstack_traverse_entitiesFollow guarantor chains, map corporate structure, trace ownership
debtstack_search_pricingGet FINRA TRACE bond prices, YTM, and spreads
debtstack_search_documentsFull-text search across credit agreements, indentures, and SEC filings
debtstack_get_changesTrack debt structure changes over time (new issuances, maturities, leverage)

Example Queries

The agent can handle complex, multi-step credit analysis:
# Leverage comparison
result = agent_executor.invoke({
    "input": "Compare the leverage of RIG, VAL, and DO"
})

# High-yield screening
result = agent_executor.invoke({
    "input": "Find bonds yielding more than 9% with senior secured status"
})

# Structural analysis
result = agent_executor.invoke({
    "input": "Who guarantees Transocean's 8.75% 2030 notes? How many entities are in the chain?"
})

# Covenant research
result = agent_executor.invoke({
    "input": "Search for maintenance covenant language in Charter's credit agreements"
})

# Change tracking
result = agent_executor.invoke({
    "input": "What changed in RIG's debt structure since January 2025?"
})

# Corporate structure comparison
result = agent_executor.invoke({
    "input": "Compare Charter and Altice's corporate structures - which has more structural subordination risk?"
})

# Distressed bond hunting
result = agent_executor.invoke({
    "input": "Find distressed bonds trading below 80 cents on the dollar"
})

Custom Tool Definitions

If you prefer to define tools manually instead of using the SDK toolkit, here’s how to build them with StructuredTool:
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),
]

LangChain Expression Language (LCEL)

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from debtstack.langchain import DebtStackToolkit

toolkit = DebtStackToolkit(api_key="ds_xxxxx")
tools = toolkit.get_tools()

prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a credit analyst assistant with access to DebtStack tools.
    When analyzing companies, always consider:
    1. Leverage levels and trends
    2. Debt structure (secured vs unsecured)
    3. Maturity profile
    4. Structural subordination risk"""),
    ("human", "{question}")
])

llm = ChatOpenAI(model="gpt-4").bind_tools(tools)

chain = prompt | llm | StrOutputParser()

Next Steps