FACETS-LLM-Assistant / scripts /frontier_agent.py
spicyavo467's picture
Upload 36 files
bb5765c verified
Raw History Blame Contribute Delete
11 kB
# imports
import os
import re
import math
import json
from typing import List, Dict
import requests
from sentence_transformers import SentenceTransformer
from agent import Agent
# Optional imports that may not be available
try:
from openai import OpenAI
OPENAI_AVAILABLE = True
except ImportError:
OPENAI_AVAILABLE = False
class FrontierAgent(Agent):
"""
An agent that uses LLMs (OpenAI, DeepSeek, or other) with RAG to estimate prices
"""
name = "Frontier Agent"
color = Agent.BLUE
DEFAULT_MODEL = "deepseek-coder-7b-instruct" # Changed to DeepSeek as default
def __init__(self, collection=None):
"""
Set up this instance by connecting to OpenAI or DeepSeek, to the Chroma Datastore,
And setting up the vector encoding model
"""
self.log("Initializing Frontier Agent")
# Set up the LLM client
self.model_name = os.getenv("LLM_MODEL", self.DEFAULT_MODEL)
self.client = self._setup_client()
# Set up the vector store
self.collection = collection
# Set up the embedding model
try:
self.model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
self.log("Sentence transformer loaded successfully")
except Exception as e:
self.log(f"Error loading sentence transformer: {e}")
self.model = None
self.log("Frontier Agent is ready")
def _setup_client(self):
"""
Set up the client for the appropriate LLM service based on environment variables
"""
# Check for DeepSeek API key
deepseek_api_key = os.getenv("DEEPSEEK_API_KEY")
if deepseek_api_key:
if OPENAI_AVAILABLE:
client = OpenAI(api_key=deepseek_api_key, base_url="https://api.deepseek.com")
self.model_name = "deepseek-chat"
self.log("Frontier Agent is set up with DeepSeek API")
return client
else:
self.log("OpenAI package not installed, can't use DeepSeek API")
# Check for OpenAI API key
openai_api_key = os.getenv("OPENAI_API_KEY")
if openai_api_key and OPENAI_AVAILABLE:
client = OpenAI(api_key=openai_api_key)
self.model_name = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
self.log(f"Frontier Agent is set up with OpenAI model: {self.model_name}")
return client
# If no valid API setup, use local inference
self.log("No valid API keys found, using local inference mode")
return None
def make_context(self, similars: List[str], prices: List[float]) -> str:
"""
Create context that can be inserted into the prompt
:param similars: similar products to the one being estimated
:param prices: prices of the similar products
:return: text to insert in the prompt that provides context
"""
message = "To provide some context, here are some other items that might be similar to the item you need to estimate.\n\n"
for similar, price in zip(similars, prices):
message += f"Potentially related product:\n{similar}\nPrice is ${price:.2f}\n\n"
return message
def messages_for(self, description: str, similars: List[str], prices: List[float]) -> List[Dict[str, str]]:
"""
Create the message list to be included in a call to OpenAI/DeepSeek
With the system and user prompt
:param description: a description of the product
:param similars: similar products to this one
:param prices: prices of similar products
:return: the list of messages in the format expected by the API
"""
system_message = "You estimate prices of items. Reply only with the price, no explanation"
user_prompt = self.make_context(similars, prices)
user_prompt += "And now the question for you:\n\n"
user_prompt += "How much does this cost?\n\n" + description
# Format differently for DeepSeek models
if "deepseek" in self.model_name.lower():
return [
{"role": "system", "content": system_message},
{"role": "user", "content": user_prompt},
{"role": "assistant", "content": "Price is $"}
]
else:
return [
{"role": "system", "content": system_message},
{"role": "user", "content": user_prompt},
{"role": "assistant", "content": "Price is $"}
]
def find_similars(self, description: str):
"""
Return a list of items similar to the given one by looking in the Chroma datastore
"""
self.log("Frontier Agent is searching for similar products")
if self.collection is None or self.model is None:
self.log("Vector search unavailable - using fallback")
return self._fallback_similars(description)
try:
vector = self.model.encode([description])
results = self.collection.query(query_embeddings=vector.astype(float).tolist(), n_results=5)
documents = results['documents'][0][:]
prices = [m['price'] for m in results['metadatas'][0][:]]
self.log("Frontier Agent has found similar products")
return documents, prices
except Exception as e:
self.log(f"Error during vector search: {e}")
return self._fallback_similars(description)
def _fallback_similars(self, description: str):
"""
Provide fallback similar products when vector search is unavailable
"""
# Create some generic similar products based on keywords in the description
desc_lower = description.lower()
similars = []
prices = []
# Generic items and prices based on common categories
if any(word in desc_lower for word in ['glass', 'cup', 'mug', 'plate']):
similars.extend([
"SET OF 4 BLUE GLASS MUGS",
"CERAMIC WHITE COFFEE CUP",
"VINTAGE GLASS TUMBLER SET"
])
prices.extend([12.99, 8.50, 15.75])
if any(word in desc_lower for word in ['christmas', 'holiday', 'decoration']):
similars.extend([
"CHRISTMAS TREE DECORATION SET",
"HOLIDAY HANGING ORNAMENTS"
])
prices.extend([7.99, 9.25])
if any(word in desc_lower for word in ['box', 'storage', 'wooden']):
similars.extend([
"WOODEN STORAGE BOX WITH LID",
"SMALL TRINKET BOX"
])
prices.extend([14.50, 6.75])
# Fill with generic items if needed
generic_items = [
"VINTAGE METAL SIGN",
"CERAMIC FLOWER POT",
"DECORATIVE WALL HANGING",
"SET OF SCENTED CANDLES",
"METAL STORAGE BASKET"
]
generic_prices = [11.99, 8.50, 15.25, 12.00, 9.75]
# Ensure we have at least 5 items
while len(similars) < 5 and generic_items:
similars.append(generic_items.pop(0))
prices.append(generic_prices.pop(0))
self.log("Using fallback similar products")
return similars, prices
def get_price(self, s) -> float:
"""
A utility that plucks a floating point number out of a string
"""
s = s.replace('$','').replace(',','')
match = re.search(r"[-+]?\d*\.\d+|\d+", s)
return float(match.group()) if match else 0.0
def _local_inference(self, description: str, similars: List[str], prices: List[float]) -> float:
"""
Provide a local price estimate when API isn't available
This is a simple heuristic based on similar items
"""
# Calculate a price based on similar items (if available)
if similars and prices:
# Average of similar prices with more weight to the first (most similar) ones
weights = [1.0, 0.8, 0.6, 0.4, 0.2][:len(prices)]
weighted_sum = sum(p * w for p, w in zip(prices, weights))
weighted_avg = weighted_sum / sum(weights[:len(prices)])
# Adjust based on description length and keywords
premium_keywords = ['luxury', 'vintage', 'special', 'handmade', 'set', 'collection']
budget_keywords = ['small', 'mini', 'basic', 'simple']
desc_lower = description.lower()
premium_count = sum(1 for word in premium_keywords if word in desc_lower)
budget_count = sum(1 for word in budget_keywords if word in desc_lower)
adjustment = (premium_count - budget_count) * 0.15 # 15% adjustment per keyword
final_price = weighted_avg * (1 + adjustment)
return max(0.99, final_price) # Ensure minimum price
else:
# Fallback to basic estimate
return 9.99 # Default price
def price(self, description: str) -> float:
"""
Make a call to OpenAI or DeepSeek to estimate the price of the described product,
by looking up 5 similar products and including them in the prompt to give context
:param description: a description of the product
:return: an estimate of the price
"""
documents, prices = self.find_similars(description)
# If we have a client, use the API
if self.client:
try:
self.log(f"Frontier Agent is calling {self.model_name} with similar product context")
response = self.client.chat.completions.create(
model=self.model_name,
messages=self.messages_for(description, documents, prices),
seed=42,
max_tokens=5
)
reply = response.choices[0].message.content
result = self.get_price(reply)
self.log(f"Frontier Agent completed - predicting ${result:.2f}")
return result
except Exception as e:
self.log(f"API call failed: {e}")
self.log("Falling back to local inference")
result = self._local_inference(description, documents, prices)
self.log(f"Frontier Agent fallback - predicting ${result:.2f}")
return result
else:
# Use local inference if no API client
self.log("Using local inference (no API client)")
result = self._local_inference(description, documents, prices)
self.log(f"Frontier Agent local inference - predicting ${result:.2f}")
return result