All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 1m12s
use gpt-oss-120b on groq instead of gemini because of more generous rate limits
185 lines
6.7 KiB
TypeScript
185 lines
6.7 KiB
TypeScript
/**
|
|
* Test script to verify Groq API integration for weather descriptions
|
|
*/
|
|
|
|
const apiKey = process.env.GROQ_API_KEY
|
|
|
|
if (!apiKey || apiKey.trim() === "") {
|
|
console.error("ERROR: GROQ_API_KEY is not set or empty")
|
|
console.error("Please check your .env file")
|
|
console.error("Add: GROQ_API_KEY=your_api_key_here")
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log(`API Key found (length: ${apiKey.length}, starts with: ${apiKey.substring(0, 10)}...)`)
|
|
|
|
// Sample weather data
|
|
const sampleWeatherData = {
|
|
condition: "PartlyCloudy",
|
|
temperature: 18,
|
|
feelsLike: 16,
|
|
humidity: 0.65,
|
|
windSpeed: 15,
|
|
precipitationChance: 0.3,
|
|
uvIndex: 6,
|
|
daytimeCondition: "MostlyCloudy",
|
|
isNighttime: false,
|
|
}
|
|
|
|
// Build the same prompt that the real code uses
|
|
function buildWeatherPrompt(weatherData: typeof sampleWeatherData): string {
|
|
let laterConditions = ""
|
|
|
|
// If it's nighttime, mention tomorrow's weather
|
|
if (weatherData.isNighttime && (weatherData as any).tomorrowCondition) {
|
|
laterConditions = `\n\nTomorrow's forecast:
|
|
- Condition: ${(weatherData as any).tomorrowCondition}
|
|
- High: ${(weatherData as any).tomorrowHighTemp ? Math.round((weatherData as any).tomorrowHighTemp) : "N/A"}°C
|
|
- Low: ${(weatherData as any).tomorrowLowTemp ? Math.round((weatherData as any).tomorrowLowTemp) : "N/A"}°C
|
|
${(weatherData as any).tomorrowPrecipitationChance ? `- Precipitation chance: ${Math.round((weatherData as any).tomorrowPrecipitationChance * 100)}%` : ""}`
|
|
}
|
|
// Otherwise, mention changes later today
|
|
else if (weatherData.daytimeCondition || (weatherData as any).overnightCondition) {
|
|
laterConditions = `\n- Later today: ${weatherData.daytimeCondition || (weatherData as any).overnightCondition}`
|
|
}
|
|
|
|
return `Generate a concise, natural weather description for a dashboard. Keep it under 25 words.
|
|
|
|
Current conditions:
|
|
- Condition: ${weatherData.condition}
|
|
- Feels like: ${Math.round(weatherData.feelsLike)}°C
|
|
- Humidity: ${Math.round(weatherData.humidity * 100)}%
|
|
- Wind speed: ${Math.round(weatherData.windSpeed)} km/h
|
|
${weatherData.precipitationChance ? `- Precipitation chance: ${Math.round(weatherData.precipitationChance * 100)}%` : ""}
|
|
- UV index: ${weatherData.uvIndex}${laterConditions}
|
|
|
|
Requirements:
|
|
- Be conversational and friendly
|
|
- Focus on what matters most (condition, any warnings)
|
|
- DO NOT mention the current temperature - it will be displayed separately
|
|
- CRITICAL: If it's nighttime and tomorrow's forecast is provided, PRIORITIZE tomorrow's weather (e.g., "Cool night. Tomorrow will be partly cloudy with a high of 10°C.")
|
|
- If it's daytime and conditions change later, mention it (e.g., "turning cloudy later", "clearing up tonight")
|
|
- Tomorrow's temperature is OK to mention
|
|
- Mention feels-like only if significantly different (>3°C) and explain WHY (e.g., "due to wind", "due to humidity")
|
|
- Include precipitation chance if >30%
|
|
- For wind: Use descriptive terms (calm, light, moderate, strong, extreme) - NEVER use specific km/h numbers
|
|
- For UV: Use descriptive terms (low, moderate, high, very high, extreme) - NEVER use specific numbers
|
|
- Warn about extreme conditions (very hot/cold, high UV, strong winds)
|
|
- Use natural language, not technical jargon
|
|
- NO emojis
|
|
- One or two short sentences maximum
|
|
|
|
Example good outputs (DAYTIME):
|
|
- "Partly cloudy and pleasant. Light winds make it comfortable."
|
|
- "Clear skies, but feels hotter. High UV - wear sunscreen."
|
|
- "Mostly sunny, turning cloudy later. Comfortable conditions."
|
|
- "Rainy with 70% chance of more rain. Bring an umbrella."
|
|
- "Feels much colder due to strong winds. Bundle up."
|
|
- "Cloudy and mild, clearing up tonight."
|
|
- "Feels warmer due to humidity. Stay hydrated."
|
|
|
|
Example good outputs (NIGHTTIME - focus on tomorrow):
|
|
- "Cool night. Tomorrow will be sunny and warm with a high of 24°C."
|
|
- "Clear skies. Expect partly cloudy skies tomorrow, high of 10°C."
|
|
- "Chilly night. Tomorrow brings rain with a high of 15°C."
|
|
- "Mild evening. Tomorrow will be hot and sunny, reaching 32°C."
|
|
|
|
Example BAD outputs (avoid these):
|
|
- "Mostly clear at 7°C, feels like 0°C due to the 21 km/h wind." ❌ (don't mention current temp, don't use specific wind speed)
|
|
- "Sunny at 28°C with UV index of 9." ❌ (don't mention current temp, don't use specific UV number)
|
|
- "Temperature is 22°C with 65% humidity." ❌ (don't mention current temp, too technical)
|
|
|
|
Generate description:`
|
|
}
|
|
|
|
const prompt = buildWeatherPrompt(sampleWeatherData)
|
|
|
|
console.log("\n=== Sending request to Groq API ===\n")
|
|
console.log("URL:", "https://api.groq.com/openai/v1/chat/completions")
|
|
console.log("Model:", "openai/gpt-oss-120b")
|
|
console.log("\nPrompt length:", prompt.length, "characters")
|
|
console.log("\nFirst 500 chars of prompt:")
|
|
console.log(prompt.substring(0, 500))
|
|
console.log("...\n")
|
|
|
|
const requestBody = {
|
|
model: "openai/gpt-oss-120b",
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: prompt,
|
|
},
|
|
],
|
|
temperature: 0.7,
|
|
max_tokens: 1000,
|
|
top_p: 0.95,
|
|
}
|
|
|
|
try {
|
|
console.log("Making API request...\n")
|
|
const response = await fetch(
|
|
"https://api.groq.com/openai/v1/chat/completions",
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify(requestBody),
|
|
}
|
|
)
|
|
|
|
console.log("=== Response Status ===")
|
|
console.log(`Status: ${response.status} ${response.statusText}`)
|
|
console.log(`OK: ${response.ok}`)
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text()
|
|
console.log("\n=== Error Response Body ===")
|
|
console.log(errorText)
|
|
try {
|
|
const errorJson = JSON.parse(errorText)
|
|
console.log("\n=== Parsed Error JSON ===")
|
|
console.log(JSON.stringify(errorJson, null, 2))
|
|
} catch {
|
|
// Not JSON, that's fine
|
|
}
|
|
process.exit(1)
|
|
}
|
|
|
|
const data = await response.json()
|
|
|
|
console.log("\n=== Full API Response ===")
|
|
console.log(JSON.stringify(data, null, 2))
|
|
|
|
console.log("\n=== Extracting Response Text ===")
|
|
const description = data.choices?.[0]?.message?.content?.trim() || ""
|
|
|
|
if (!description) {
|
|
console.error("ERROR: Response text is empty!")
|
|
console.log("Response structure:")
|
|
console.log("- choices exists:", !!data.choices)
|
|
console.log("- choices[0] exists:", !!data.choices?.[0])
|
|
console.log("- message exists:", !!data.choices?.[0]?.message)
|
|
console.log("- content exists:", !!data.choices?.[0]?.message?.content)
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log("\n=== Weather Description ===")
|
|
console.log(description)
|
|
console.log("\n=== Description Length ===")
|
|
console.log(description.length, "characters")
|
|
console.log(description.split(" ").length, "words")
|
|
|
|
console.log("\n✅ Test completed successfully!")
|
|
} catch (error) {
|
|
console.error("\n=== Request Failed ===")
|
|
console.error(error)
|
|
if (error instanceof Error) {
|
|
console.error("Error message:", error.message)
|
|
console.error("Error stack:", error.stack)
|
|
}
|
|
process.exit(1)
|
|
}
|
|
|