Spaces:
Build error
Build error
Download scripts/frontier_agent.py from DS5983-FACETS-team/FACETS-LLM-Assistant: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/spaces/DS5983-FACETS-team/FACETS-LLM-Assistant/resolve/main/scripts/frontier_agent.py
- Command line
-
hf download hf://spaces/DS5983-FACETS-team/FACETS-LLM-Assistant/scripts/frontier_agent.py
-
curl -L -o frontier_agent.py https://huggingface.co/spaces/DS5983-FACETS-team/FACETS-LLM-Assistant/resolve/main/scripts/frontier_agent.py
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 | |