Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/docs/Components/bundles-adanos.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Adanos
slug: /bundles-adanos
---

import Icon from "@site/src/components/icon";
import { GraduatedBundleInstall } from '@site/docs/_partial-bundle-graduated-install.mdx';

<GraduatedBundleInstall packageName="adanos" />

<Icon name="Blocks" aria-hidden="true" /> [**Bundles**](/components-bundle-components) contain custom components that support specific third-party integrations with Langflow.

This page describes the component available in the **Adanos** bundle.

## Adanos Market Sentiment

The Adanos Market Sentiment component retrieves read-only market sentiment data for use in agents, prompts, and data-processing flows. It supports individual assets, trending assets, and aggregate market sentiment.

For stocks, choose Reddit, X / FinTwit, financial news, or Polymarket as the source. Crypto sentiment currently uses Reddit data.

Create an API key at [Adanos registration](https://adanos.org/register). For endpoint details and plan limits, see the [Adanos API documentation](https://api.adanos.org/docs).

The output is [`Data`](/data-types#data).

### Parameters

| Name | Type | Description |
|------|------|-------------|
| Adanos API Key (`api_key`) | SecretString | Input parameter. Your Adanos API key. |
| Operation (`operation`) | Dropdown | Input parameter. Retrieve asset sentiment, trending assets, or aggregate market sentiment. |
| Asset Type (`asset_type`) | Dropdown | Input parameter. Stocks or Crypto. |
| Stock Source (`source`) | Dropdown | Input parameter. Reddit, X / FinTwit, News, or Polymarket. Applies to stocks only. |
| Ticker or Symbol (`symbol`) | String | Input parameter. Required for Asset sentiment, for example `AAPL` or `BTC`. |
| From Date (`start_date`) | String | Input parameter. Optional inclusive UTC date in `YYYY-MM-DD` format. |
| To Date (`end_date`) | String | Input parameter. Optional inclusive UTC date in `YYYY-MM-DD` format. |
| Result Limit (`limit`) | Integer | Input parameter. Maximum number of trending results, from 1 to 100. Default: `20`. |
1 change: 1 addition & 0 deletions docs/docs/_partial-bundle-graduated-install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Admonition from '@theme/Admonition';

export const GraduatedBundleInstall = ({ packageName }) => {
const providers = {
'adanos': { name: 'Adanos', coreSupport: false },
'amazon': { name: 'Amazon Bedrock', coreSupport: true },
'anthropic': { name: 'Anthropic', coreSupport: true },
'arxiv': { name: 'arXiv Search', coreSupport: false },
Expand Down
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ module.exports = {
"Components/bundles-agentics",
"Components/bundles-aiml",
"Components/bundles-altk",
"Components/bundles-adanos",
"Components/bundles-amazon",
"Components/bundles-anthropic",
"Components/bundles-apify",
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies = [
# The cross-bundle CI matrix verifies these ranges resolve together across
# the supported lfx axis.
"lfx-duckduckgo>=0.1.0,<1.0.0",
"lfx-adanos>=0.1.0,<1.0.0",
"lfx-arxiv>=0.1.0,<1.0.0",
"lfx-ibm>=0.1.0,<1.0.0",
"lfx-docling>=0.1.0,<1.0.0",
Expand Down Expand Up @@ -107,6 +108,7 @@ lfx = { workspace = true }
langflow-sdk = { workspace = true }
# langflow-extensions:bundle-sources-start
lfx-duckduckgo = { workspace = true }
lfx-adanos = { workspace = true }
lfx-arxiv = { workspace = true }
lfx-ibm = { workspace = true }
lfx-docling = { workspace = true }
Expand Down Expand Up @@ -138,6 +140,7 @@ members = [
"src/sdk",
# langflow-extensions:bundle-members-start
"src/bundles/duckduckgo",
"src/bundles/adanos",
"src/bundles/arxiv",
"src/bundles/ibm",
"src/bundles/docling",
Expand Down
32 changes: 32 additions & 0 deletions src/bundles/adanos/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# lfx-adanos

[Adanos](https://adanos.org/) market sentiment data as a standalone Langflow Extension Bundle.

## What it ships

The **Adanos Market Sentiment** component retrieves read-only sentiment data for:

- individual stocks from Reddit, X / FinTwit, financial news, or Polymarket;
- individual crypto assets from Reddit;
- trending stocks or crypto assets; and
- aggregate stock or crypto market sentiment.

Results are returned as Langflow `Data` and can be passed to agents, prompts, or other data-processing components. The API key is sent only in the `X-API-Key` request header.

Create an API key at [adanos.org/register](https://adanos.org/register) and see the [Adanos API documentation](https://api.adanos.org/docs) for endpoint and plan details.

## Install

```bash
pip install lfx-adanos
```

`pip install langflow` already includes it.

## Develop

```bash
uv sync
uv run pytest src/bundles/adanos/tests -q
uv run lfx extension validate src/bundles/adanos/src/lfx_adanos
```
38 changes: 38 additions & 0 deletions src/bundles/adanos/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[project]
name = "lfx-adanos"
version = "0.1.0"
description = "Adanos market sentiment data as a standalone Langflow Extension Bundle."
readme = "README.md"
requires-python = ">=3.10,<3.15"
license = { text = "MIT" }
authors = [
{ name = "Langflow", email = "contact@langflow.org" },
]
keywords = ["langflow", "lfx", "extension", "bundle", "adanos", "market-sentiment", "finance"]
dependencies = [
"lfx>=1.11.0.dev0,<2.0.0",
"httpx>=0.28.1,<1.0.0",
]

[project.urls]
Homepage = "https://adanos.org/"
Documentation = "https://api.adanos.org/docs"
Repository = "https://github.com/langflow-ai/langflow"

[project.entry-points."langflow.extensions"]
lfx-adanos = "lfx_adanos"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/lfx_adanos"]
include = ["src/lfx_adanos/extension.json", "src/lfx_adanos/components/**/*.py"]

[tool.hatch.build.targets.sdist]
include = [
"src/lfx_adanos",
"README.md",
"pyproject.toml",
]
5 changes: 5 additions & 0 deletions src/bundles/adanos/src/lfx_adanos/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Adanos market sentiment extension bundle for Langflow."""

from lfx_adanos.components.adanos.adanos_market_sentiment import AdanosMarketSentimentComponent

__all__ = ["AdanosMarketSentimentComponent"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .adanos_market_sentiment import AdanosMarketSentimentComponent

__all__ = ["AdanosMarketSentimentComponent"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
from __future__ import annotations

import re
from datetime import date
from typing import Any
from urllib.parse import quote

import httpx
from lfx.custom.custom_component.component import Component
from lfx.io import DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
from lfx.schema.data import Data

ADANOS_API_BASE_URL = "https://api.adanos.org"
ADANOS_REQUEST_TIMEOUT = httpx.Timeout(95.0, connect=10.0)
MAX_RESULT_LIMIT = 100
MIN_NUMERIC_STOCK_SYMBOL_LENGTH = 3

_OPERATION_PATHS = {
"Asset sentiment": "asset",
"Trending assets": "trending",
"Market sentiment": "market-sentiment",
}
_STOCK_SOURCES = {
"Reddit": "reddit",
"X / FinTwit": "x",
"News": "news",
"Polymarket": "polymarket",
}
_STOCK_SYMBOL_PATTERN = re.compile(r"^(?:[A-Z0-9]{1,10}|[A-Z0-9]{1,8}[.-][A-Z])$")
_CRYPTO_SYMBOL_PATTERN = re.compile(r"^\$?[A-Z0-9]{1,20}$")
_DATE_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}$")


class AdanosMarketSentimentComponent(Component):
display_name = "Adanos Market Sentiment"
description = "Retrieve stock and crypto market sentiment from Adanos."
documentation = "https://api.adanos.org/docs"
name = "AdanosMarketSentiment"
icon = "TrendingUp"

inputs = [
SecretStrInput(
name="api_key",
display_name="Adanos API Key",
info="Create an API key at https://adanos.org/register",
password=True,
required=True,
),
DropdownInput(
name="operation",
display_name="Operation",
options=list(_OPERATION_PATHS),
value="Asset sentiment",
),
DropdownInput(
name="asset_type",
display_name="Asset Type",
options=["Stocks", "Crypto"],
value="Stocks",
),
DropdownInput(
name="source",
display_name="Stock Source",
options=list(_STOCK_SOURCES),
value="Reddit",
info="Used for stocks. Crypto sentiment is currently sourced from Reddit.",
),
MessageTextInput(
name="symbol",
display_name="Ticker or Symbol",
value="AAPL",
info="Required for Asset sentiment, for example AAPL or BTC.",
tool_mode=True,
),
MessageTextInput(
name="start_date",
display_name="From Date",
value="",
info="Optional inclusive UTC date in YYYY-MM-DD format.",
advanced=True,
),
MessageTextInput(
name="end_date",
display_name="To Date",
value="",
info="Optional inclusive UTC date in YYYY-MM-DD format.",
advanced=True,
),
IntInput(
name="limit",
display_name="Result Limit",
value=20,
info="Maximum number of results for Trending assets (1-100).",
advanced=True,
),
]

outputs = [Output(display_name="Data", name="data", method="fetch_sentiment")]

@staticmethod
def _secret_value(value: Any) -> str:
if hasattr(value, "get_secret_value"):
value = value.get_secret_value()
return str(value or "").strip()

@staticmethod
def _parse_date(value: str, field_name: str) -> str | None:
normalized = (value or "").strip()
if not normalized:
return None
if not _DATE_PATTERN.fullmatch(normalized):
msg = f"{field_name} must use YYYY-MM-DD format."
raise ValueError(msg)
try:
parsed_date = date.fromisoformat(normalized)
except ValueError as exc:
msg = f"{field_name} must use YYYY-MM-DD format."
raise ValueError(msg) from exc
return parsed_date.isoformat()

def _request_parts(self) -> tuple[str, dict[str, str | int]]:
if self.operation not in _OPERATION_PATHS:
msg = "Unsupported Adanos operation."
raise ValueError(msg)
if self.asset_type not in {"Stocks", "Crypto"}:
msg = "Asset Type must be Stocks or Crypto."
raise ValueError(msg)

start_date = self._parse_date(self.start_date, "From Date")
end_date = self._parse_date(self.end_date, "To Date")
if start_date and end_date and start_date > end_date:
msg = "From Date cannot be after To Date."
raise ValueError(msg)

params: dict[str, str | int] = {}
if start_date:
params["from"] = start_date
if end_date:
params["to"] = end_date

if self.asset_type == "Crypto":
prefix = "/reddit/crypto/v1"
asset_segment = "token"
else:
source = _STOCK_SOURCES.get(self.source)
if source is None:
msg = "Unsupported Adanos stock source."
raise ValueError(msg)
prefix = f"/{source}/stocks/v1"
asset_segment = "stock"

operation = _OPERATION_PATHS[self.operation]
if operation == "asset":
symbol = (self.symbol or "").strip().upper()
normalized_symbol = symbol.removeprefix("$")
is_valid_stock = bool(
_STOCK_SYMBOL_PATTERN.fullmatch(normalized_symbol)
and (
any(character.isalpha() for character in normalized_symbol)
or len(normalized_symbol) >= MIN_NUMERIC_STOCK_SYMBOL_LENGTH
)
)
is_valid_crypto = bool(_CRYPTO_SYMBOL_PATTERN.fullmatch(symbol))
is_valid_symbol = is_valid_crypto if self.asset_type == "Crypto" else is_valid_stock
if not symbol or not is_valid_symbol:
msg = "Enter a valid ticker or crypto symbol for Asset sentiment."
raise ValueError(msg)
path = f"{prefix}/{asset_segment}/{quote(normalized_symbol, safe='.-')}"
else:
path = f"{prefix}/{operation}"
if operation == "trending":
if not isinstance(self.limit, int) or not 1 <= self.limit <= MAX_RESULT_LIMIT:
msg = "Result Limit must be between 1 and 100."
raise ValueError(msg)
params["limit"] = self.limit

return path, params

def fetch_sentiment(self) -> Data:
api_key = self._secret_value(self.api_key)
if not api_key:
msg = "Adanos API key is required."
raise ValueError(msg)

path, params = self._request_parts()
try:
response = httpx.get(
f"{ADANOS_API_BASE_URL}{path}",
headers={"X-API-Key": api_key, "Accept": "application/json"},
params=params,
timeout=ADANOS_REQUEST_TIMEOUT,
)
response.raise_for_status()
payload = response.json()
except httpx.HTTPStatusError as exc:
msg = f"Adanos API request failed with status {exc.response.status_code}."
raise ValueError(msg) from exc
except (httpx.RequestError, ValueError) as exc:
msg = "Could not retrieve market sentiment from Adanos."
raise ValueError(msg) from exc

data = payload if isinstance(payload, dict) else {"results": payload}
result = Data(data=data)
self.status = result
return result
16 changes: 16 additions & 0 deletions src/bundles/adanos/src/lfx_adanos/extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://schemas.langflow.org/extension/v1.json",
"id": "lfx-adanos",
"version": "0.1.0",
"name": "Adanos",
"description": "Market sentiment data for stocks and crypto from Adanos.",
"lfx": {
"compat": ["1"]
},
"bundles": [
{
"name": "adanos",
"path": "components/adanos"
}
]
}
Loading
Loading