refactor: update model initialization to use settings.model across services
This commit is contained in:
@@ -5,6 +5,7 @@ class Settings(BaseSettings):
|
|||||||
database_url: Optional[str] = None
|
database_url: Optional[str] = None
|
||||||
secret_key: Optional[str] = None
|
secret_key: Optional[str] = None
|
||||||
api_key: Optional[str] = None
|
api_key: Optional[str] = None
|
||||||
|
model: str = "openai/gpt-oss-120b"
|
||||||
GROQ_API_KEY: str
|
GROQ_API_KEY: str
|
||||||
class Config:
|
class Config:
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
|
|||||||
+85
-80
@@ -727,97 +727,101 @@ async def match_specific_receipts(request: MatchSpecificRequest, db: db_dependen
|
|||||||
)
|
)
|
||||||
logger.info(f"Matching completed, got {len(matching_results)} results")
|
logger.info(f"Matching completed, got {len(matching_results)} results")
|
||||||
|
|
||||||
|
# Filter results by confidence threshold (10% minimum)
|
||||||
|
CONFIDENCE_THRESHOLD = 0.10
|
||||||
|
filtered_results = [r for r in matching_results if r.confidence_score >= CONFIDENCE_THRESHOLD]
|
||||||
|
logger.info(f"After filtering by {CONFIDENCE_THRESHOLD*100}% threshold: {len(filtered_results)} matches remain")
|
||||||
|
|
||||||
# Convert matching results to response format
|
# Convert matching results to response format
|
||||||
match_responses = []
|
match_responses = []
|
||||||
for result in matching_results:
|
for result in filtered_results:
|
||||||
# Get final tax amount from LLM analysis if available, otherwise use receipt's stated tax
|
# Get final tax amount from LLM analysis if available, otherwise use receipt's stated tax
|
||||||
if result.confidence_score > 0:
|
final_tax = result.receipt.tax
|
||||||
final_tax = result.receipt.tax
|
# if result.tax_analysis and "final_tax_amount" in result.tax_analysis:
|
||||||
# if result.tax_analysis and "final_tax_amount" in result.tax_analysis:
|
# final_tax = result.tax_analysis["final_tax_amount"]
|
||||||
# final_tax = result.tax_analysis["final_tax_amount"]
|
|
||||||
|
|
||||||
# Extract flag_for_review and auto_approve from tax_analysis if available
|
# Extract flag_for_review and auto_approve from tax_analysis if available
|
||||||
flag_for_review = None
|
flag_for_review = None
|
||||||
auto_approve = None
|
auto_approve = None
|
||||||
if result.tax_analysis:
|
if result.tax_analysis:
|
||||||
flag_for_review = result.tax_analysis.get("flag_for_review")
|
flag_for_review = result.tax_analysis.get("flag_for_review")
|
||||||
auto_approve = result.tax_analysis.get("auto_approve")
|
auto_approve = result.tax_analysis.get("auto_approve")
|
||||||
|
|
||||||
match_response = MatchResponse(
|
match_response = MatchResponse(
|
||||||
receipt_id=result.receipt.id,
|
receipt_id=result.receipt.id,
|
||||||
transaction_id=result.transaction.id
|
transaction_id=result.transaction.id
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else "no_match",
|
else "no_match",
|
||||||
confidence_score=result.confidence_score * 100,
|
confidence_score=result.confidence_score * 100,
|
||||||
match_reason=result.match_reason,
|
match_reason=result.match_reason,
|
||||||
receipt_vendor=result.receipt.vendor,
|
receipt_vendor=result.receipt.vendor,
|
||||||
receipt_amount=result.receipt.amount,
|
receipt_amount=result.receipt.amount,
|
||||||
receipt_description=result.receipt.description,
|
receipt_description=result.receipt.description,
|
||||||
receipt_category=result.receipt.category,
|
receipt_category=result.receipt.category,
|
||||||
receipt_tax_amount=final_tax,
|
receipt_tax_amount=final_tax,
|
||||||
transaction_vendor=result.transaction.vendor
|
transaction_vendor=result.transaction.vendor
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else "",
|
else "",
|
||||||
transaction_amount=result.transaction.amount
|
transaction_amount=result.transaction.amount
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else 0.0,
|
else 0.0,
|
||||||
tax_analysis=result.tax_analysis,
|
tax_analysis=result.tax_analysis,
|
||||||
flag_for_review=flag_for_review,
|
flag_for_review=flag_for_review,
|
||||||
auto_approve=auto_approve,
|
auto_approve=auto_approve,
|
||||||
# Transaction metadata
|
# Transaction metadata
|
||||||
transaction_source=result.transaction.source
|
transaction_source=result.transaction.source
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
# QuickBooks CSV fields
|
# QuickBooks CSV fields
|
||||||
TxnId=result.transaction.TxnId if result.transaction else None,
|
TxnId=result.transaction.TxnId if result.transaction else None,
|
||||||
AccountType=result.transaction.AccountType
|
AccountType=result.transaction.AccountType
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
AccountNumber=result.transaction.AccountNumber
|
AccountNumber=result.transaction.AccountNumber
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
TransactionDate=result.transaction.TransactionDate
|
TransactionDate=result.transaction.TransactionDate
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
TransactionType=result.transaction.TransactionType
|
TransactionType=result.transaction.TransactionType
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
ChequeNumber=result.transaction.ChequeNumber
|
ChequeNumber=result.transaction.ChequeNumber
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
Description1=result.transaction.Description1
|
Description1=result.transaction.Description1
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
Description2=result.transaction.Description2
|
Description2=result.transaction.Description2
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
VendorId=result.transaction.VendorId
|
VendorId=result.transaction.VendorId
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
VendorName=result.transaction.VendorName
|
VendorName=result.transaction.VendorName
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
AccountId=result.transaction.AccountId
|
AccountId=result.transaction.AccountId
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
AccountName=result.transaction.AccountName
|
AccountName=result.transaction.AccountName
|
||||||
if result.transaction
|
if result.transaction
|
||||||
else None,
|
else None,
|
||||||
Source=result.transaction.source if result.transaction else None,
|
Source=result.transaction.source if result.transaction else None,
|
||||||
)
|
)
|
||||||
match_responses.append(match_response)
|
match_responses.append(match_response)
|
||||||
|
|
||||||
# Calculate statistics
|
# Calculate statistics on filtered results
|
||||||
high_confidence = len(
|
high_confidence = len(
|
||||||
[r for r in matching_results if r.confidence_score >= 0.8]
|
[r for r in filtered_results if r.confidence_score >= 0.8]
|
||||||
)
|
)
|
||||||
low_confidence = len(
|
low_confidence = len(
|
||||||
[r for r in matching_results if r.confidence_score < 0.5]
|
[r for r in filtered_results if r.confidence_score < 0.5]
|
||||||
)
|
)
|
||||||
avg_score = (
|
avg_score = (
|
||||||
sum(r.confidence_score for r in matching_results)
|
sum(r.confidence_score for r in filtered_results)
|
||||||
/ len(matching_results)
|
/ len(filtered_results)
|
||||||
if matching_results
|
if filtered_results
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -829,6 +833,7 @@ async def match_specific_receipts(request: MatchSpecificRequest, db: db_dependen
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(f"Generated stats: {stats}")
|
logger.info(f"Generated stats: {stats}")
|
||||||
|
logger.info(f"Match responses: {match_responses}")
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Match-specific completed successfully with {len(match_responses)} matches"
|
f"Match-specific completed successfully with {len(match_responses)} matches"
|
||||||
)
|
)
|
||||||
|
|||||||
+80
-36
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|||||||
class AIMatcher:
|
class AIMatcher:
|
||||||
def __init__(self, use_batch_matching=True):
|
def __init__(self, use_batch_matching=True):
|
||||||
self.client = groq.Groq(api_key=settings.GROQ_API_KEY)
|
self.client = groq.Groq(api_key=settings.GROQ_API_KEY)
|
||||||
self.model = "llama-3.1-8b-instant"
|
self.model = settings.model
|
||||||
self.max_retries = 3
|
self.max_retries = 3
|
||||||
self.retry_delay = 2 # seconds - increased for rate limiting
|
self.retry_delay = 2 # seconds - increased for rate limiting
|
||||||
self.rate_limit_delay = 1.0 # seconds between API calls
|
self.rate_limit_delay = 1.0 # seconds between API calls
|
||||||
@@ -116,7 +116,7 @@ class AIMatcher:
|
|||||||
for i, transaction in enumerate(candidates):
|
for i, transaction in enumerate(candidates):
|
||||||
transaction_amount_abs = abs(transaction.amount)
|
transaction_amount_abs = abs(transaction.amount)
|
||||||
date_diff = abs((receipt.receipt_date - transaction.transaction_date).days)
|
date_diff = abs((receipt.receipt_date - transaction.transaction_date).days)
|
||||||
amount_diff = abs(receipt.amount - transaction_amount_abs)
|
amount_diff = abs(receipt.amount - transaction_amount_abs - receipt.tax)
|
||||||
amount_percent_diff = (
|
amount_percent_diff = (
|
||||||
(amount_diff / receipt.amount) * 100 if receipt.amount > 0 else 0
|
(amount_diff / receipt.amount) * 100 if receipt.amount > 0 else 0
|
||||||
)
|
)
|
||||||
@@ -127,10 +127,12 @@ Candidate {i + 1}:
|
|||||||
- Amount: ${transaction.amount} (absolute: ${transaction_amount_abs})
|
- Amount: ${transaction.amount} (absolute: ${transaction_amount_abs})
|
||||||
- Date: {transaction.transaction_date.strftime("%Y-%m-%d")} ({date_diff} days difference)
|
- Date: {transaction.transaction_date.strftime("%Y-%m-%d")} ({date_diff} days difference)
|
||||||
- Notes: {transaction.notes}
|
- Notes: {transaction.notes}
|
||||||
- Amount difference: ${amount_diff} ({amount_percent_diff:.1f}%)
|
- Amount difference: ${amount_diff} ({amount_percent_diff:.1f}%) Taking in account receipt tax
|
||||||
"""
|
"""
|
||||||
|
logger.info(f"\nThis is the receipt: {receipt}\n")
|
||||||
|
logger.info(f"\nCandidate text: {candidates_text}\n")
|
||||||
|
|
||||||
prompt = f"""You are an expert at matching receipts to bank transactions. Analyze the receipt below against ALL the candidate transactions and return the BEST match.
|
prompt = f"""You are an expert at matching receipts to bank transactions. Your PRIMARY goal is to find the candidate with the CLOSEST AMOUNT match.
|
||||||
|
|
||||||
RECEIPT TO MATCH:
|
RECEIPT TO MATCH:
|
||||||
- Vendor: {receipt.vendor}
|
- Vendor: {receipt.vendor}
|
||||||
@@ -142,39 +144,52 @@ RECEIPT TO MATCH:
|
|||||||
CANDIDATE TRANSACTIONS:
|
CANDIDATE TRANSACTIONS:
|
||||||
{candidates_text}
|
{candidates_text}
|
||||||
|
|
||||||
SCORING CRITERIA (Amount is the PRIMARY factor):
|
CRITICAL INSTRUCTIONS FOR SELECTION:
|
||||||
|
1. FIRST: Find the candidate(s) with the SMALLEST amount percentage difference
|
||||||
|
2. ONLY if multiple candidates have similar amounts (within 2% of each other), THEN consider vendor/date/notes
|
||||||
|
3. USE THE PERCENTAGE DIFFERENCE PROVIDED for each candidate - DO NOT calculate yourself
|
||||||
|
4. IGNORE vendor/description matches if amounts are far apart (>20% difference)
|
||||||
|
5. The candidate with the closest amount is almost always the correct match
|
||||||
|
|
||||||
Amount Similarity (MOST IMPORTANT - 60% weight):
|
SCORING CRITERIA - AMOUNT DIFFERENCE IS 90% OF THE DECISION:
|
||||||
- Exact match or within 1%: Start at 0.9-1.0
|
|
||||||
- Within 5%: Start at 0.75-0.89
|
|
||||||
- Within 10%: Start at 0.5-0.74
|
|
||||||
- Within 20%: Start at 0.3-0.49
|
|
||||||
- More than 20% difference: Start at 0.0-0.29
|
|
||||||
|
|
||||||
Then adjust UP or DOWN based on:
|
Step 1: Calculate BASE SCORE using the provided amount percentage difference:
|
||||||
- Vendor similarity (20% weight): Exact or similar name increases score
|
- 0-1% difference: Base score = 0.95
|
||||||
- Date proximity (15% weight): Within 7 days increases score, within 30 days moderate increase
|
- 1-2% difference: Base score = 0.90
|
||||||
- Description/notes match (5% weight): Relevant keywords increase score
|
- 2-3% difference: Base score = 0.85
|
||||||
|
- 3-5% difference: Base score = 0.75
|
||||||
|
- 5-7% difference: Base score = 0.65
|
||||||
|
- 7-10% difference: Base score = 0.55
|
||||||
|
- 10-15% difference: Base score = 0.40
|
||||||
|
- 15-20% difference: Base score = 0.25
|
||||||
|
- 20-30% difference: Base score = 0.15
|
||||||
|
- 30-50% difference: Base score = 0.08
|
||||||
|
- 50-100% difference: Base score = 0.03
|
||||||
|
- >100% difference: Base score = 0.01
|
||||||
|
|
||||||
|
Step 2: ADJUST the base score (±0.10 maximum):
|
||||||
|
- Vendor exact match: +0.10
|
||||||
|
- Vendor similar/partial match: +0.05
|
||||||
|
- Date within 7 days: +0.05
|
||||||
|
- Date within 30 days: +0.02
|
||||||
|
- Description/notes keywords match: +0.02
|
||||||
|
- Vendor completely different: -0.05
|
||||||
|
- Date >90 days apart: -0.03
|
||||||
|
|
||||||
|
Step 3: Ensure final score is between 0.0 and 1.0
|
||||||
|
|
||||||
EXAMPLES:
|
|
||||||
- Amount match + vendor match + close date = 0.95-1.0 (Perfect match)
|
|
||||||
- Amount match + different vendor + close date = 0.85-0.94 (High confidence)
|
|
||||||
- Amount match + different vendor + far date = 0.70-0.84 (Medium-high confidence)
|
|
||||||
- Amount similar (5%) + vendor match = 0.75-0.85 (Medium-high confidence)
|
|
||||||
- Amount similar (10%) + some matches = 0.50-0.69 (Medium confidence)
|
|
||||||
- Amount very different (>20%) = 0.0-0.29 regardless of other factors
|
|
||||||
|
|
||||||
CRITICAL: You MUST return valid JSON only. No explanations, no text before or after.
|
CRITICAL: You MUST return valid JSON only. No explanations, no text before or after.
|
||||||
|
|
||||||
Return format:
|
Return format:
|
||||||
{{"candidate_number": 1, "confidence_score": 0.87, "reason": "Exact amount match with similar vendor"}}
|
{{"candidate_number": 1, "confidence_score": 0.65, "reason": "5.8% amount difference with similar vendor"}}
|
||||||
|
|
||||||
Another example:
|
Another example:
|
||||||
{{"candidate_number": 3, "confidence_score": 0.15, "reason": "Poor match but best available"}}
|
{{"candidate_number": 2, "confidence_score": 0.01, "reason": "9850% amount difference, extremely poor match"}}
|
||||||
|
|
||||||
Return ONLY JSON for the best candidate:"""
|
Return ONLY JSON for the best candidate:"""
|
||||||
|
|
||||||
logger.info(f"This is the prompt: {prompt}")
|
# logger.info(f"This is the prompt: {prompt}")
|
||||||
for attempt in range(self.max_retries):
|
for attempt in range(self.max_retries):
|
||||||
try:
|
try:
|
||||||
result = self._call_groq_api_with_timeout(
|
result = self._call_groq_api_with_timeout(
|
||||||
@@ -192,6 +207,22 @@ Return ONLY JSON for the best candidate:"""
|
|||||||
|
|
||||||
if 0 <= candidate_num < len(candidates):
|
if 0 <= candidate_num < len(candidates):
|
||||||
best_transaction = candidates[candidate_num]
|
best_transaction = candidates[candidate_num]
|
||||||
|
|
||||||
|
# Validate the match - catch AI errors with extreme amount differences
|
||||||
|
transaction_amount_abs = abs(best_transaction.amount)
|
||||||
|
amount_diff = abs(receipt.amount - transaction_amount_abs)
|
||||||
|
amount_percent_diff = (
|
||||||
|
(amount_diff / receipt.amount) * 100 if receipt.amount > 0 else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# If amount difference is >100%, force very low score
|
||||||
|
if amount_percent_diff > 100:
|
||||||
|
logger.warning(
|
||||||
|
f"Overriding AI score for extreme mismatch: {receipt.amount} vs {transaction_amount_abs} ({amount_percent_diff:.1f}% diff)"
|
||||||
|
)
|
||||||
|
score = min(0.05, score) # Cap at 0.05 for extreme mismatches
|
||||||
|
reason = f"{amount_percent_diff:.1f}% amount difference, extreme mismatch"
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"AI selected candidate {candidate_num + 1}: {best_transaction.vendor} (score: {score:.3f})"
|
f"AI selected candidate {candidate_num + 1}: {best_transaction.vendor} (score: {score:.3f})"
|
||||||
)
|
)
|
||||||
@@ -303,7 +334,7 @@ Return ONLY JSON for the best candidate:"""
|
|||||||
logger.warning(f"Fallback parsing also failed: {fallback_error}")
|
logger.warning(f"Fallback parsing also failed: {fallback_error}")
|
||||||
|
|
||||||
# Final fallback
|
# Final fallback
|
||||||
logger.warning(f"Could not parse single match response: {result}")
|
# logger.warning(f"Could not parse single match response: {result}")
|
||||||
return -1, 0.0, f"Parse error: {result[:50]}..."
|
return -1, 0.0, f"Parse error: {result[:50]}..."
|
||||||
|
|
||||||
def _filter_candidates(
|
def _filter_candidates(
|
||||||
@@ -311,18 +342,29 @@ Return ONLY JSON for the best candidate:"""
|
|||||||
) -> List[Transaction]:
|
) -> List[Transaction]:
|
||||||
"""Filter transactions to create a reasonable candidate list"""
|
"""Filter transactions to create a reasonable candidate list"""
|
||||||
candidates = []
|
candidates = []
|
||||||
amount_threshold = receipt.amount * 2.0 # 200% threshold - very inclusive
|
|
||||||
|
|
||||||
for transaction in transactions:
|
for transaction in transactions:
|
||||||
# Use absolute value for transaction amount comparison
|
# Use absolute value for transaction amount comparison
|
||||||
transaction_amount_abs = abs(transaction.amount)
|
transaction_amount_abs = abs(transaction.amount)
|
||||||
|
amount_diff = abs(receipt.amount - transaction_amount_abs)
|
||||||
# Only exclude transactions with obviously different amounts
|
|
||||||
if abs(receipt.amount - transaction_amount_abs) <= amount_threshold:
|
# Calculate percentage difference
|
||||||
|
if receipt.amount > 0:
|
||||||
|
percent_diff = (amount_diff / receipt.amount) * 100
|
||||||
|
else:
|
||||||
|
percent_diff = 0
|
||||||
|
|
||||||
|
# Be more restrictive: exclude transactions with >300% difference
|
||||||
|
# This prevents extreme mismatches while still being generous
|
||||||
|
if percent_diff <= 300:
|
||||||
candidates.append(transaction)
|
candidates.append(transaction)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
f"Filtered out transaction ${transaction_amount_abs} for receipt ${receipt.amount} ({percent_diff:.1f}% difference)"
|
||||||
|
)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Filtered {len(transactions)} transactions to {len(candidates)} candidates"
|
f"Filtered {len(transactions)} transactions to {len(candidates)} candidates for receipt ${receipt.amount}"
|
||||||
)
|
)
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
@@ -389,7 +431,9 @@ Return ONLY JSON for the best candidate:"""
|
|||||||
|
|
||||||
Consider description and category similarity in your scoring.
|
Consider description and category similarity in your scoring.
|
||||||
|
|
||||||
The most important factor to consider is the Amount for both the transaction and the receipt. The closer the amounts, the higher the score. If the amounts are different or not close return a low score (0-0.1) based on other factors.
|
THINGS TO NOTE:
|
||||||
|
The most important factor to consider is the Amount for both the transaction and the receipt, the closer the amounts, the higher the score.
|
||||||
|
If the amounts are different or not close return a low score (0-0.1) based on other factors.
|
||||||
|
|
||||||
IMPORTANT: Return ONLY the score and reason separated by a pipe character.
|
IMPORTANT: Return ONLY the score and reason separated by a pipe character.
|
||||||
Format: [score]|[reason]
|
Format: [score]|[reason]
|
||||||
@@ -405,8 +449,8 @@ Return ONLY JSON for the best candidate:"""
|
|||||||
# Parse the result - handle multiple formats
|
# Parse the result - handle multiple formats
|
||||||
score, reason = self._parse_ai_response(result)
|
score, reason = self._parse_ai_response(result)
|
||||||
|
|
||||||
logger.debug(f"AI Response: {result}")
|
# logger.debug(f"AI Response: {result}")
|
||||||
logger.debug(f"Parsed: score={score}, reason={reason}")
|
# logger.debug(f"Parsed: score={score}, reason={reason}")
|
||||||
|
|
||||||
return score, reason
|
return score, reason
|
||||||
|
|
||||||
@@ -509,7 +553,7 @@ Return ONLY JSON for the best candidate:"""
|
|||||||
{"role": "user", "content": prompt}
|
{"role": "user", "content": prompt}
|
||||||
],
|
],
|
||||||
max_tokens=150,
|
max_tokens=150,
|
||||||
temperature=0.1,
|
temperature=0,
|
||||||
)
|
)
|
||||||
return response.choices[0].message.content.strip()
|
return response.choices[0].message.content.strip()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class AIRulesMatcher:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.client = groq.Groq(api_key=settings.GROQ_API_KEY)
|
self.client = groq.Groq(api_key=settings.GROQ_API_KEY)
|
||||||
self.model = "llama-3.1-8b-instant"
|
self.model = settings.model
|
||||||
|
|
||||||
def apply_rules_to_matches(
|
def apply_rules_to_matches(
|
||||||
self, matches: List[Match], ai_rules: Optional[List[Dict]] = None
|
self, matches: List[Match], ai_rules: Optional[List[Dict]] = None
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class LLMTaxAnalyzer:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.client = groq.Groq(api_key=settings.GROQ_API_KEY)
|
self.client = groq.Groq(api_key=settings.GROQ_API_KEY)
|
||||||
self.model = "llama-3.1-8b-instant"
|
self.model = settings.model
|
||||||
self.max_retries = 3
|
self.max_retries = 3
|
||||||
|
|
||||||
def analyze_and_apply_tax_rules_batch(
|
def analyze_and_apply_tax_rules_batch(
|
||||||
|
|||||||
Reference in New Issue
Block a user