semantio 0.0.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- semantio/__init__.py +0 -0
- semantio/agent.py +608 -0
- semantio/api/__init__.py +0 -0
- semantio/api/api_generator.py +23 -0
- semantio/api/fastapi_app.py +70 -0
- semantio/cli/__init__.py +0 -0
- semantio/cli/main.py +31 -0
- semantio/knowledge_base/__init__.py +5 -0
- semantio/knowledge_base/document_loader.py +61 -0
- semantio/knowledge_base/retriever.py +41 -0
- semantio/knowledge_base/vector_store.py +35 -0
- semantio/llm/__init__.py +17 -0
- semantio/llm/anthropic.py +39 -0
- semantio/llm/base_llm.py +12 -0
- semantio/llm/groq.py +39 -0
- semantio/llm/llama.py +0 -0
- semantio/llm/openai.py +26 -0
- semantio/memory.py +11 -0
- semantio/rag.py +18 -0
- semantio/storage/__init__.py +0 -0
- semantio/storage/cloud_storage.py +0 -0
- semantio/storage/local_storage.py +0 -0
- semantio/tools/__init__.py +0 -0
- semantio/tools/base_tool.py +12 -0
- semantio/tools/crypto.py +133 -0
- semantio/tools/duckduckgo.py +128 -0
- semantio/tools/stocks.py +131 -0
- semantio/tools/web_browser.py +153 -0
- semantio/utils/__init__.py +7 -0
- semantio/utils/config.py +41 -0
- semantio/utils/date_utils.py +44 -0
- semantio/utils/file_utils.py +56 -0
- semantio/utils/logger.py +20 -0
- semantio/utils/validation_utils.py +44 -0
- semantio-0.0.1.dist-info/LICENSE +21 -0
- semantio-0.0.1.dist-info/METADATA +163 -0
- semantio-0.0.1.dist-info/RECORD +40 -0
- semantio-0.0.1.dist-info/WHEEL +5 -0
- semantio-0.0.1.dist-info/entry_points.txt +3 -0
- semantio-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
from fastapi import FastAPI, HTTPException
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
3
|
+
from typing import Optional, Dict, List
|
4
|
+
from ..agent import Agent
|
5
|
+
|
6
|
+
def create_fastapi_app(agent: Agent, api_config: Optional[Dict] = None) -> FastAPI:
|
7
|
+
"""
|
8
|
+
Create a FastAPI app for the given agent.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
agent (Agent): The agent instance for which the API is being created.
|
12
|
+
api_config (Optional[Dict]): Configuration for the API, including CORS settings.
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
FastAPI: A FastAPI app with endpoints for interacting with the agent.
|
16
|
+
"""
|
17
|
+
app = FastAPI()
|
18
|
+
|
19
|
+
# Default CORS settings (allow all)
|
20
|
+
cors_config = {
|
21
|
+
"allow_origins": ["*"],
|
22
|
+
"allow_credentials": True,
|
23
|
+
"allow_methods": ["*"],
|
24
|
+
"allow_headers": ["*"],
|
25
|
+
}
|
26
|
+
|
27
|
+
# Override default CORS settings with user-provided settings
|
28
|
+
if api_config and "cors" in api_config:
|
29
|
+
cors_config.update(api_config["cors"])
|
30
|
+
|
31
|
+
# Add CORS middleware
|
32
|
+
app.add_middleware(
|
33
|
+
CORSMiddleware,
|
34
|
+
allow_origins=cors_config["allow_origins"],
|
35
|
+
allow_credentials=cors_config["allow_credentials"],
|
36
|
+
allow_methods=cors_config["allow_methods"],
|
37
|
+
allow_headers=cors_config["allow_headers"],
|
38
|
+
)
|
39
|
+
|
40
|
+
@app.post("/chat")
|
41
|
+
async def chat(message: str):
|
42
|
+
"""
|
43
|
+
Endpoint to interact with the agent.
|
44
|
+
"""
|
45
|
+
response = agent.print_response(message=message)
|
46
|
+
|
47
|
+
if agent.json_output:
|
48
|
+
return response
|
49
|
+
else:
|
50
|
+
return {"response": response}
|
51
|
+
|
52
|
+
@app.get("/tools")
|
53
|
+
async def get_tools():
|
54
|
+
"""
|
55
|
+
Endpoint to get the list of tools available to the agent.
|
56
|
+
"""
|
57
|
+
return {"tools": agent.tools}
|
58
|
+
|
59
|
+
@app.post("/load_image")
|
60
|
+
async def load_image(image_url: str):
|
61
|
+
"""
|
62
|
+
Endpoint to load an image from a URL.
|
63
|
+
"""
|
64
|
+
try:
|
65
|
+
image = agent.load_image_from_url(image_url)
|
66
|
+
return {"status": "success", "image": "Image loaded successfully"}
|
67
|
+
except Exception as e:
|
68
|
+
raise HTTPException(status_code=400, detail=str(e))
|
69
|
+
|
70
|
+
return app
|
semantio/cli/__init__.py
ADDED
File without changes
|
semantio/cli/main.py
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
import argparse
|
2
|
+
import warnings
|
3
|
+
from hashai.assistant import Assistant
|
4
|
+
from hashai.llm import get_llm
|
5
|
+
from urllib3.exceptions import NotOpenSSLWarning
|
6
|
+
|
7
|
+
# Suppress the NotOpenSSLWarning
|
8
|
+
warnings.filterwarnings("ignore", category=NotOpenSSLWarning)
|
9
|
+
|
10
|
+
def main():
|
11
|
+
parser = argparse.ArgumentParser(description="opAi CLI")
|
12
|
+
parser.add_argument("--message", type=str, required=True, help="Message to send to the assistant")
|
13
|
+
parser.add_argument("--provider", type=str, required=True, help="LLM provider (e.g., groq, openai)")
|
14
|
+
parser.add_argument("--api-key", type=str, required=True, help="API key for the LLM provider")
|
15
|
+
parser.add_argument("--model", type=str, default=None, help="Model name (e.g., mixtral-8x7b-32768)")
|
16
|
+
args = parser.parse_args()
|
17
|
+
|
18
|
+
# Initialize LLM
|
19
|
+
llm_config = {"api_key": args.api_key}
|
20
|
+
if args.model:
|
21
|
+
llm_config["model"] = args.model
|
22
|
+
|
23
|
+
llm = get_llm(provider=args.provider, **llm_config)
|
24
|
+
|
25
|
+
# Create an assistant
|
26
|
+
assistant = Assistant(model=args.provider, llm=llm)
|
27
|
+
assistant.print_response(args.message)
|
28
|
+
|
29
|
+
|
30
|
+
if __name__ == "__main__":
|
31
|
+
main()
|
@@ -0,0 +1,61 @@
|
|
1
|
+
from typing import List, Dict, Any
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
class DocumentLoader:
|
5
|
+
"""
|
6
|
+
A class to load documents from various sources (e.g., files, URLs) into the knowledge base.
|
7
|
+
"""
|
8
|
+
|
9
|
+
def __init__(self):
|
10
|
+
"""
|
11
|
+
Initialize the DocumentLoader.
|
12
|
+
"""
|
13
|
+
pass
|
14
|
+
|
15
|
+
def load_from_file(self, file_path: str) -> List[Dict[str, Any]]:
|
16
|
+
"""
|
17
|
+
Load documents from a file.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
file_path (str): The path to the file.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
List[Dict[str, Any]]: A list of documents, where each document is a dictionary.
|
24
|
+
"""
|
25
|
+
file_path = Path(file_path)
|
26
|
+
if not file_path.exists():
|
27
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
28
|
+
|
29
|
+
# Example: Load a JSON file
|
30
|
+
if file_path.suffix == ".json":
|
31
|
+
import json
|
32
|
+
with open(file_path, "r") as f:
|
33
|
+
return json.load(f)
|
34
|
+
# Example: Load a text file
|
35
|
+
elif file_path.suffix == ".txt":
|
36
|
+
with open(file_path, "r") as f:
|
37
|
+
return [{"text": f.read()}]
|
38
|
+
else:
|
39
|
+
raise ValueError(f"Unsupported file type: {file_path.suffix}")
|
40
|
+
|
41
|
+
def load_from_url(self, url: str) -> List[Dict[str, Any]]:
|
42
|
+
"""
|
43
|
+
Load documents from a URL.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
url (str): The URL to load documents from.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
List[Dict[str, Any]]: A list of documents, where each document is a dictionary.
|
50
|
+
"""
|
51
|
+
import requests
|
52
|
+
response = requests.get(url)
|
53
|
+
if response.status_code != 200:
|
54
|
+
raise ValueError(f"Failed to fetch data from URL: {url}")
|
55
|
+
|
56
|
+
# Example: Load JSON data from a URL
|
57
|
+
if "application/json" in response.headers.get("Content-Type", ""):
|
58
|
+
return response.json()
|
59
|
+
# Example: Load text data from a URL
|
60
|
+
else:
|
61
|
+
return [{"text": response.text}]
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from typing import List, Dict
|
2
|
+
import numpy as np
|
3
|
+
|
4
|
+
class Retriever:
|
5
|
+
def __init__(self, vector_store):
|
6
|
+
self.vector_store = vector_store
|
7
|
+
|
8
|
+
def retrieve(self, query: str, k: int = 5) -> List[Dict]:
|
9
|
+
"""
|
10
|
+
Retrieve relevant documents for a given query.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
query (str): The query string.
|
14
|
+
k (int): Number of documents to retrieve.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
List[Dict]: List of relevant documents.
|
18
|
+
"""
|
19
|
+
# Convert the query to an embedding (dummy implementation for now)
|
20
|
+
query_embedding = self._embed_query(query)
|
21
|
+
|
22
|
+
# Search the vector store for similar embeddings
|
23
|
+
indices = self.vector_store.search(query_embedding, k=k)
|
24
|
+
|
25
|
+
# Retrieve the documents (dummy implementation for now)
|
26
|
+
documents = [{"content": f"Document {i}", "score": 0.9} for i in indices]
|
27
|
+
|
28
|
+
return documents
|
29
|
+
|
30
|
+
def _embed_query(self, query: str) -> np.ndarray:
|
31
|
+
"""
|
32
|
+
Convert a query string to an embedding.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
query (str): The query string.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
np.ndarray: The query embedding.
|
39
|
+
"""
|
40
|
+
# Dummy implementation: return a random embedding
|
41
|
+
return np.random.rand(768) # Assuming 768-dimensional embeddings
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import faiss
|
3
|
+
|
4
|
+
class VectorStore:
|
5
|
+
def __init__(self, dimension: int = 768):
|
6
|
+
"""
|
7
|
+
Initialize a vector store.
|
8
|
+
|
9
|
+
Args:
|
10
|
+
dimension (int): Dimensionality of the embeddings.
|
11
|
+
"""
|
12
|
+
self.index = faiss.IndexFlatL2(dimension)
|
13
|
+
|
14
|
+
def add_embeddings(self, embeddings: np.ndarray):
|
15
|
+
"""
|
16
|
+
Add embeddings to the vector store.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
embeddings (np.ndarray): Array of embeddings to add.
|
20
|
+
"""
|
21
|
+
self.index.add(embeddings)
|
22
|
+
|
23
|
+
def search(self, query_embedding: np.ndarray, k: int = 5) -> np.ndarray:
|
24
|
+
"""
|
25
|
+
Search for similar embeddings.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
query_embedding (np.ndarray): The query embedding.
|
29
|
+
k (int): Number of nearest neighbors to retrieve.
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
np.ndarray: Indices of the nearest neighbors.
|
33
|
+
"""
|
34
|
+
distances, indices = self.index.search(query_embedding.reshape(1, -1), k)
|
35
|
+
return indices[0]
|
semantio/llm/__init__.py
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
from .openai import OpenAILlm
|
2
|
+
from .anthropic import AnthropicLlm
|
3
|
+
# from .llama import LlamaLlm
|
4
|
+
from .groq import GroqLlm
|
5
|
+
|
6
|
+
def get_llm(provider: str, **kwargs):
|
7
|
+
provider = provider.lower() # Convert provider name to lowercase
|
8
|
+
if provider == "openai":
|
9
|
+
return OpenAILlm(**kwargs)
|
10
|
+
elif provider == "anthropic":
|
11
|
+
return AnthropicLlm(**kwargs)
|
12
|
+
# elif provider == "llama":
|
13
|
+
# return LlamaLlm(**kwargs)
|
14
|
+
elif provider == "groq":
|
15
|
+
return GroqLlm(**kwargs)
|
16
|
+
else:
|
17
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from typing import List, Dict, Optional
|
2
|
+
from .base_llm import BaseLLM
|
3
|
+
import anthropic
|
4
|
+
import os
|
5
|
+
|
6
|
+
class AnthropicLlm(BaseLLM):
|
7
|
+
def __init__(
|
8
|
+
self,
|
9
|
+
model: str = "claude-2.1", # Default Anthropic model
|
10
|
+
api_key: Optional[str] = None,
|
11
|
+
):
|
12
|
+
self.model = model
|
13
|
+
self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
|
14
|
+
if not self.api_key:
|
15
|
+
raise ValueError("Anthropic API key is required. Set ANTHROPIC_API_KEY environment variable or pass it explicitly.")
|
16
|
+
self.client = anthropic.Client(api_key=self.api_key)
|
17
|
+
|
18
|
+
def generate(
|
19
|
+
self,
|
20
|
+
prompt: str,
|
21
|
+
context: Optional[List[Dict]] = None,
|
22
|
+
memory: Optional[List[Dict]] = None,
|
23
|
+
) -> str:
|
24
|
+
# Prepare messages for the Anthropic API
|
25
|
+
messages = []
|
26
|
+
if memory:
|
27
|
+
messages.extend(memory)
|
28
|
+
if context:
|
29
|
+
messages.append({"role": "system", "content": "Context: " + str(context)})
|
30
|
+
messages.append({"role": "user", "content": prompt})
|
31
|
+
|
32
|
+
# Call Anthropic API
|
33
|
+
response = self.client.completion(
|
34
|
+
model=self.model,
|
35
|
+
messages=messages,
|
36
|
+
)
|
37
|
+
|
38
|
+
# Extract and return the response
|
39
|
+
return response.choices[0].message.content
|
semantio/llm/base_llm.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import List, Dict, Optional
|
3
|
+
|
4
|
+
class BaseLLM(ABC):
|
5
|
+
@abstractmethod
|
6
|
+
def generate(
|
7
|
+
self,
|
8
|
+
prompt: str,
|
9
|
+
context: Optional[List[Dict]] = None,
|
10
|
+
memory: Optional[List[Dict]] = None,
|
11
|
+
) -> str:
|
12
|
+
pass
|
semantio/llm/groq.py
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
from typing import List, Dict, Optional
|
2
|
+
from .base_llm import BaseLLM
|
3
|
+
import groq
|
4
|
+
import os
|
5
|
+
|
6
|
+
class GroqLlm(BaseLLM):
|
7
|
+
def __init__(
|
8
|
+
self,
|
9
|
+
model: str = "mixtral-8x7b-32768", # Default Groq model
|
10
|
+
api_key: Optional[str] = None,
|
11
|
+
):
|
12
|
+
self.model = model
|
13
|
+
self.api_key = api_key or os.getenv("GROQ_API_KEY")
|
14
|
+
if not self.api_key:
|
15
|
+
raise ValueError("Groq API key is required. Set GROQ_API_KEY environment variable or pass it explicitly.")
|
16
|
+
self.client = groq.Client(api_key=self.api_key)
|
17
|
+
|
18
|
+
def generate(
|
19
|
+
self,
|
20
|
+
prompt: str,
|
21
|
+
context: Optional[List[Dict]] = None,
|
22
|
+
memory: Optional[List[Dict]] = None,
|
23
|
+
) -> str:
|
24
|
+
# Prepare messages for the Groq API
|
25
|
+
messages = []
|
26
|
+
if memory:
|
27
|
+
messages.extend(memory)
|
28
|
+
if context:
|
29
|
+
messages.append({"role": "system", "content": "Context: " + str(context)})
|
30
|
+
messages.append({"role": "user", "content": prompt})
|
31
|
+
|
32
|
+
# Call Groq API
|
33
|
+
response = self.client.chat.completions.create(
|
34
|
+
model=self.model,
|
35
|
+
messages=messages,
|
36
|
+
)
|
37
|
+
|
38
|
+
# Extract and return the response
|
39
|
+
return response.choices[0].message.content
|
semantio/llm/llama.py
ADDED
File without changes
|
semantio/llm/openai.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import List, Dict, Optional
|
2
|
+
from .base_llm import BaseLLM
|
3
|
+
import openai
|
4
|
+
import os
|
5
|
+
|
6
|
+
class OpenAILlm(BaseLLM):
|
7
|
+
def __init__(self, model: str = "gpt-4", api_key: Optional[str] = None):
|
8
|
+
self.model = model
|
9
|
+
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
|
10
|
+
if not self.api_key:
|
11
|
+
raise ValueError("OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it explicitly.")
|
12
|
+
openai.api_key = self.api_key
|
13
|
+
|
14
|
+
def generate(self, prompt: str, context: Optional[List[Dict]] = None, memory: Optional[List[Dict]] = None) -> str:
|
15
|
+
messages = []
|
16
|
+
if memory:
|
17
|
+
messages.extend(memory)
|
18
|
+
if context:
|
19
|
+
messages.append({"role": "system", "content": "Context: " + str(context)})
|
20
|
+
messages.append({"role": "user", "content": prompt})
|
21
|
+
|
22
|
+
response = openai.ChatCompletion.create(
|
23
|
+
model=self.model,
|
24
|
+
messages=messages,
|
25
|
+
)
|
26
|
+
return response.choices[0].message["content"]
|
semantio/memory.py
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
from typing import List, Dict
|
2
|
+
|
3
|
+
class Memory:
|
4
|
+
def __init__(self):
|
5
|
+
self.history = []
|
6
|
+
|
7
|
+
def add_message(self, role: str, content: str):
|
8
|
+
self.history.append({"role": role, "content": content})
|
9
|
+
|
10
|
+
def get_history(self) -> List[Dict]:
|
11
|
+
return self.history
|
semantio/rag.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from typing import List, Dict
|
2
|
+
from .knowledge_base.retriever import Retriever
|
3
|
+
|
4
|
+
class RAG:
|
5
|
+
def __init__(self, retriever: Retriever):
|
6
|
+
self.retriever = retriever
|
7
|
+
|
8
|
+
def retrieve(self, query: str) -> List[Dict]:
|
9
|
+
"""
|
10
|
+
Retrieve relevant context for a given query.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
query (str): The query string.
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
List[Dict]: List of relevant documents.
|
17
|
+
"""
|
18
|
+
return self.retriever.retrieve(query)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# base_tool.py
|
2
|
+
from typing import Dict, Any, Optional
|
3
|
+
from pydantic import BaseModel, Field
|
4
|
+
|
5
|
+
class BaseTool(BaseModel):
|
6
|
+
name: str = Field(..., description="The name of the tool.")
|
7
|
+
description: str = Field(..., description="A brief description of the tool's functionality.")
|
8
|
+
llm: Optional[Any] = Field(None, description="The LLM instance to use for tool execution.")
|
9
|
+
|
10
|
+
def execute(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
11
|
+
"""Execute the tool's functionality."""
|
12
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
semantio/tools/crypto.py
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
from .base_tool import BaseTool
|
2
|
+
import yfinance as yf
|
3
|
+
from typing import Dict, Any, List, Optional
|
4
|
+
from pydantic import Field
|
5
|
+
import logging
|
6
|
+
import re
|
7
|
+
|
8
|
+
# Configure logging
|
9
|
+
logging.basicConfig(level=logging.INFO)
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
class CryptoPriceChecker(BaseTool):
|
13
|
+
default_symbol: str = Field(default="BTC-USD", description="Default cryptocurrency symbol (e.g., 'BTC-USD').")
|
14
|
+
enable_historical_data: bool = Field(default=True, description="Enable fetching historical data.")
|
15
|
+
enable_multiple_symbols: bool = Field(default=True, description="Enable fetching data for multiple cryptocurrencies.")
|
16
|
+
enable_metrics: bool = Field(default=True, description="Enable additional metrics like volume and market cap.")
|
17
|
+
llm: Optional[Any] = Field(None, description="The LLM instance to use for symbol extraction.")
|
18
|
+
|
19
|
+
def __init__(self, **kwargs):
|
20
|
+
super().__init__(
|
21
|
+
name="CryptoPriceChecker",
|
22
|
+
description="Fetch real-time and historical cryptocurrency prices and metrics.",
|
23
|
+
**kwargs
|
24
|
+
)
|
25
|
+
|
26
|
+
def execute(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
27
|
+
"""
|
28
|
+
Execute the cryptocurrency price check based on the provided input.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
input_data (Dict[str, Any]): Input data containing the query and optional parameters.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
Dict[str, Any]: Cryptocurrency price and additional metrics.
|
35
|
+
"""
|
36
|
+
try:
|
37
|
+
query = input_data.get("query", "")
|
38
|
+
symbols = self._extract_symbols(query) if query else [self.default_symbol]
|
39
|
+
time_range = input_data.get("time_range", "1d") # Default to 1 day
|
40
|
+
metrics = input_data.get("metrics", ["price"]) # Default to price only
|
41
|
+
|
42
|
+
results = {}
|
43
|
+
|
44
|
+
for symbol in symbols:
|
45
|
+
# Validate the symbol
|
46
|
+
if not self._is_valid_symbol(symbol):
|
47
|
+
logger.warning(f"Invalid cryptocurrency symbol: {symbol}")
|
48
|
+
results[symbol] = {"error": "Invalid symbol"}
|
49
|
+
continue
|
50
|
+
|
51
|
+
try:
|
52
|
+
crypto = yf.Ticker(symbol)
|
53
|
+
data = {}
|
54
|
+
|
55
|
+
# Fetch real-time price
|
56
|
+
if "price" in metrics:
|
57
|
+
history = crypto.history(period=time_range)
|
58
|
+
if not history.empty:
|
59
|
+
data["price"] = history["Close"].iloc[-1]
|
60
|
+
else:
|
61
|
+
data["price"] = "No data available"
|
62
|
+
|
63
|
+
# Fetch historical data
|
64
|
+
if self.enable_historical_data and "history" in metrics:
|
65
|
+
history = crypto.history(period=time_range)
|
66
|
+
if not history.empty:
|
67
|
+
data["history"] = history.to_dict()
|
68
|
+
else:
|
69
|
+
data["history"] = "No historical data available"
|
70
|
+
|
71
|
+
# Fetch additional metrics
|
72
|
+
if self.enable_metrics:
|
73
|
+
info = crypto.info
|
74
|
+
if "volume" in metrics:
|
75
|
+
data["volume"] = info.get("volume24Hr", "No volume data available")
|
76
|
+
if "market_cap" in metrics:
|
77
|
+
data["market_cap"] = info.get("marketCap", "No market cap data available")
|
78
|
+
|
79
|
+
results[symbol] = data
|
80
|
+
except Exception as e:
|
81
|
+
results[symbol] = {"error": str(e)}
|
82
|
+
|
83
|
+
return results
|
84
|
+
except Exception as e:
|
85
|
+
return {"error": str(e)}
|
86
|
+
|
87
|
+
def _extract_symbols(self, query: str) -> List[str]:
|
88
|
+
"""
|
89
|
+
Use the LLM to extract cryptocurrency symbols from the user's query.
|
90
|
+
"""
|
91
|
+
if not self.llm:
|
92
|
+
logger.error("LLM instance not available for symbol extraction.")
|
93
|
+
return []
|
94
|
+
|
95
|
+
# Create a prompt for the LLM
|
96
|
+
prompt = f"""
|
97
|
+
Extract the cryptocurrency symbols from the following user query.
|
98
|
+
Return the symbols as a comma-separated list in the format CRYPTO-FIAT (e.g., "BTC-USD,ETH-USD").
|
99
|
+
If no symbols are found, return "None".
|
100
|
+
|
101
|
+
User Query: "{query}"
|
102
|
+
"""
|
103
|
+
|
104
|
+
try:
|
105
|
+
# Call the LLM to generate the response
|
106
|
+
response = self.llm.generate(prompt=prompt)
|
107
|
+
logger.info(f"LLM response: {response}")
|
108
|
+
symbols = response.strip().replace('"', '').replace("'", "")
|
109
|
+
|
110
|
+
# Fallback: Extract symbols from verbose responses
|
111
|
+
if ":" in symbols or "\n" in symbols:
|
112
|
+
# Look for patterns like "BTC-USD,ETH-USD" in the response
|
113
|
+
symbol_pattern = re.compile(r"\b[A-Z]{2,5}-[A-Z]{2,5}\b")
|
114
|
+
symbols = ",".join(symbol_pattern.findall(symbols))
|
115
|
+
logger.info(f"Extracted symbols: {symbols}")
|
116
|
+
|
117
|
+
# Parse the response into a list of symbols
|
118
|
+
if symbols.lower() == "none":
|
119
|
+
return []
|
120
|
+
return [s.strip() for s in symbols.split(",")]
|
121
|
+
except Exception as e:
|
122
|
+
logger.error(f"Failed to extract symbols: {e}")
|
123
|
+
return []
|
124
|
+
|
125
|
+
def _is_valid_symbol(self, symbol: str) -> bool:
|
126
|
+
"""
|
127
|
+
Validate if the symbol is a valid cryptocurrency symbol.
|
128
|
+
"""
|
129
|
+
# Check if the symbol follows the CRYPTO-FIAT format (e.g., BTC-USD, ETH-USD)
|
130
|
+
if "-" not in symbol:
|
131
|
+
return False
|
132
|
+
crypto, fiat = symbol.split("-")
|
133
|
+
return bool(crypto.strip()) and bool(fiat.strip())
|