|
936 | 936 | " )\n",
|
937 | 937 | " return response.choices[0].message.content.strip()\n",
|
938 | 938 | " except Exception as e:\n",
|
939 |
| - " return f\"⚠️ Failed to explain result: {e}\"" |
| 939 | + " return f\"⚠️ Failed to explain result: {e}\"\n", |
| 940 | + "\n", |
| 941 | + "# 🧠 Use LLM to explain manual guidance in plain language\n", |
| 942 | + "def explain_manual_guidance(question, anomaly_text, emergency_text):\n", |
| 943 | + " try:\n", |
| 944 | + " context = f\"Anomaly Detection:\\n{anomaly_text}\\n\\nEmergency Protocol:\\n{emergency_text}\"\n", |
| 945 | + " response = openai_client.chat.completions.create(\n", |
| 946 | + " model=\"gpt-3.5-turbo\",\n", |
| 947 | + " messages=[\n", |
| 948 | + " {\"role\": \"system\", \"content\": \"You are a helpful assistant that explains machine manuals in plain language.\"},\n", |
| 949 | + " {\"role\": \"user\", \"content\": f\"The user asked: {question}\\n\\nHere is the relevant manual content:\\n\\n{context}\\n\\nPlease provide a clear answer based on the manual.\"}\n", |
| 950 | + " ],\n", |
| 951 | + " temperature=0.3,\n", |
| 952 | + " max_tokens=300\n", |
| 953 | + " )\n", |
| 954 | + " return response.choices[0].message.content.strip()\n", |
| 955 | + " except Exception as e:\n", |
| 956 | + " return f\"⚠️ Failed to generate manual-based explanation: {e}\"" |
940 | 957 | ]
|
941 | 958 | },
|
942 | 959 | {
|
|
1146 | 1163 | {
|
1147 | 1164 | "cell_type": "code",
|
1148 | 1165 | "execution_count": null,
|
1149 |
| - "id": "a9d328af", |
| 1166 | + "id": "782359be", |
1150 | 1167 | "metadata": {},
|
1151 | 1168 | "outputs": [],
|
1152 | 1169 | "source": [
|
1153 | 1170 | "def get_combined_answer(question):\n",
|
1154 |
| - " # Step 1: Fetch table schemas dynamically\n", |
1155 |
| - " schema_motor = fetch_table_schema(\"motor_readings\")\n", |
1156 |
| - " schema_manual = fetch_table_schema(\"machine_manuals\")\n", |
1157 |
| - "\n", |
1158 |
| - " # Step 2: Analyze question to extract context\n", |
1159 |
| - " machine_id = extract_machine_id_from_query(question)\n", |
1160 |
| - " needs_manual = detect_manual_intent(question)\n", |
1161 |
| - "\n", |
1162 |
| - " # Step 3: Optionally load manual and extract anomaly/emergency info\n", |
1163 |
| - " manual_context = \"\"\n", |
1164 |
| - " anomaly_text, emergency_text = None, None\n", |
1165 |
| - " sql2 = None # placeholder for optional manual SQL\n", |
1166 |
| - "\n", |
1167 |
| - " if needs_manual and machine_id is not None:\n", |
1168 |
| - " manual = fetch_machine_manual(machine_id)\n", |
1169 |
| - " if manual:\n", |
1170 |
| - " anomaly_text, emergency_text = extract_anomaly_info(manual)\n", |
1171 |
| - " manual_context = f'''\n", |
| 1171 | + " try:\n", |
| 1172 | + " # Step 1: Fetch table schemas dynamically\n", |
| 1173 | + " schema_motor = fetch_table_schema(\"motor_readings\")\n", |
| 1174 | + " schema_manual = fetch_table_schema(\"machine_manuals\")\n", |
| 1175 | + "\n", |
| 1176 | + " # Step 2: Analyze question to extract context\n", |
| 1177 | + " machine_id = extract_machine_id_from_query(question)\n", |
| 1178 | + " needs_manual = detect_manual_intent(question)\n", |
| 1179 | + "\n", |
| 1180 | + " # Step 3: Optionally load manual and extract anomaly/emergency info\n", |
| 1181 | + " manual_context = \"\"\n", |
| 1182 | + " anomaly_text, emergency_text = None, None\n", |
| 1183 | + " sql2 = None # placeholder for optional manual SQL\n", |
| 1184 | + "\n", |
| 1185 | + " if needs_manual and machine_id is not None:\n", |
| 1186 | + " manual = fetch_machine_manual(machine_id)\n", |
| 1187 | + " if manual:\n", |
| 1188 | + " anomaly_text, emergency_text = extract_anomaly_info(manual)\n", |
| 1189 | + " manual_context = f'''\n", |
1172 | 1190 | "Manual Guidance for Machine {machine_id}:\n",
|
1173 | 1191 | "--- Anomaly Detection ---\n",
|
1174 | 1192 | "{anomaly_text}\n",
|
1175 | 1193 | "--- Emergency Protocol ---\n",
|
1176 | 1194 | "{emergency_text}\n",
|
1177 | 1195 | "'''\n",
|
1178 |
| - " sql2 = f\"SELECT manual FROM machine_manuals WHERE machine_id = {machine_id};\"\n", |
| 1196 | + " sql2 = f\"SELECT manual FROM machine_manuals WHERE machine_id = {machine_id};\"\n", |
1179 | 1197 | "\n",
|
1180 |
| - " # Step 4: Build LLM prompt\n", |
1181 |
| - " prompt = f'''\n", |
| 1198 | + " # Step 4: Build LLM prompt\n", |
| 1199 | + " prompt = f'''\n", |
1182 | 1200 | "You are a CrateDB data assistant. Your job is to answer questions using telemetry data and machine manuals.\n",
|
1183 | 1201 | "\n",
|
1184 | 1202 | "Tables:\n",
|
|
1188 | 1206 | "{manual_context}\n",
|
1189 | 1207 | "\n",
|
1190 | 1208 | "Instructions:\n",
|
1191 |
| - "- For telemetry questions (e.g., performance metrics), generate SQL and explain the result.\n", |
| 1209 | + "- For telemetry questions (e.g., performance metrics), generate SQL that directly supports answering the user's question.\n", |
| 1210 | + " - If the question implies a condition (e.g., \"Is the machine overheating?\"), retrieve the most recent values and select only the relevant columns (e.g., temperature, timestamp).\n", |
| 1211 | + " - Prefer checking the condition using WHERE clauses when thresholds are known.\n", |
1192 | 1212 | "- For manual-related queries, summarize anomaly and emergency procedures using the context above.\n",
|
1193 |
| - "- If the question involves conditions (e.g., overheating, excessive vibration), \n", |
1194 |
| - " retrieve **both telemetry and manual**. Compare telemetry values against safe thresholds.\n", |
| 1213 | + "- If the question involves conditions (e.g., overheating, excessive vibration), retrieve **both telemetry and manual**. Compare telemetry values against safe thresholds.\n", |
1195 | 1214 | "- Use the following format:\n",
|
1196 | 1215 | "\n",
|
1197 | 1216 | "---\n",
|
|
1206 | 1225 | "<your explanation>\n",
|
1207 | 1226 | "'''\n",
|
1208 | 1227 | "\n",
|
1209 |
| - " # Step 5: Query the LLM with the prompt and question\n", |
1210 |
| - " response = openai_client.chat.completions.create(\n", |
1211 |
| - " model=\"gpt-3.5-turbo\",\n", |
1212 |
| - " messages=[\n", |
1213 |
| - " {\"role\": \"system\", \"content\": \"You are a helpful assistant working with CrateDB telemetry and manuals.\"},\n", |
1214 |
| - " {\"role\": \"user\", \"content\": prompt + f\"\\n\\nUser question: {question}\"}\n", |
1215 |
| - " ],\n", |
1216 |
| - " temperature=0,\n", |
1217 |
| - " max_tokens=700\n", |
1218 |
| - " )\n", |
| 1228 | + " # Step 5: Query the LLM with the prompt and question\n", |
| 1229 | + " response = openai_client.chat.completions.create(\n", |
| 1230 | + " model=\"gpt-3.5-turbo\",\n", |
| 1231 | + " messages=[\n", |
| 1232 | + " {\"role\": \"system\", \"content\": \"You are a helpful assistant working with CrateDB telemetry and manuals.\"},\n", |
| 1233 | + " {\"role\": \"user\", \"content\": prompt + f\"\\n\\nUser question: {question}\"}\n", |
| 1234 | + " ],\n", |
| 1235 | + " temperature=0,\n", |
| 1236 | + " max_tokens=700\n", |
| 1237 | + " )\n", |
1219 | 1238 | "\n",
|
1220 |
| - " output = response.choices[0].message.content.strip()\n", |
| 1239 | + " output = response.choices[0].message.content.strip()\n", |
1221 | 1240 | "\n",
|
1222 |
| - " # Step 6: Parse and clean LLM output\n", |
1223 |
| - " parsed_sql1, parsed_sql2, answer = extract_sql_blocks(output)\n", |
1224 |
| - " sql1 = patch_cratedb_sql(parsed_sql1) if parsed_sql1 else None\n", |
1225 |
| - " sql2 = patch_cratedb_sql(parsed_sql2) if parsed_sql2 else sql2\n", |
| 1241 | + " # Step 6: Parse and clean LLM output\n", |
| 1242 | + " parsed_sql1, parsed_sql2, answer = extract_sql_blocks(output)\n", |
| 1243 | + " sql1 = patch_cratedb_sql(parsed_sql1) if parsed_sql1 else None\n", |
| 1244 | + " sql2 = patch_cratedb_sql(parsed_sql2) if parsed_sql2 else sql2\n", |
1226 | 1245 | "\n",
|
1227 |
| - " # Step 7: Execute queries\n", |
1228 |
| - " df1, df2 = None, None\n", |
| 1246 | + " # Step 7: Execute queries\n", |
| 1247 | + " df1, df2 = None, None\n", |
1229 | 1248 | "\n",
|
1230 |
| - " try:\n", |
1231 | 1249 | " if sql1:\n",
|
1232 |
| - " df1 = pd.read_sql(sql1, con=engine)\n", |
1233 |
| - " except Exception as e:\n", |
1234 |
| - " print(\"❌ Failed to execute SQL 1:\", sql1)\n", |
1235 |
| - " print(e)\n", |
| 1250 | + " try:\n", |
| 1251 | + " df1 = pd.read_sql(sql1, con=engine)\n", |
| 1252 | + " except Exception as e:\n", |
| 1253 | + " print(\"❌ Failed to execute SQL 1:\", sql1)\n", |
| 1254 | + " print(e)\n", |
1236 | 1255 | "\n",
|
1237 |
| - " try:\n", |
1238 | 1256 | " if sql2:\n",
|
1239 |
| - " df2 = pd.read_sql(sql2, con=engine)\n", |
1240 |
| - " except Exception as e:\n", |
1241 |
| - " print(\"❌ Failed to execute SQL 2:\", sql2)\n", |
1242 |
| - " print(e)\n", |
| 1257 | + " try:\n", |
| 1258 | + " df2 = pd.read_sql(sql2, con=engine)\n", |
| 1259 | + " except Exception as e:\n", |
| 1260 | + " print(\"❌ Failed to execute SQL 2:\", sql2)\n", |
| 1261 | + " print(e)\n", |
| 1262 | + "\n", |
| 1263 | + " # ✅ Step 8: Construct final answer\n", |
| 1264 | + " if needs_manual and anomaly_text and emergency_text:\n", |
| 1265 | + " final_answer = explain_manual_guidance(question, anomaly_text, emergency_text)\n", |
| 1266 | + " elif df1 is not None and not df1.empty:\n", |
| 1267 | + " final_answer = explain_result_with_llm(question, df1)\n", |
| 1268 | + " else:\n", |
| 1269 | + " final_answer = answer or \"No answer could be generated.\"\n", |
1243 | 1270 | "\n",
|
1244 |
| - " # Step 8: Construct final natural language explanation\n", |
1245 |
| - " if needs_manual and anomaly_text and emergency_text:\n", |
1246 |
| - " final_answer = f'''\n", |
1247 |
| - "📌 Manual Guidance for Machine {machine_id}\n", |
| 1271 | + " print(\"✅ Finished get_combined_answer\") # ✅ DEBUG\n", |
| 1272 | + " return sql1, sql2, df1, df2, final_answer\n", |
1248 | 1273 | "\n",
|
1249 |
| - "--- Anomaly Detection ---\n", |
1250 |
| - "{anomaly_text}\n", |
1251 |
| - "\n", |
1252 |
| - "--- Emergency Protocol ---\n", |
1253 |
| - "{emergency_text}\n", |
1254 |
| - "'''.strip()\n", |
1255 |
| - " elif df1 is not None and not df1.empty:\n", |
1256 |
| - " final_answer = explain_result_with_llm(question, df1)\n", |
1257 |
| - " else:\n", |
1258 |
| - " final_answer = answer or \"No answer could be generated.\"\n", |
1259 |
| - "\n", |
1260 |
| - " return sql1, sql2, df1, df2, final_answer" |
| 1274 | + " except Exception as e:\n", |
| 1275 | + " print(\"❌ ERROR inside get_combined_answer:\", e)\n", |
| 1276 | + " return None" |
1261 | 1277 | ]
|
1262 | 1278 | },
|
1263 | 1279 | {
|
1264 | 1280 | "cell_type": "markdown",
|
1265 |
| - "id": "cc65dc9e", |
| 1281 | + "id": "d26052c9", |
1266 | 1282 | "metadata": {},
|
1267 | 1283 | "source": [
|
1268 | 1284 | "### Ask a Question (Natural Language Interface)\n",
|
1269 | 1285 | "\n",
|
1270 | 1286 | "This is your main interaction point with the assistant. Just type in a natural language question, and the assistant will:\n",
|
1271 |
| - "\t•\tAnalyze your query\n", |
1272 |
| - "\t•\tDecide if telemetry data or manual context is needed\n", |
1273 |
| - "\t•\tGenerate and run SQL queries\n", |
1274 |
| - "\t•\tExplain the results in plain language\n", |
1275 |
| - "\t•\tOptionally summarize emergency protocols from manuals\n", |
| 1287 | + "- Analyze your query\n", |
| 1288 | + "- Decide if telemetry data or manual context is needed\n", |
| 1289 | + "- Generate and run SQL queries\n", |
| 1290 | + "- Explain the results in plain language\n", |
| 1291 | + "- Optionally summarize emergency protocols from manuals\n", |
1276 | 1292 | "\n",
|
1277 | 1293 | "\n",
|
1278 | 1294 | "Here are some example queries and what kind of answers you can expect:\n",
|
1279 | 1295 | "\n",
|
1280 |
| - "| 💬 Question | 🧠 Assistant Behavior |\n", |
| 1296 | + "| Question | Assistant Behavior |\n", |
1281 | 1297 | "|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------|\n",
|
1282 | 1298 | "| `What was the average temperature for machine 3 last week?` | Retrieves average temperature from telemetry and explains the result. |\n",
|
1283 | 1299 | "| `Is machine 4 overheating?` | Checks the latest temperature and compares it with manual thresholds if present.|\n",
|
|
1289 | 1305 | {
|
1290 | 1306 | "cell_type": "code",
|
1291 | 1307 | "execution_count": null,
|
1292 |
| - "id": "c03bb3ca", |
| 1308 | + "id": "eb35d155", |
1293 | 1309 | "metadata": {},
|
1294 | 1310 | "outputs": [],
|
1295 | 1311 | "source": [
|
| 1312 | + "from IPython.display import display, HTML\n", |
| 1313 | + "\n", |
1296 | 1314 | "# Ask a combined question\n",
|
1297 | 1315 | "# Example questions:\n",
|
1298 | 1316 | "# \n",
|
|
1303 | 1321 | "# Show me the maintenance steps for machine 1.\n",
|
1304 | 1322 | "\n",
|
1305 | 1323 | "# question = \"Type your question here and push ▶️\"\n",
|
1306 |
| - "question = \"Give me the max and min vibration for machine 6 when RPM > 1600\"\n", |
| 1324 | + "question = \"Is machine 4 overheating?\"\n", |
1307 | 1325 | "\n",
|
1308 | 1326 | "sql1, sql2, df1, df2, final_answer = get_combined_answer(question)\n",
|
1309 | 1327 | "\n",
|
|
1330 | 1348 | "output.append(\"\\n🧾 Final Answer:\")\n",
|
1331 | 1349 | "output.append(final_answer.strip() if final_answer else \"No answer returned.\")\n",
|
1332 | 1350 | "\n",
|
1333 |
| - "# Join and print once\n", |
1334 |
| - "print(\"\\n\".join(output))" |
| 1351 | + "# Join all parts of the output\n", |
| 1352 | + "joined_output = \"\\n\".join(output)\n", |
| 1353 | + "\n", |
| 1354 | + "# Display in dark theme box\n", |
| 1355 | + "display(HTML(f\"\"\"\n", |
| 1356 | + "<div style=\"\n", |
| 1357 | + " background-color: #1e1e1e;\n", |
| 1358 | + " color: #f0f0f0;\n", |
| 1359 | + " border: 1px solid #444;\n", |
| 1360 | + " padding: 16px;\n", |
| 1361 | + " border-radius: 6px;\n", |
| 1362 | + " white-space: pre-wrap;\n", |
| 1363 | + " font-family: 'Courier New', monospace;\n", |
| 1364 | + " font-size: 14px;\n", |
| 1365 | + " line-height: 1.6;\n", |
| 1366 | + "\">\n", |
| 1367 | + "{joined_output}\n", |
| 1368 | + "</div>\n", |
| 1369 | + "\"\"\"))" |
1335 | 1370 | ]
|
1336 | 1371 | },
|
1337 | 1372 | {
|
|
0 commit comments