second commit
Browse files- .gitignore +4 -1
- README.md +55 -0
- ai_functions.py +0 -60
- chainlit.md +14 -0
- chat-app.py +0 -55
- chatxbt-assistant.py +56 -0
- sample.env +28 -0
- src/data_sources/coin_gecko.py +3 -3
- src/data_sources/cryptocompare.py +3 -2
- src/databases/redis.py +1 -1
- src/libs/logger.py +8 -8
- src/libs/redis.py +0 -14
- src/requirements.txt +0 -8
- src/tools/coin_data_toolkit.py +60 -0
- src/tools/crypto_coin_price_tool.py +0 -60
- src/xbt-core.py +0 -43
.gitignore
CHANGED
|
@@ -109,4 +109,7 @@ dmypy.json
|
|
| 109 |
.idea/
|
| 110 |
|
| 111 |
# Mac OS
|
| 112 |
-
.DS_Store
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
.idea/
|
| 110 |
|
| 111 |
# Mac OS
|
| 112 |
+
.DS_Store
|
| 113 |
+
|
| 114 |
+
TODO.md
|
| 115 |
+
run.py
|
README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ChatXBT Web3 AI Assitant Service
|
| 2 |
+
|
| 3 |
+
AI-powered unification and execution protocol for web3 applications
|
| 4 |
+
|
| 5 |
+
## Requirements
|
| 6 |
+
|
| 7 |
+
The list below contains all the requirements needed to have a complete developer environment to sart working on this project.
|
| 8 |
+
Follow the links to install or update these tools on your machine.
|
| 9 |
+
|
| 10 |
+
1. [Python 3.12+](https://www.python.org/downloads/release/python-3120/)
|
| 11 |
+
2. [Pyenv](https://github.com/pyenv/pyenv)
|
| 12 |
+
3. [Pip Installer](https://pip.pypa.io/en/stable/installation/)
|
| 13 |
+
4. [Python Venv](https://docs.python.org/3/library/venv.html)
|
| 14 |
+
|
| 15 |
+
## Installation
|
| 16 |
+
|
| 17 |
+
1. Clone the repository: `git clone https://github.com/ChatXBT/chatxbt-web3-ai-assistant.git`
|
| 18 |
+
2. Get a copy of the `\`.env`\` file from the project manager`
|
| 19 |
+
3. Create a virtual environment: `python -m venv env`
|
| 20 |
+
4. Activate the virtual environment: `source env/bin/activate`
|
| 21 |
+
4. Install the dependencies: `pip install -r requirements.txt`
|
| 22 |
+
5. Run the application: `chainlit run chatxbt-assistant.py -w`
|
| 23 |
+
6. Open the application in your browser: `http://localhost:8000`
|
| 24 |
+
|
| 25 |
+
## Development
|
| 26 |
+
|
| 27 |
+
The project is structured as follows:
|
| 28 |
+
|
| 29 |
+
1. src/data_sources: `a list of data sources that can be used to retrieve data for the AI assistant.`
|
| 30 |
+
2. src/databases: `a list of databases that can be used to store data for the AI assistant.`
|
| 31 |
+
3. src/libs: `a list of libraries that can be used to extend the classes and functions servicing the AI assistant.`
|
| 32 |
+
4. src/llms: `a list of language models powered libs that can be used to power the AI assistant.`
|
| 33 |
+
5. src/search_services: `a list of search services that can be used to retrieve data for the AI assistant.`
|
| 34 |
+
6. src/tools: `a list of tools that can be used to extend the functionality of the AI assistant.`
|
| 35 |
+
|
| 36 |
+
## Guides
|
| 37 |
+
|
| 38 |
+
1. Follow the development, structure and documentation patterns of the `src/tools/coin_data_toolkit.py` to build custom toolkits
|
| 39 |
+
2. Follow the development, structure and documentation patterns of the `src/tools/coin_data_toolkit.py` to build classes
|
| 40 |
+
3. Pay attention to the `constants` variables and `caching` decorators to optimize perfomance of class functions/methods across the project
|
| 41 |
+
4. Create new classes in the appropriate folders to maintain an organized codebase and project
|
| 42 |
+
5. After installing any new package run `` to update the dependency requirements list
|
| 43 |
+
6. Expose RPC functions for SDKs that do not support python runtime. use [DeepKit](https://deepkit.io/documentation/rpc) and [BunJS](https://bun.sh/) if possible.
|
| 44 |
+
|
| 45 |
+
## Deplopyment to producton
|
| 46 |
+
|
| 47 |
+
To be finalised and added
|
| 48 |
+
|
| 49 |
+
## TODO
|
| 50 |
+
|
| 51 |
+
TODO: Add deployment instructions
|
| 52 |
+
TODO: Setup CI/CD pipelines for automatic deployment
|
| 53 |
+
TODO: Setup and integrate `LitLLM` instance for LLM service high availability
|
| 54 |
+
TODO: Add RPC class for remmote method calls to other chatxbt services written in other launguages such as `JS`, `TS` & `GO`
|
| 55 |
+
TODO: Write enough unit test to ensure class and function input and output correctness
|
ai_functions.py
DELETED
|
@@ -1,60 +0,0 @@
|
|
| 1 |
-
from pydantic import BaseModel, Field
|
| 2 |
-
from typing import Dict, Optional, Type
|
| 3 |
-
|
| 4 |
-
from data_sources.coin_gecko import CoinGecko
|
| 5 |
-
from src.data_sources.cryptocompare import CryptoCompare
|
| 6 |
-
|
| 7 |
-
from langchain.tools.base import BaseTool
|
| 8 |
-
|
| 9 |
-
class CryptoCoinPrice(BaseModel):
|
| 10 |
-
"""Represents the prices of a coin in various currencies."""
|
| 11 |
-
prices: Dict[str, float] = Field(..., description="Prices in various currencies")
|
| 12 |
-
|
| 13 |
-
class CryptoCoinPriceData(BaseModel):
|
| 14 |
-
"""Encapsulates both CoinGecko and CryptoCompare price data."""
|
| 15 |
-
coingecko_price: Dict[str, CryptoCoinPrice] = Field(..., description="CoinGecko prices for various coins")
|
| 16 |
-
crypto_compare_price: Dict[str, CryptoCoinPrice] = Field(..., description="CryptoCompare prices for various coins")
|
| 17 |
-
|
| 18 |
-
class PriceInput(BaseModel):
|
| 19 |
-
coin_id: str = Field(..., description="The ID of the cryptocurrency coin to retrieve prices for")
|
| 20 |
-
vs_currency: str = Field("usd", description="The currency to compare against")
|
| 21 |
-
|
| 22 |
-
class CryptoCoinPriceOutput(BaseModel):
|
| 23 |
-
price_data: CryptoCoinPriceData
|
| 24 |
-
|
| 25 |
-
class CryptoCoinPriceTool(BaseTool):
|
| 26 |
-
name = "CryptoCoinPriceTool"
|
| 27 |
-
description = "Fetches price data for a given cryptocurrency coin from CoinGecko and CryptoCompare"
|
| 28 |
-
args_schema: Type[BaseModel] = PriceInput
|
| 29 |
-
return_direct: bool = True
|
| 30 |
-
|
| 31 |
-
def __init__(self, id: Optional[str] = None):
|
| 32 |
-
self.id = id
|
| 33 |
-
self.coingecko = CoinGecko()
|
| 34 |
-
self.crypto_compare = CryptoCompare()
|
| 35 |
-
|
| 36 |
-
def _run(self, coin_id: str, vs_currency: str = "usd") -> CryptoCoinPriceData:
|
| 37 |
-
coingecko_price_data = self.coingecko.get_coin_price(ids=[coin_id], vs_currencies=[vs_currency])
|
| 38 |
-
crypto_compare_price_data = self.crypto_compare.get_coin_price(ids=[coin_id], vs_currencies=[vs_currency])
|
| 39 |
-
|
| 40 |
-
coingecko_price = {}
|
| 41 |
-
crypto_compare_price = {}
|
| 42 |
-
|
| 43 |
-
if coin_id in coingecko_price_data:
|
| 44 |
-
coingecko_price[coin_id] = CryptoCoinPrice(prices=coingecko_price_data[coin_id])
|
| 45 |
-
else:
|
| 46 |
-
print(f"Warning: CoinGecko data for {coin_id} not found.")
|
| 47 |
-
|
| 48 |
-
if coin_id.upper() in crypto_compare_price_data:
|
| 49 |
-
crypto_compare_price[coin_id] = CryptoCoinPrice(prices=crypto_compare_price_data[coin_id.upper()])
|
| 50 |
-
else:
|
| 51 |
-
print(f"Warning: CryptoCompare data for {coin_id} not found.")
|
| 52 |
-
|
| 53 |
-
return CryptoCoinPriceData(
|
| 54 |
-
coingecko_price=coingecko_price,
|
| 55 |
-
crypto_compare_price=crypto_compare_price
|
| 56 |
-
)
|
| 57 |
-
|
| 58 |
-
def __call__(self, inputs: PriceInput) -> CryptoCoinPriceOutput:
|
| 59 |
-
price_data = self._run(inputs.coin_id, inputs.vs_currency)
|
| 60 |
-
return CryptoCoinPriceOutput(price_data=price_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chainlit.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Welcome to Chainlit! 🚀🤖
|
| 2 |
+
|
| 3 |
+
Hi there, Developer! 👋 We're excited to have you on board. Chainlit is a powerful tool designed to help you prototype, debug and share applications built on top of LLMs.
|
| 4 |
+
|
| 5 |
+
## Useful Links 🔗
|
| 6 |
+
|
| 7 |
+
- **Documentation:** Get started with our comprehensive [Chainlit Documentation](https://docs.chainlit.io) 📚
|
| 8 |
+
- **Discord Community:** Join our friendly [Chainlit Discord](https://discord.gg/k73SQ3FyUh) to ask questions, share your projects, and connect with other developers! 💬
|
| 9 |
+
|
| 10 |
+
We can't wait to see what you create with Chainlit! Happy coding! 💻😊
|
| 11 |
+
|
| 12 |
+
## Welcome screen
|
| 13 |
+
|
| 14 |
+
To modify the welcome screen, edit the `chainlit.md` file at the root of your project. If you do not want a welcome screen, just leave this file empty.
|
chat-app.py
DELETED
|
@@ -1,55 +0,0 @@
|
|
| 1 |
-
from dotenv import load_dotenv
|
| 2 |
-
|
| 3 |
-
from langchain.chains import LLMMathChain
|
| 4 |
-
from langchain.llms.openai import OpenAI
|
| 5 |
-
from langchain.chat_models import ChatOpenAI
|
| 6 |
-
from langchain.utilities.serpapi import SerpAPIWrapper
|
| 7 |
-
from langchain.agents import initialize_agent, Tool, AgentExecutor
|
| 8 |
-
import chainlit as cl
|
| 9 |
-
|
| 10 |
-
from src.tools.crypto_coin_price_tool import CryptoCoinPriceTool
|
| 11 |
-
|
| 12 |
-
load_dotenv()
|
| 13 |
-
|
| 14 |
-
@cl.on_chat_start
|
| 15 |
-
def start():
|
| 16 |
-
llm = ChatOpenAI(temperature=0, streaming=True)
|
| 17 |
-
llm1 = OpenAI(temperature=0, streaming=True)
|
| 18 |
-
search = SerpAPIWrapper()
|
| 19 |
-
get_crypto_coin_price = CryptoCoinPriceTool()
|
| 20 |
-
llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=True)
|
| 21 |
-
|
| 22 |
-
tools = [
|
| 23 |
-
Tool(
|
| 24 |
-
name="Search",
|
| 25 |
-
func=search.run,
|
| 26 |
-
description="useful for when you need to answer questions about current events. You should ask targeted questions",
|
| 27 |
-
handle_tool_error=True,
|
| 28 |
-
),
|
| 29 |
-
Tool(
|
| 30 |
-
name="Calculator",
|
| 31 |
-
func=llm_math_chain.run,
|
| 32 |
-
description="useful for when you need to answer questions about math",
|
| 33 |
-
handle_tool_error=True,
|
| 34 |
-
),
|
| 35 |
-
Tool(
|
| 36 |
-
name=get_crypto_coin_price.name,
|
| 37 |
-
func=get_crypto_coin_price.run,
|
| 38 |
-
description=get_crypto_coin_price.description,
|
| 39 |
-
handle_tool_error=True,
|
| 40 |
-
),
|
| 41 |
-
]
|
| 42 |
-
agent = initialize_agent(
|
| 43 |
-
tools, llm1, agent="chat-zero-shot-react-description", verbose=True, handle_parsing_errors=True
|
| 44 |
-
)
|
| 45 |
-
cl.user_session.set("agent", agent)
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
@cl.on_message
|
| 49 |
-
async def main(message: cl.Message):
|
| 50 |
-
agent = cl.user_session.get("agent") # type: AgentExecutor
|
| 51 |
-
cb = cl.LangchainCallbackHandler(stream_final_answer=True)
|
| 52 |
-
|
| 53 |
-
await cl.make_async(agent.run)(message.content, callbacks=[cb])
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chatxbt-assistant.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
load_dotenv()
|
| 4 |
+
|
| 5 |
+
import chainlit as cl
|
| 6 |
+
from phi.assistant import Assistant
|
| 7 |
+
from phi.tools.duckduckgo import DuckDuckGo
|
| 8 |
+
from phi.llm.openai import OpenAIChat
|
| 9 |
+
from phi.tools.yfinance import YFinanceTools
|
| 10 |
+
from src.tools.coin_data_toolkit import CryptoDataTools
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# assistant = Assistant(tools=[DuckDuckGo()], show_tool_calls=True)
|
| 14 |
+
# assistant.print_response("Whats happening in France?", markdown=True)
|
| 15 |
+
|
| 16 |
+
# f_assistant = Assistant(
|
| 17 |
+
# llm=OpenAIChat(model="gpt-4o"),
|
| 18 |
+
# tools=[YFinanceTools(stock_price=True, analyst_recommendations=True, company_info=True, company_news=True)],
|
| 19 |
+
# show_tool_calls=True,
|
| 20 |
+
# markdown=True,
|
| 21 |
+
# )
|
| 22 |
+
# f_assistant.print_response("What is the stock price of NVDA")
|
| 23 |
+
# f_assistant.print_response("Write a comparison between NVDA and AMD, use all tools available.")
|
| 24 |
+
|
| 25 |
+
@cl.on_chat_start
|
| 26 |
+
def start():
|
| 27 |
+
is_dev_mode = True if os.getenv("DEV_MODE") else False
|
| 28 |
+
|
| 29 |
+
# Initialize the assistant
|
| 30 |
+
cxbt_assistant = Assistant(
|
| 31 |
+
llm=OpenAIChat(model="gpt-4o"),
|
| 32 |
+
tools=[CryptoDataTools(), DuckDuckGo(), YFinanceTools(stock_price=True)],
|
| 33 |
+
show_tool_calls= is_dev_mode,
|
| 34 |
+
markdown=True,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# Set the assistant in the user session
|
| 38 |
+
cl.user_session.set("agent", cxbt_assistant)
|
| 39 |
+
|
| 40 |
+
@cl.on_message
|
| 41 |
+
async def main(message: cl.Message):
|
| 42 |
+
# Retrieve the assistant from the user session
|
| 43 |
+
agent = cl.user_session.get("agent")
|
| 44 |
+
|
| 45 |
+
# Process the user message using the assistant
|
| 46 |
+
# response = agent.run(message.content, stream=True)
|
| 47 |
+
response = ""
|
| 48 |
+
for delta in agent.run(message.content, stream=True):
|
| 49 |
+
response += delta
|
| 50 |
+
|
| 51 |
+
# Send the response back to the user
|
| 52 |
+
await cl.Message(content=response).send()
|
| 53 |
+
|
| 54 |
+
# Run the Chainlit application
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
cl.run()
|
sample.env
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
OPENAI_API_KEY=""
|
| 2 |
+
|
| 3 |
+
GROQ_API_KEY=""
|
| 4 |
+
|
| 5 |
+
LANGCHAIN_TRACING_V2=""
|
| 6 |
+
LANGCHAIN_ENDPOINT=""
|
| 7 |
+
LANGCHAIN_API_KEY=""
|
| 8 |
+
|
| 9 |
+
JINA_READER_API_KEY=""
|
| 10 |
+
|
| 11 |
+
EXA_API_KEY=""
|
| 12 |
+
|
| 13 |
+
SERPAPI_API_KEY=""
|
| 14 |
+
|
| 15 |
+
CRYPTOCOMPARE_API_KEY=""
|
| 16 |
+
|
| 17 |
+
COINGECKO_PRO_API_KEY=""
|
| 18 |
+
COINGECKO_DEMO_API_KEY=""
|
| 19 |
+
|
| 20 |
+
UPSTASH_REDIS_REST_URL=""
|
| 21 |
+
UPSTASH_REDIS_REST_TOKEN=""
|
| 22 |
+
UPSTASH_REDIS_HOST=""
|
| 23 |
+
UPSTASH_REDIS_PORT=""
|
| 24 |
+
UPSTASH_REDIS_PASSWORD=""
|
| 25 |
+
UPSTASH_REDIS_CONNECTION_STRING=""
|
| 26 |
+
|
| 27 |
+
LOGFIRE_TOKEN=""
|
| 28 |
+
LOG_LEVEL=""
|
src/data_sources/coin_gecko.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
from dotenv import load_dotenv
|
| 3 |
from src.databases.redis import REDIS_CACHED
|
| 4 |
-
from src.libs.constants import ONE_HOUR_IN_SECONDS, ONE_MONTH_IN_SECONDS
|
| 5 |
from src.libs.logger import logger
|
| 6 |
from coingecko import CoinGeckoProClient, CoinGeckoDemoClient
|
| 7 |
|
|
@@ -16,9 +16,9 @@ class CoinGecko:
|
|
| 16 |
else:
|
| 17 |
self.cgc = CoinGeckoDemoClient(api_key=os.getenv('COINGECKO_DEMO_API_KEY'))
|
| 18 |
|
| 19 |
-
@redis_cache(ttl=
|
| 20 |
@logger.instrument()
|
| 21 |
-
def get_coin_price(self, ids: list, vs_currencies: list, cache_ttl: int = None) -> dict:
|
| 22 |
# logger.debug(f"ids: {ids}")
|
| 23 |
# logger.debug(f"vs_currencies: {vs_currencies}")
|
| 24 |
|
|
|
|
| 1 |
import os
|
| 2 |
from dotenv import load_dotenv
|
| 3 |
from src.databases.redis import REDIS_CACHED
|
| 4 |
+
from src.libs.constants import ONE_HOUR_IN_SECONDS, ONE_MINUTE_IN_SECONDS, ONE_MONTH_IN_SECONDS
|
| 5 |
from src.libs.logger import logger
|
| 6 |
from coingecko import CoinGeckoProClient, CoinGeckoDemoClient
|
| 7 |
|
|
|
|
| 16 |
else:
|
| 17 |
self.cgc = CoinGeckoDemoClient(api_key=os.getenv('COINGECKO_DEMO_API_KEY'))
|
| 18 |
|
| 19 |
+
@redis_cache(ttl=ONE_MINUTE_IN_SECONDS)
|
| 20 |
@logger.instrument()
|
| 21 |
+
def get_coin_price(self, ids: list[str], vs_currencies: list[str], cache_ttl: int = None) -> dict:
|
| 22 |
# logger.debug(f"ids: {ids}")
|
| 23 |
# logger.debug(f"vs_currencies: {vs_currencies}")
|
| 24 |
|
src/data_sources/cryptocompare.py
CHANGED
|
@@ -38,7 +38,7 @@ class CryptoCompare:
|
|
| 38 |
|
| 39 |
@redis_cache(ttl=ONE_MINUTE_IN_SECONDS)
|
| 40 |
@logger.instrument()
|
| 41 |
-
def get_coin_price(self, ids: list, vs_currencies: list, cache_ttl: int = None) -> dict:
|
| 42 |
|
| 43 |
url = f"{self.CRYPTO_COMPARE_BASE_URL}data/pricemulti"
|
| 44 |
# logger.debug(url)
|
|
@@ -59,5 +59,6 @@ class CryptoCompare:
|
|
| 59 |
response.raise_for_status() # Raise an exception if the request was unsuccessful
|
| 60 |
return response.json()
|
| 61 |
except httpx.HTTPError as e:
|
| 62 |
-
logger.debug(f"An error occurred while making the request: {e}")
|
|
|
|
| 63 |
return None
|
|
|
|
| 38 |
|
| 39 |
@redis_cache(ttl=ONE_MINUTE_IN_SECONDS)
|
| 40 |
@logger.instrument()
|
| 41 |
+
def get_coin_price(self, ids: list[str], vs_currencies: list[str], cache_ttl: int = None) -> dict:
|
| 42 |
|
| 43 |
url = f"{self.CRYPTO_COMPARE_BASE_URL}data/pricemulti"
|
| 44 |
# logger.debug(url)
|
|
|
|
| 59 |
response.raise_for_status() # Raise an exception if the request was unsuccessful
|
| 60 |
return response.json()
|
| 61 |
except httpx.HTTPError as e:
|
| 62 |
+
# logger.debug(f"An error occurred while making the request: {e}")
|
| 63 |
+
print(f"An error occurred while making the request: {e}")
|
| 64 |
return None
|
src/databases/redis.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
import json
|
| 2 |
import os
|
|
|
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
from upstash_redis import Redis
|
| 5 |
from src.libs.logger import logger
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
+
import json
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
from upstash_redis import Redis
|
| 5 |
from src.libs.logger import logger
|
src/libs/logger.py
CHANGED
|
@@ -1,17 +1,17 @@
|
|
| 1 |
import os
|
| 2 |
from dotenv import load_dotenv
|
| 3 |
|
| 4 |
-
import logfire
|
| 5 |
|
| 6 |
load_dotenv()
|
| 7 |
|
| 8 |
-
|
| 9 |
token=os.getenv('LOGFIRE_TOKEN'),
|
| 10 |
-
pydantic_plugin=
|
| 11 |
-
console=
|
| 12 |
)
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
|
| 17 |
-
logger =
|
|
|
|
| 1 |
import os
|
| 2 |
from dotenv import load_dotenv
|
| 3 |
|
| 4 |
+
import logfire as lf
|
| 5 |
|
| 6 |
load_dotenv()
|
| 7 |
|
| 8 |
+
lf.configure(
|
| 9 |
token=os.getenv('LOGFIRE_TOKEN'),
|
| 10 |
+
pydantic_plugin=lf.PydanticPlugin(record='all'),
|
| 11 |
+
console=lf.ConsoleOptions(min_log_level= os.getenv('LOG_LEVEL'))
|
| 12 |
)
|
| 13 |
+
lf.instrument_redis()
|
| 14 |
+
lf.instrument_httpx()
|
| 15 |
+
lf.instrument_requests()
|
| 16 |
|
| 17 |
+
logger = lf
|
src/libs/redis.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
keys = {
|
| 2 |
-
"cryptocompare": {
|
| 3 |
-
"top_exchanges": "top_exchanges",
|
| 4 |
-
"all_exchanges": "all_exchanges",
|
| 5 |
-
"top_assets": "top_assets",
|
| 6 |
-
"all_assets": "all_assets",
|
| 7 |
-
},
|
| 8 |
-
"coingecko": {
|
| 9 |
-
"top_exchanges": "top_exchanges",
|
| 10 |
-
"all_exchanges": "all_exchanges",
|
| 11 |
-
"top_assets": "top_assets",
|
| 12 |
-
"all_assets": "all_assets",
|
| 13 |
-
}
|
| 14 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/requirements.txt
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
auto_mix_prep==0.2.0
|
| 2 |
-
httpx==0.27.0
|
| 3 |
-
logfire==0.42.0
|
| 4 |
-
pyjsonq==1.0.2
|
| 5 |
-
python-dotenv==1.0.1
|
| 6 |
-
scrapegraphai==1.6.0
|
| 7 |
-
ulid==1.1
|
| 8 |
-
upstash_redis==1.1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/tools/coin_data_toolkit.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
from phi.tools import Toolkit
|
| 4 |
+
from phi.utils.log import logger
|
| 5 |
+
|
| 6 |
+
from src.data_sources.coin_gecko import CoinGecko
|
| 7 |
+
from src.data_sources.cryptocompare import CryptoCompare
|
| 8 |
+
|
| 9 |
+
class CryptoDataTools(Toolkit):
|
| 10 |
+
def __init__(self):
|
| 11 |
+
super().__init__(name="crypto_data_tools")
|
| 12 |
+
self.register(self.get_coin_price)
|
| 13 |
+
self.coingecko = CoinGecko()
|
| 14 |
+
self.crypto_compare = CryptoCompare()
|
| 15 |
+
|
| 16 |
+
def get_coin_price(self, coin_id: str, vs_currency: str = "usd") -> str:
|
| 17 |
+
"""
|
| 18 |
+
Fetches the price data for a given cryptocurrency coin from CoinGecko and CryptoCompare.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
coin_id (str): The unique identifier for the cryptocurrency coin.
|
| 22 |
+
vs_currency (str, optional): The currency to which the coin price will be compared. Defaults to "usd".
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
str: A JSON string containing the price data from CoinGecko and CryptoCompare for the specified coin.
|
| 26 |
+
|
| 27 |
+
Raises:
|
| 28 |
+
Exception: If an error occurs while fetching the price data.
|
| 29 |
+
|
| 30 |
+
Example:
|
| 31 |
+
>>> get_coin_price("bitcoin", "eur")
|
| 32 |
+
"""
|
| 33 |
+
logger.info(f"Fetching price data for {coin_id} cryptocurrency coin from CoinGecko and CryptoCompare")
|
| 34 |
+
try:
|
| 35 |
+
coingecko_price = {}
|
| 36 |
+
crypto_compare_price = {}
|
| 37 |
+
|
| 38 |
+
coingecko_price_data = self.coingecko.get_coin_price(ids=[coin_id], vs_currencies=[vs_currency])
|
| 39 |
+
crypto_compare_price_data = self.crypto_compare.get_coin_price(ids=[coin_id], vs_currencies=[vs_currency])
|
| 40 |
+
|
| 41 |
+
logger.debug(f"coingecko_price_data: {coingecko_price_data}")
|
| 42 |
+
logger.debug(f"crypto_compare_price_data: {crypto_compare_price_data}")
|
| 43 |
+
|
| 44 |
+
if coin_id in coingecko_price_data:
|
| 45 |
+
coingecko_price[coin_id] = coingecko_price_data[coin_id]
|
| 46 |
+
else:
|
| 47 |
+
logger.warning(f"Warning: CoinGecko data for {coin_id} not found.")
|
| 48 |
+
|
| 49 |
+
if coin_id.upper() in crypto_compare_price_data:
|
| 50 |
+
crypto_compare_price[coin_id] = crypto_compare_price_data[coin_id.upper()]
|
| 51 |
+
else:
|
| 52 |
+
logger.warning(f"Warning: CryptoCompare data for {coin_id} not found.")
|
| 53 |
+
|
| 54 |
+
logger.debug(f"respons {coingecko_price}")
|
| 55 |
+
logger.debug(f"respons {crypto_compare_price}")
|
| 56 |
+
|
| 57 |
+
return f"{(coingecko_price, crypto_compare_price)}"
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.warning(f"Failed to fetch price data for {coin_id}: {e}")
|
| 60 |
+
return f"Error: {e}"
|
src/tools/crypto_coin_price_tool.py
DELETED
|
@@ -1,60 +0,0 @@
|
|
| 1 |
-
from pydantic import BaseModel, Field
|
| 2 |
-
from typing import Dict, Optional, Type
|
| 3 |
-
|
| 4 |
-
from src.data_sources.coin_gecko import CoinGecko
|
| 5 |
-
from src.data_sources.cryptocompare import CryptoCompare
|
| 6 |
-
|
| 7 |
-
from langchain.tools.base import BaseTool
|
| 8 |
-
|
| 9 |
-
class CryptoCoinPrice(BaseModel):
|
| 10 |
-
"""Represents the prices of a coin in various currencies."""
|
| 11 |
-
prices: Dict[str, float] = Field(..., description="Prices in various currencies")
|
| 12 |
-
|
| 13 |
-
class CryptoCoinPriceData(BaseModel):
|
| 14 |
-
"""Encapsulates both CoinGecko and CryptoCompare price data."""
|
| 15 |
-
coingecko_price: Dict[str, CryptoCoinPrice] = Field(..., description="CoinGecko prices for various coins")
|
| 16 |
-
crypto_compare_price: Dict[str, CryptoCoinPrice] = Field(..., description="CryptoCompare prices for various coins")
|
| 17 |
-
|
| 18 |
-
class PriceInput(BaseModel):
|
| 19 |
-
coin_id: str = Field(..., description="The ID of the cryptocurrency coin to retrieve prices for")
|
| 20 |
-
vs_currency: str = Field("usd", description="The currency to compare against")
|
| 21 |
-
|
| 22 |
-
class CryptoCoinPriceOutput(BaseModel):
|
| 23 |
-
price_data: CryptoCoinPriceData
|
| 24 |
-
|
| 25 |
-
class CryptoCoinPriceTool(BaseTool):
|
| 26 |
-
name = "CryptoCoinPriceTool"
|
| 27 |
-
description = "Fetches price data for a given cryptocurrency coin from CoinGecko and CryptoCompare"
|
| 28 |
-
args_schema: Type[BaseModel] = PriceInput
|
| 29 |
-
return_direct: bool = True
|
| 30 |
-
|
| 31 |
-
def __init__(self, id: Optional[str] = None):
|
| 32 |
-
self.id = id
|
| 33 |
-
self.coingecko = CoinGecko()
|
| 34 |
-
self.crypto_compare = CryptoCompare()
|
| 35 |
-
|
| 36 |
-
def _run(self, coin_id: str, vs_currency: str = "usd") -> CryptoCoinPriceData:
|
| 37 |
-
coingecko_price_data = self.coingecko.get_coin_price(ids=[coin_id], vs_currencies=[vs_currency])
|
| 38 |
-
crypto_compare_price_data = self.crypto_compare.get_coin_price(ids=[coin_id], vs_currencies=[vs_currency])
|
| 39 |
-
|
| 40 |
-
coingecko_price = {}
|
| 41 |
-
crypto_compare_price = {}
|
| 42 |
-
|
| 43 |
-
if coin_id in coingecko_price_data:
|
| 44 |
-
coingecko_price[coin_id] = CryptoCoinPrice(prices=coingecko_price_data[coin_id])
|
| 45 |
-
else:
|
| 46 |
-
print(f"Warning: CoinGecko data for {coin_id} not found.")
|
| 47 |
-
|
| 48 |
-
if coin_id.upper() in crypto_compare_price_data:
|
| 49 |
-
crypto_compare_price[coin_id] = CryptoCoinPrice(prices=crypto_compare_price_data[coin_id.upper()])
|
| 50 |
-
else:
|
| 51 |
-
print(f"Warning: CryptoCompare data for {coin_id} not found.")
|
| 52 |
-
|
| 53 |
-
return CryptoCoinPriceData(
|
| 54 |
-
coingecko_price=coingecko_price,
|
| 55 |
-
crypto_compare_price=crypto_compare_price
|
| 56 |
-
)
|
| 57 |
-
|
| 58 |
-
def __call__(self, inputs: PriceInput) -> CryptoCoinPriceOutput:
|
| 59 |
-
price_data = self._run(inputs.coin_id, inputs.vs_currency)
|
| 60 |
-
return CryptoCoinPriceOutput(price_data=price_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/xbt-core.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import httpx
|
| 3 |
-
import urllib.parse
|
| 4 |
-
from typing import Self
|
| 5 |
-
from pyjsonq import JsonQ
|
| 6 |
-
from dotenv import load_dotenv
|
| 7 |
-
|
| 8 |
-
load_dotenv()
|
| 9 |
-
|
| 10 |
-
JINA_SEARCH_BASE_ENDPOINT = "r.jina.ai"
|
| 11 |
-
JINA_READER_BASE_ENDPOINT = "s.jina.ai"
|
| 12 |
-
|
| 13 |
-
class XBTCore:
|
| 14 |
-
def __init__(self) -> None:
|
| 15 |
-
self.value = 0
|
| 16 |
-
self.JINA_SEARCH_BASE_ENDPOINT = "s.jina.ai"
|
| 17 |
-
self.JINA_READER_BASE_ENDPOINT = "r.jina.ai"
|
| 18 |
-
|
| 19 |
-
def search_web_with_jina(self, search_query: str=False) -> Self:
|
| 20 |
-
url = self.JINA_SEARCH_BASE_ENDPOINT
|
| 21 |
-
encoded_search_query = urllib.parse.quote(search_query)
|
| 22 |
-
|
| 23 |
-
try:
|
| 24 |
-
with httpx.Client() as client:
|
| 25 |
-
response = client.get(f"{url}/{encoded_search_query}")
|
| 26 |
-
response.raise_for_status()
|
| 27 |
-
return response.json()
|
| 28 |
-
except httpx.HTTPError as e:
|
| 29 |
-
print(f"An error occurred: {e}")
|
| 30 |
-
return None
|
| 31 |
-
|
| 32 |
-
def read_website_with_jina(self, website_url: str=False) -> Self:
|
| 33 |
-
url = self.JINA_READER_BASE_ENDPOINT
|
| 34 |
-
|
| 35 |
-
try:
|
| 36 |
-
with httpx.Client() as client:
|
| 37 |
-
response = client.get(f"{url}/{website_url}")
|
| 38 |
-
response.raise_for_status()
|
| 39 |
-
return response.json()
|
| 40 |
-
except httpx.HTTPError as e:
|
| 41 |
-
print(f"An error occurred: {e}")
|
| 42 |
-
return None
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|