autoweather/Using AI Models for Normali...

2017 lines
1.5 MiB
Plaintext
Raw Normal View History

2024-01-08 15:44:36 +01:00
{
"cells": [
{
"cell_type": "code",
"execution_count": 3,
"id": "94d29fbf-9045-4c83-ace0-3419ac2aba45",
"metadata": {},
"outputs": [],
"source": [
"NORMAL_MESSAGES = [\n",
" \"Good morning! How are you?\",\n",
" \"Are you going to New York tomorrow?\",\n",
" \"I hate it when it rains in New York!\", # Getting tricky\n",
" \"It's too hot in here!\", # Talking, of course, about being indoors.\n",
" \"Does anyone else think it's too hot in here?\",\n",
" \"What's your favorite show on The Weather Channel?\", # We're not talking about the weather.\n",
"]\n",
"\n",
"FORECAST_MESSAGES = [\n",
" \"What's the weather forecast today for New York?\",\n",
" \"Is it going to snow this week in New York?\",\n",
" \"What's the high going to be tomorrow in New York?\",\n",
"]"
]
},
{
"attachments": {
"007baf6f-6005-466e-9e63-232db79b9fe9.png": {
"image/png": "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
},
"192da82a-eb51-4d15-9ed7-aea34c3c66c7.png": {
"image/png": "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
},
"5bbaf8eb-b243-4151-9db6-54af1eb32abe.png": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAZ8AAAGMCAIAAACKwjj2AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOzddXQUVxsH4HdmNdm4O3GICxYsBIcgwa3FWqQtlNJSA0pLW1oKfC1ubYEWKxQIwV2CBScJIUhCCHG3dZm53x8RkpCQBRYKy/scDie7O3Pn3tnd396xOxQhBBBCSO/Q/3UFEELopcB0QwjpJ0w3hJB+wnRDCOknTDeEkH7CdEMI6SdMN4SQfsJ0QwjpJ0w3hJB+wnRDCOknTDeEkH7CdEMI6adnTjdNypaXUQ+EENIt7jNNrUnZQiSPXlJV3ja5+UUUgIW5KZ/P+6/rgpAeera+m/rmT7pcOCm/9OvQIDsjgYFF8OwLal0W/Vq7kXh3z8FTjzJzc/KLDh4/d/biDZXq7Wk9Qq/IM6SbJmULkWZqMaFk2yADit/ml7vM06dj7q2ZPjs6Wdhp6tfTp/T2ebZu5CtCSncMN6cpmt924b3HzdFcn+PHoyiKomiaZ2Dm6Nd51De77spqXqJNx+xTNlLiwWPn+DzeoL5dw1oFhAa2GNS3q5eHy56DpyRS2atoEEJvjWdINx133ACY9NR0De02/LuF3837MNyC0maeVzzYJik7uuu4tJmXK8RHx6TWTWtKGPTOvB++n/PpmA6ie7t/Htnjo30lTVXuRuLdZs4O/j4eAJCbX3Qj8S4A2NtadY8Ii71w/WW1AqG3krbpVtNxU8cvkG00km000jLsZDuGGvBafBZ96OcRLZ2MhSL71hO3pqgBlPvGOwzYWMQyKYvb8Q2itpQDaHJO/DI6rJmZ0MDCI3zyn/FiAgCa+G8DeIaDNlzb8mEHZ5FB2KIUBkjJpVUTO3taGApNnNu88+v5IgIAbObKLgJBh/lH/p09IMBOJDRxCZ95IIetrsa93d+OCHO3MDQwc+v0/uqrZQSASG5t+qSXr41IaGQfNPC7w1ma+pUnZUd3HZe4DflmQjC5+US88QOGf/3N3Hk/r/j37O6PPSA7Zue5xjps1R5l5lRGGwDY21r5t6j629Lc1NhIVFxars0qRQhpQ9t0q59lPBOe31RtF8LcXzby/UNmQ76cOy5AcWPj9LkxZYTrM3TWuFaGFG3TZfrCBRNaCstPf9Gj39zTnG7Tf5w3KbBg6wcDPjtSXtUXUp6b3e+zS83GzP3xq/7u6psL+vX4JLqi9eTvfpzRWXPoq4GTtlSnmPrSvEFfXvcYO2f2kGbF55dNXRCrBAAm9Y9h4cPn73lk23vihyN8ZBllXAHFPPhjeNf3NjzyemfOj18NMI77adio5ffqxhcpPbLzhMS2a+/BkT29yM3de1Ia2dbmmZga0cCo1GzDr1fKzS+yt7WueXjw2LlHmbk1D5s529d+iBB6UUQL6vubpRtEtf+pbsxvfHLx1oFC4LVecEdDCJFuHyIEbovPL8gIIUS6d4w5xfH6Ik5FCFEefs+K5jb/+oqaEObBko5Crsf0U+VqtVqtlhz7wIlj/m6MjKhvzvXnAsflvQPFLCGEsIVbBppybEb9W6BSq9Vq+fW5AVxh7z/zWSZjRQQfeGEL76kJIWzh+j4C4IUtTtEQ6ZFJDjRl2nP1A9XjSkqPTHLgGPVYk65Uq9VqZfryCD6v1c93NLXawRZvG2zKsRq7V0JUF2Z6cKqbRIj62mxfLmUUtfZ2Ssrd+Nhts7vZ0pRpnz8ymaqXTN7dq3hiveTkFV5PuFPz8FZyau2H9V5FCL0grXbls3lnabuOAMAWJ4K64tk6bgAAlImVJR8AgGduYUoTuUxefwpV/OV4pUayvKvp8prnBMUlssrOG2XUOap75Y45ze3LNyRMwT/Dbf6pmZBnUFTGggEAAG1uZcEBAMrYwpxPEblUBszDq9cKWX73EcPcHp95wTy8eq2QkRz/0FXwYfVzHI/iEhaAU/WQlBzZdVIsbBvarDA92zKwtQWzOzom5YuvW1RNQCR7P/DbW1k/nm27GZvWTXCi4Ymt28cszU0vXUsMDWxR9dDCNDe/qObVR5m59rZWTa1JhJC2tEo3fqffK/9QHO7N5p3n+U0FvtnzLY+iAIAQqL/7nRBCgOvz0ba/JrhVbS1TtIWHBQVVR2mpWpMCbT9qXfRnwdWVp02auXEgr+ElkqojERRV57AFIQQoo96LDv8YIayum6GD7+P8IyVHdp2oYKXHZgS7zah6rmz3npQvZlXFG2UY/uX66a2FQjN7r5AQbwt+U23n83mW5ma5+UX2tla5+UUpDzIkUlnKgwwvDxeVSp2bXxjWKqCpMhBC2nr20zCeueOmFX5gq0D+vzeu3BH8NixIUOuF+n0hrk+rYENy+FqC3HtiF5NaLzS6y4vjFhJsTifG7d6TMXZiMw4AwzAcjltoiAWVGH8912XmMJsGDtdW9twEbT/98+twEQCA6uqy9xdciN5z/8tZPlVVcWk/aMgAwZPzNi6sVcCeg6e6R4QZGxl6ebgAAJ/PU6nUB4+fD2sV+CwlIYSa8Mzp9iIdt6fgeE74ZtzagX/82CMibcqQYMOs09FpPbbHTPOoPyFlM3TWx0tPLlg9qFP+lNHtLUsu7b7qs+bId6GNt0TUc+aXHfZ8eWhqp24XhrU2TDtxjJl6Imbc51+F7/l8x/iO4gsTurvIE2JOmMw9uqyHceU8pPjwrlNiXutR00cNdKUBAEj70r+XxR6Pjrn/pY/X8zaTz+f17dkp9sJ1YyNRM2d7AZ+X8iAjN78wrFUgbpYipFvPdq0CxTd9GR03AADKMnLlqb0/jvAuPPDbdz+sPSX2bevBbfAQpajdD0eOLHmvpSp29fffLduX5drO1/Ape7sAgO//2d5jqz/obHx315o/9qVadu7ewhB4vjP2nPz94wiDmxt/nLtoe7J52yALpnqDmRQf3nVSzA3s08u5eg1RVj37hQk0Cbv33G/iLOWnMxIZ9u3Zyc/Ho7i0PCe/yN7WalDfrhhtCOkcRZ7l9Fg27yxtF/7yaoMQQrrybOmGEEJvChzfDSGknzDdEEL6CdMNIaSfMN0QQvoJ0w0hpJ8w3RBC+gnTDSGknzDdEEL6CdMNIaSfMN0QQvoJ0w0hpJ9ey9vsIfTGUmsYjYZhmBcaSOYtxOFwuFwOj8tpelKt4VX0COkGIUSpVAMFXC6Hy9Hlt/RtoGEYjYYBAgIBr9442s8N0w0h3VAoVBRN8bhcmtbNl/Ntw7JErdEQlgiFTQ7jrxXc74aQDqg1DFCA0fYiaJricblAgVqjm+16TDeEdECjYbhcDkbbC6JpisvlaDDdEHp9MAyD+9p0gsvh6OqYDKYbQkg/YbohhPSTtul28OiZZav/Li4py8rOu3Q1vub5pav+yspu5D7JCCH039H2bN6U1HSKokpKygCguKSs5nknRzsDA2Hj8yGE0H9Dq/PdKuOspKTMwEAolyvuP0g3NBBeuhof2SsiyL9FvYmJLHnb6lNpDAAAUUmLlZ4T5g4JfqY7ttdHyu6e3r4/IVMOfFOXsD49e3iLco/9uYnt/1Vv+6d3Pok4/eIDw7Bgmzr7e1VF148dP56QK2E4Jm4tBw3q4CW8s35eUvC3w1s++3k2ytsx84+ZT/mksxOUxG3dfiSb69Qholn8wezwD8cHCZs+hMaWJl0qtAvztqJrFYU7DN40EqncSGSg7dRMZvTCLVfB2JADAMBx7fLJcD+R7g+3stnafU1eN8+2MhunVd9t2eq/IntFWJqb/fLbuk8+HAcAWdl5lhbmJbU6cTUoQ993PvcFACCyhO0bTtmFBzw12giBJs5MVt7f/2+66/tTpzjy1UX3bxbVHE/RIjoK7pxIcGpVO92I9ObOrYc5XSbOHGUnUOXeuiN+sdOZ+R7h7w3j2dNAxA9vpDkMmz3Qn8eWuw9VWGgRbQDA5l07k9q2rbdVraKQ/qNtu0+c0M1Kr84gSUi6e/lqwpiRUbW35y5djT999vKsmVNefX20SrdPPhpvaWEGAN/P+aQy0caMGpiVnefkaPeUuaR3TuwrCnpvmDUHQFOUFBN9KU3GsjzbiKF9w2zp7GPr92oCLXLuFtp1mNTD6FrM
},
"65ca83d8-971f-4c5d-a29b-12497d0bbb6d.png": {
"image/png": "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
},
"69a9e5dc-2052-4b3b-a042-22b7bcc3f0db.png": {
"image/png": "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
},
"6afec254-d5ab-401f-87b4-7ad298106b33.png": {
"image/png": "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
},
"80c1d32c-53d5-4665-9d85-930661403d25.png": {
"image/png": "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
},
"98a7c64b-86d9-4910-a353-df9e2d2ef8a9.png": {
"image/png": "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
},
"ae551171-46fc-4075-9076-58fbc1152b64.png": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAacAAAHsCAIAAAD5EvVgAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOzdd1gURx8H8N/uVcrRe5GO0osiKILYu2LBGluiMYlGjZqoeWOaxkST2HuiJorGBvbeC2IXEBGkS+/t+u3uvH9QPJoCHlFz83ny5PG43dmZ3b3vzZabJRBCgGEYpjbIt10BDMOwfxVOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1AtOPQzD1Mubph6VHK6SemAYhv072G8yM5UcjoSZqqqKmssrKCYADPR1uVzO264Lhv2XvVFfT/H4J1XVAwAAVdz5fYyXmTZPw8D76yiFKot+pz2KSzx6+kpmVl5uQfHpizdv3H4kl6tP6zHs39b21KOSw5EoqwUTCveP1CC4XX9JpF89HZ20de7XkQn8oNlL5s4a6PJGvdD2gsoOjtUnCZLrvyrpZXOoh/9z4xAEQRAkydHQs3TrOeGbI4niurdI3cknZM2UePrCTS6HM3JI74AuHr6enUYO6e3k0OHo6StCkfjfaBCGqZ+2p56KO3oAdEZKBkXajf1u1XfffxpsQLRknn/5ab6o/PyRiyIbJ1uIiTyWUj/FCb7XpO9//OF/X0wO1EqKWDm+32cnSl9XuUdxiTbWFu4uDgCQV1D8KC4RAMxNjfqGBFyPethercAw9dbG1Kvr6Clifhbv1hbv1m5hCIoPjtHgdFoQeWbluM5WAr6Wud+MfckKANmJaRbDdxczdPKv3bgaI8IrAKjcS79MDLDR42sYOAR//GdMFQIAKuZbD47myF0Pwj8NtNbSCFidTAMqvbN5Rk9HA02+jnXXSb/fKkYAwGRt6sXjBa44d+jr4R5mWnydDsELT+UytdVIivh2XIC9gaaGnl3QR1vulyMAJHyyZ94AVxMtvra5V+h3Z7OphpVH5eePXBTajf5mujd63Cj2uB5jl3yz7PuVGw/diPjcAXKOHb7ZXAevVmZWbnXkAYC5qZF7p5p/G+rrCrS1SsoqWrJKMQxrlTamXsOM4+hw3Ga3dGb6+frxH53RG/3Vsqke0ke75y47Vo7YLmOWTu2iSZAmveau+nl6Z37F1S/7DV12ldVn7vLvZ3oW7vtk+IJzFTV9J9nNr4cuuGMzednyxcPsFY9/HtpvXmSl38ffLZ/fkzqzOHRmeG26Ke58P/Krhw5T/vf1aJuSW+tn/3xdBgB0yh9hwWNXHM00HTjj03Eu4hflbB5Bp/4xtveHuzKdJv1v+eLhguifwiZsSKofa6js3OFLQtPeA0cN7u+EHkccTW7mmJ2jo6tNAi1XME2/Xy2voNjc1Lju5ekLNzOz8upe2libK7/EMExlUOspnu8V7dJS/k/+aEXzk1ftC+UDx+/nZxRCSHRgNB/YnRZFiRFCSHR8sj7BcvoyWo4Qkp390Ihkd1xyT4EQnbq2B5/tMPdKhUKhUCiEFz6xYul/cEyMFI+XubOB1eHDUyUMQggxReGhuiyTCYcK5QqFQiF5uMyDzR/4ZwFDv9gYwgVOwKokBUKIKdo5iAecgF+TKSQ6N9OCJHT7b0mVv6yk6NxMC5Z2v60ZMoVCoZBlbAjhcrqsfEYptYMp2T9Kl2U05bgQyaMWOrBqm4SQ4sHXrmxCe8S2p8nJiTHX93/dx5QkdAf9kUXXvKXzwXFpo/WSm1/0MPZZ3csnCSnKLxu8i2GYqrTlmgGTf4M06wEATEkcKCpb19EDACB0jAy5AAAcfQNdEknEkoZTyGPuxsgo4Ybeuhvq/sYrKRVXd/YI7Z4j+laf+KOe3n0kpAv/GWvyT92EHI3icgY0AABIfSMDFgAQAgN9LoEkIjHQ6fcfFDHcvuPC7F7eIUKn339QRAsvfmrL+7T2byyHklIGgFXzEpWeO3K5iu/va1OUkWPo6WdAR0QeS/5ySaeaCZDw+Cdux6vrxzHtNn/P9ulWJDQ6Sn7JUF/3zoM4X89ONS8NdPMKiuvezczKMzc1et2axDCs1dqSetygHdX/kJ4dyOTf4rjNBq5e2xZPEACAEDQ87Y8QQsB2+Wz/X9Ptag7CCdLAwYCAmqvGhNKkQJpP2B65wLu2LaSOjR0L8pteIqq5AkIQ9S6XIISA0B64+uzyEH5t3TQtXF/mIio9d+RSJSO6MN/bbn7N38ojjiZ/ubQm9gjN4K92zvXj8/XMnXx8nA24r2s7l8sx1NfLKyg2NzXKKyhOTn0hFImTU184OXSQyxV5BUUBXTxeVwaGYa32xveHtLqj1yJczy6e3EOP7j3jrQnz4im90bDvxHbp4q2Jzj6IlTjP6KWj9Eazp9RYdj7e+mRcdMTRF1Nm2LAAaJpmsex8fQyIuJiHeR0Whpk0cfm4uqfH8//izyXBWgAA8vvrP/o5KvLo86+WutRUpUP3kaOH8xrP27yALh5HT1/pGxIg0NZ0cugAAFwuRy5XnL54K6CLZ2tKwjCspd409d6ko/cKLMfp30zdFvrH8n4habNGe2tmX41M63fg2ByHhhMSJmOWfr7u8s9bRgYVzJrY3bD0TsR9l63nvvNtvmFa/Rd+FXj0qzOzg/pEhflppl26QM++dGzqosXBRxcdnNajKmp63w6S2GOXdJadX99PUD0PKjl75EoVx2/C3AmhtiQAAOpe9vf66xcjjz3/ysWprc3kcjlD+gddj3oo0NaysTbncTnJqS/yCooCunjiw1sMaydv9NsMgqvbHh09AADCcPCmK8eXj3MuOrXmux+3Xaly9XdgN3nJVKvbj+fOrf2ws/z6lh++W38i27abq+YrzqYBANd9wfELWz7pKUg8svWPEymGPft20gSO6/yjl3d8HqLxePfyZasPJOj7exnQtQfeqOTskctVbM9BA6xrVxhh1H9oAI+KjTj6/DV3X7+atpbmkP5Bbi4OJWUVuQXF5qZGI4f0xpGHYe2HQG9wny+Tf4M0C1ZhbTAMw9rbG6UehmHYewePr4dhmHrBqYdhmHrBqYdhmHrBqYdhmHrBqYdhmHrBqYdhmHrBqYdhmHrBqYdhmHrBqYdhmHrBqYdhmHrBqYdhmHp5J5+/iGH/FQqKpiiapt9oYB41xGKx2GwWh816/aSth0cfwLB2gRCSyRRAAJvNYrPa5dP7H0bRNEXRgIDH4zQY9/zN4dTDsHYhlcoJkuCw2SSp4g+tmmAYpKAoxCA+/7WPY2gdfF4Pw1RPQdFAAI68N0GSBIfNBgIUlIrPD+DUwzDVoyiazWbhyHtDJEmw2SwKpx6Gvftomsbn8lSCzWKp/FoQTj0Mw9QLTj0Mw9RLG1Pv9Plr67f8XVJanp2Tf+d+TN3f123+KzunmedvYxiGvQPaeJdyckoGQRClpeUAUFJaXvd3K0szDQ2+aqqGYRjWDtpyv151zJWWlmto8CUS6fPUDE0N/p37MYMHhHi5d2owMRIn7N9yJY0GAEByUYnMcfqy0d68N6kzKk+8euBkbJYEuLodAgb17+eslXfhzz3MsMUDzV/dd0VVGbdTNQO8TeqdZ5YXP7xw8WJsnpBm6dh1Hjky0In/bOf38d7fju3c+vuEZE+PrbigP2teTysojd534FwO2yowxCbmdE7wp9O8+K+/pMeUxd8pMgtwNiKVisLnId43QpFEW0ujpVPTWZGrwu+DQJMFAMCy7TVvrJuW6i//Mjkt+5i8a1q3MlugLX299Vv+GjwgxFBf75c12+d9OhUAsnPyDQ30S5U6fXUITddJi1wBAJA49sCuK2bBHq+MPITgNXdiy56fPJRh+9HsWZZcRfHzx8V113daECmFzy7FWnVRTj0kenx431lWrxkLJ5jx5HlPnlW92V3bXIfgD8M45iSgqvRHaRZhX4e6c5gK+zFSgxZEHgAw+Q+upfj7OxspFYX995GmfWdM72P0n7rTJTY+8e792MnjRygf/925H3P1xt2lC2e9xYq1JfXmfTbN0EAPAH7437zqpJs8ITQ7
},
"b71f74f7-8633-4d62-8cb3-4403e43af463.png": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAIeCAYAAAC4M7QGAAAABHNCSVQICAgIfAhkiAAAIABJREFUeF7snQWAVFUXx88WtTRISYd0SXeJAYiUgaCIhdhioGKDovhhYhGCAZiAgWAgSEl3SArSnQss7PK++z+77zkzOzM7m87u/I8O+97t+7sz59577r3vhVlGhEICJEACJJDtCYRn+xqygiRAAiRAAkqACp9fBBIgARIIEQJU+CHS0KwmCZAACVDh8ztAAiRAAiFCgAo/RBqa1SQBEiABrwo/bsvnJEMCJEACJJDNCER61gfK3jq909OZ91mAwL4DhyXMlLNwoQKSI0dUFigxi0gCJJCZBJKM8C+sfDnT8n/vvfekTJkykjt3bnnmmWcyLd/sltGKNX/J1Om/y85d+2SvUfrTf50ncxeukPPnL2S3qrI+JEACaSDgpvB1dB+zK8XJdevWTcLCwmTr1q0Bx925c6c8+OCDEhcXJ4899ph07Ngx4LjBHvCLL75QHvhs377drbirVq1y/OCPzq5GjRry/PPPGwV9XsPaYfr27ZtsVaf/Mk9yREVJ987tpWnD2nJ5nWp6XaVSWe0ETsecSTYNBiABEggNAm4KPzNH95s3b5aLFy9K7969ZejQodKmTZsUE4+Pj09xnMyI8NVXX0nFihVVsePam9StW1fr/cADDxjzSw556aWX5L777vMW1KcbRvblypSSWtUraRiYdOAGKVm8qFzRtqn8sWC5z/j0IAESCC0CjsJ3Hd1fWDVczozPq5/UdAL2CPf777+XG264QfLmzSvly5eXKVOmKF24d+7cWa/ffPNNVYznzp3TDuCVV16RsmXLSnR0tLRt21bWrVvntEi1amb02r27fP7551K6dGlp0qSJ+h07dkxuv/12KVSokH5uvfVWOX78uPrt379f03/55ZflySeflGLFiknRokVlyJAhTrq4wEgcZUX8AgUKyIABA+TUqVMaBmXADARlQr5Q1CirNzl9+rTMnDlTrr/+eqlXr558/fXX3oJJrVq11Iw1YsQIWbJkiZQqVUomT57sNawvx5279jrKHmGg5GtVS1D+uC9ibPn58kbLkWMnfCVBdxIggRAi4Ch8r4o9Kr9E1UzZqNOVHZRznjx55Nlnn5UjR47InXfeKWfPnhUo7n79+mnQdu3ayeuvvy5RxiwxaNAgVYJQroiza9cuueaaazSOLfPnz1dTEOI/9dRTcuHCBbnqqqu0M7nnnnvk3nvvlalTp8odd9zh1oxIb/Hixar0L7nkEu1YFi1apGFgXmrcuLF8++230qlTJ7n55ptl06ZNkjNnTtmxY4e0atVKw6BsyOu5554TrD94E3RmKC/CoewrVqyQbdu2eQvquGGEj04R5q1AH22E0XzJ4pe4pQvzDuz4rlKuTMkkbn4LQ08SIIHsSwAPT7uw+TMr5uPoJJ/zK4bBO1m57rrr8AA2a8uWLRrWjFT1/pFHHnHiGnu0uq1evVrdZsyYofeDBw/W+z179lgRERGWUdqWUeL6scNMnz5dw1StWlXj/PTTT3oP+fLLL9UNedrxjFLXtE6cOGHt27dP/Rs2bGgZhapxxo4dq25vv/223iNP13t1TJSBAwdakZGRllH4TvrNmjWzGjVq5BrMuQaLfPnyWcYeb/3555+a7vDhwx3/lStXqhvCgdfy5cutRx99VN3MrEfD2WH69OnjNQ847t1/yFq+eqOb/9oNW5O4eQvnM1F6kAAJZGsCOsK/uH+uhJdoqR8xo3qVNI7ukQTMJ7YUKVJEL2F+8SbLli0T2OQ//PBDHe3jgxEy5MCBA04UjLptdzjao3SsBdjxXn31VU0LswpbkL/pBPTWsywwqUC8LZIifYy8y5Ur56RvFLlbmew8YAL6+eefpWnTpmI6MK0/zEPe7PjfffedVKlSRRo0aCAjR47UWcS4cePspJL9C3MNTDquUqRwgSTxMOJHWAoJkAAJ6D78HK1GOyTOzbjadADzE0w5OQqmOyHTffpN86677pK7777bLUylSv/apX1FRkcB5ekq2PJ5+PBhX1Ec84ldJtj6vYkZ4cvcuXNV4duC3TWeAiWOtYhff/1VKlSo4HibEbvuYKpcubLjBgUP0xTSgeK/7LLLPJPze4999kUKFdSFWtju8XfLtn90Vw7+YpcOtmXuO3BId+9QSIAESCDJwStFkg6j+5SirV+/voSHh8vChQvVPu6qXP2lZSt52Mqx0JoaQRpQylhsNiYcTQKjeih62w+2fMwi/AlG8qgDFl9hl4csXbpU1wuweIs1B1uwMN2rVy9/ySXrB0WOrZfYjZMvbx5V8hB0BlD203+db5R9nWTTYQASIIHQIOBV4etCbQaM7v0hxWgcI3uM1DH67dmzpy6UYsHT2PJ9RoXSxKLv6NGjdWcOFl+xOIu97S+88ILPeK4eTzzxhCp7jLhhWsKo+4cffpAFCxYI/Mw6ge4CgumnePHigpH8sGHDpEOHDk4yJ0+elF9++UXNOdjtYwt2GmEnDjoDV4UfUMGSCQTF3vnKVrr1ErtxsECb07hhhJ8wsq+jo38KCZAACYBAEoUflqNAmnbmpAXrqFGj9OQtbNk4iARTDrZYwh5v298904dN//fffxez+KuKGJ0D9ribBU/PoD7vYVKZN2+ePP7446rcCxYsKDhMZm/DtBU/yoXOoEWLFrrTx1WQd2xsrHTp0sXNHWm1bNlS5syZI2aR1mcZUuuRNzqPKn1svbR36EDJ04yTWqKMRwLZl0AYlqRdq5ewgNs6+9aYNSMBEiCBECWQROGHKAdWmwRIgASyPYEkD0/L9jVmBUmABEggRAlQ4Ydow7PaJEACoUeACj/02pw1JgESCFECVPgh2vCsNgmQQOgRoMIPvTZnjUmABEKUABV+iDY8q00CJBB6BKjwQ6/NWWMSIIEQJUCFH6INz2qTAAmEHgEq/NBrc9aYBEggRAlQ4Ydow7PaJEACoUeACj/02pw1JgESCFECSZ6WGaIcWG0SIIEUEsBzF2PNexfi4uJTGJPBM5NAZGSEPjYdL3jiw9MykzzzIoFsQuDChTh9yU7OnDnMo8vDVZlQgo8AOuX4+ItyLva8thUVfvC1EUtEAkFNAArk7LlYic6Ti4o+qFvq38JB8cecOSe04WeRBmMxSSBYCJy/cEFyYbTIUX2wNEmy5UBbwaxDhZ8sKgYgARJwJYARPsw4lKxFALZ8tlrWajOWlgSCggBH90HRDCkqBNqMCj9FyBiYBEiABLIuAa8K/+zZc3Lk6HG3WnneZ90qs+QkQAIkEJoEvCr83+cuks8mTxMo/sVLVyuZ519+WxYtXRWalFhrEiABEsgGBLxuy4SiP2M++PvW+xNk6DMP64i/SOGCkjt3rmxQbVaBBEggtQROx5yVvNG5Uxud8f5DAl4Vvl0eKHxbwb/13gRp16ap1K1Vza24U77/RdZt2Cw4iHHqdIwULlRA/e+49Xq5tFTxgKu2YtV6qVypnOTPlzdJnO1/75JpP/6qnU5UVKR069JR6tWpLtN/niMXL16Ua69pnyROSh2wr3joq6Pk6ccGSt68eWTOvMUya86fUqVyeWncoI5MnzlbHn3wjpQmK571evv9T6RD22ZSq8ZlKU6LEUggGAikVeEPefENiYyMdHb6VCxfRvredF2mVy099UemFz6VGbo9WmHL1h1S2IziMZKHPP7Ma3JX/xtVydetXU1KlyqRJJseXa8UfLb9/Y98M22mDH7k7iRhAnGY/+dyKXZJkSQKf+/+gzLmk69MB9JLKlcsJydOnpJt2/8JJMkUhcmdK6fcdduNquwhCxevlP59e0rFCmX0ROFNvbqkKD07sGe9ru9+jcM3VQkyEglkAwIPDryVvwMv7bh63V9qRr/FdICe1hSY1GfPXSxPPTrAS8zAnCIxip9tRrPtWjWRb76bKXWMcu98VVuN/dDAflL60gQl365108BSdAn1y6z5snL1BgkLD9NO46orWslnX3wnJYtfIle0ay5/zF8ie/YdMIc4csrfO3bJp5OmSpkypbSytmCU3d7kDWUPKZA/n1xer2aSsvy9Y7f8
},
"c36cc22b-1f97-4666-8d9d-88c9a35a082c.png": {
"image/png": "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
},
"cd883706-1490-4cf9-a59d-623ca3a9e293.png": {
"image/png": "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
},
"d3f59f76-f15e-4158-a777-3dda65edfb63.png": {
"image/png": "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
}
},
"cell_type": "markdown",
"id": "f1db91b9-4d7f-40af-ad57-ba5314b380f8",
"metadata": {},
"source": [
"# First up - There's Currently Not a Yes/No Binary Model\n",
"\n",
"I mean, it would be so tempting to just go,\n",
"\n",
"```python\n",
"response = model({\n",
" \"question\": \"Is this person asking about the weather forecast?\",\n",
" \"context\": \"Is it going to snow this week in New York?\",\n",
"}) # outputs => \"yes\", or even better `True`\n",
"```\n",
"\n",
"However, there's no such thing. I suppose since human text is so nuanced it would be dangerous for an AI model to make a binary determination like that.\n",
"\n",
"So, we have to think outside the box on this one.\n",
"\n",
"Perhaps we can detect the topic from the question. If it's something like \"weather_forecast\" we can proceed!\n",
"\n",
"# Choosing the Right Model\n",
"\n",
"For this we'll use the \"zero-shot classification\" task.\n",
"\n",
"In order for the model to be viable, I'll use the following metrics:\n",
"\n",
"1. The model should return a weather-related category for questions like\n",
" - What's the forecast in New York for tomorrow?\n",
" - Is it going to rain in New York tomorrow?\n",
" - What's the high in New York this Friday?\n",
"2. The model should **not** return a weather-related category for questions like\n",
" - How do you keep your hair dry in the rain?\n",
" - What do you do to weather the storms of life?\n",
" - What's your favorite show on The Weather Channel?\n",
" - What's worse: snow or rain?\n",
"\n",
"Note I'm focusing only on questions here. Determining if a sentence is a question is a pretty straight-forward task and we'll get to it later. So \"I hate it when it snows in New York!\" will not constitute a weather-related question.\n",
"\n",
"Also note that I focus heavily on rooting out the false positive statements. I realize I'm not rooting them all out, but it's a start for this tutorial.\n",
"\n",
"For now, let's try [zero-shot classifications with topics](https://huggingface.co/models?pipeline_tag=text-classification&sort=trending&search=topic) first. If you have a huggingface account, you cane experiment with these tokens on the model page.\n",
"\n",
"One of the top \"topic\" classifications is **jonaskoenig/topic_classification_04**. Testing out a simple question like \"What's the weather in New York tomorrow?\" is less than promising:\n",
"\n",
"![image.png](attachment:b71f74f7-8633-4d62-8cb3-4403e43af463.png)\n",
"\n",
"If anything, the question is more closely related to \"Health\" than \"Science and Mathematics.\" So that won't do.\n",
"\n",
"Let's check out [facebook/bart-large-mnli](https://huggingface.co/facebook/bart-large-mnli). It's a different type of zero-shot classification mechanism that takes *Text to classify* and *Possible class names*.\n",
"\n",
"Let's start by trying to coerce the model to return a boolean value. Let's start with the values `weather forecast`, `other`.\n",
"\n",
"![image.png](attachment:6afec254-d5ab-401f-87b4-7ad298106b33.png)\n",
"![image.png](attachment:cd883706-1490-4cf9-a59d-623ca3a9e293.png)\n",
"![image.png](attachment:c36cc22b-1f97-4666-8d9d-88c9a35a082c.png)\n",
"\n",
"So far so good. Let's try some of the tricky false negatives we wanted to test:\n",
"\n",
"![image.png](attachment:5bbaf8eb-b243-4151-9db6-54af1eb32abe.png)\n",
"![image.png](attachment:192da82a-eb51-4d15-9ed7-aea34c3c66c7.png)\n",
"![image.png](attachment:d3f59f76-f15e-4158-a777-3dda65edfb63.png)\n",
"![image.png](attachment:65ca83d8-971f-4c5d-a29b-12497d0bbb6d.png)\n",
"\n",
"Alright, so didn't do so well for the last 2. How can we accomodate this?\n",
"\n",
"Let's try adding some false negative categories to catch these. The ones I've chosen are: `weather forecast`, `comparison`, `opinion`, `television`, and, of course, our old favorite `other`.\n",
"\n",
"![image.png](attachment:80c1d32c-53d5-4665-9d85-930661403d25.png)\n",
"![image.png](attachment:98a7c64b-86d9-4910-a353-df9e2d2ef8a9.png)\n",
"\n",
"Nice! And just to verify, let's run through the true positives. \n",
"\n",
"![image.png](attachment:69a9e5dc-2052-4b3b-a042-22b7bcc3f0db.png)\n",
"![image.png](attachment:ae551171-46fc-4075-9076-58fbc1152b64.png)\n",
"![image.png](attachment:007baf6f-6005-466e-9e63-232db79b9fe9.png)\n",
"\n",
"So, looks like **facebook/bart-large-mnli** is good enough for our purposes. Let's set it as a constant for our program."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f184ab70-f638-49c7-a083-36264fcc2223",
"metadata": {},
"outputs": [],
"source": [
"FORECAST_CLASSIFICATION_MODEL = \"facebook/bart-large-mnli\""
]
},
{
"cell_type": "markdown",
"id": "1962acaa-0e50-4100-9e1b-532d757de21a",
"metadata": {},
"source": [
"# Type of Sentence\n",
"\n",
"Before we can determine the weather forecast classification, we need to first determine if the sentence is a statement or a question. This would filter out sentences like \"I hate it when it snows in New York!\"\n",
"\n",
"We could use the same classification model and use `question`, `statement` classifiers, but I'd prefer to use a model already trained on this specific task. One I found is [huaen/question_detection](https://huggingface.co/huaen/)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "93cde3f0-d39c-4499-9634-f0d3dfe64e1c",
"metadata": {},
"outputs": [],
"source": [
"QUESTION_DETECTION_MODEL = \"huaen/question_detection\""
]
},
{
"cell_type": "markdown",
"id": "00217e14-ff87-4404-b493-b8d959aa12d0",
"metadata": {},
"source": [
"So then from here we can load our models."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "658a16b8-7ab1-4876-b6c0-1cee39c104d7",
"metadata": {},
"outputs": [],
"source": [
"from transformers import pipeline\n",
"\n",
"sentence_pipe = pipeline(\"text-classification\", model=QUESTION_DETECTION_MODEL)\n",
"forecast_pipe = pipeline(\"zero-shot-classification\", model=FORECAST_CLASSIFICATION_MODEL)"
]
},
{
"cell_type": "markdown",
"id": "d169bf0b-1db4-4958-9ab2-612e24a3c363",
"metadata": {},
"source": [
"# Determining If a (1) Question is asking about (2) The Weather\n",
"\n",
"So, here is a python function for determining the Golden Question."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "ce36776c-de82-452a-aaad-886770d61e71",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"These should return False\n",
"====================\n",
"\"Good morning! How are you?\" => False\n",
"\"Are you going to New York tomorrow?\" => False\n",
"\"I hate it when it rains in New York!\" => False\n",
"\"It's too hot in here!\" => False\n",
"\"Does anyone else think it's too hot in here?\" => False\n",
"\"What's your favorite show on The Weather Channel?\" => False\n",
"These should return True\n",
"====================\n",
"\"What's the weather forecast today for New York?\" => True\n",
"\"Is it going to snow this week in New York?\" => True\n",
"\"What's the high going to be tomorrow in New York?\" => True\n"
]
}
],
"source": [
"import typing as T # I like to do this ;-)\n",
"\n",
"CLASSIFIERS = [\n",
" \"weather forecast\",\n",
" \"comparison\",\n",
" \"opinion\",\n",
" \"television\",\n",
" \"other\",\n",
"]\n",
"\n",
"class ClassifierOutput(T.TypedDict):\n",
" sequence: T.AnyStr\n",
" labels: T.Iterable[T.AnyStr]\n",
" scores: T.Iterable[float]\n",
"\n",
"def max_label_2(classifier_output : ClassifierOutput):\n",
" max_score = -1\n",
" max_label = None\n",
" for label, score in zip(classifier_output['labels'], classifier_output['scores']):\n",
" if score > max_score:\n",
" max_score = score\n",
" max_label = label\n",
" return max_label\n",
"\n",
"def is_asking_for_forecast(text : T.AnyStr):\n",
" # Returns True if the text is a question and asking about the weather.\n",
" if sentence_pipe(text)[0][\"label\"].lower() != \"question\":\n",
" return False\n",
" labels = forecast_pipe(text, CLASSIFIERS)\n",
" # In cases like this I prefer using \"in\", just in case we add other items.\n",
" return max_label_2(labels).lower() in (\"weather forecast\",)\n",
"\n",
"# Now let's run it through our test cases\n",
"\n",
"print(\"These should return False\")\n",
"print(\"=\"*20)\n",
"for message in NORMAL_MESSAGES:\n",
" is_forecast = is_asking_for_forecast(message)\n",
" print(f'\"{message}\" => {is_forecast}')\n",
"\n",
"print(\"These should return True\")\n",
"print(\"=\"*20)\n",
"for message in FORECAST_MESSAGES:\n",
" is_forecast = is_asking_for_forecast(message)\n",
" print(f'\"{message}\" => {is_forecast}')"
]
},
{
"cell_type": "markdown",
"id": "e34e5716-1a54-43b7-bd2a-696c656dfa70",
"metadata": {},
"source": [
"And looks like they do!\n",
"\n",
"Next up, we need to find 2 things in order to get a forecast:\n",
"\n",
"1. Temporal information. Temporal meaning \"date and time\" type stuff. This might be a bit tricky.\n",
"2. Determine the user's location from the context. This is a lot easier.\n",
"\n",
"# Extracting the Date and Time\n",
"\n",
"For a start we need to extract the date like object. Extraction of these features usually calls for **Token Classification**. After looking for a proper model, the one I happened across was [satyaalmasian/temporal_tagger_BERT_tokenclassifier](https://huggingface.co/satyaalmasian/temporal_tagger_BERT_tokenclassifier?text=What%27s+the+weather+like+in+New+York+this+Friday%3F). This seems to do the trick. In the test I supplied the model was able to pick up **this Friday** as a date.\n",
"\n",
"Let's use it as a pipe."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "8c58440f-5ae6-48a8-9062-e909b652cc31",
"metadata": {},
"outputs": [],
"source": [
"TEMPORAL_TAGGER_MODEL = \"satyaalmasian/temporal_tagger_BERT_tokenclassifier\"\n",
"temporal_pipe = pipeline(\"token-classification\", model=TEMPORAL_TAGGER_MODEL)"
]
},
{
"cell_type": "markdown",
"id": "eb38749a-5052-4c14-a7b0-542bb3b1c2b3",
"metadata": {},
"source": [
"Just to see what kinds of output we get, let's run it through our messages again."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "4fc90055-88c2-4e03-b438-4b4eff84868e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Forecast messages:\n",
"\"What's the weather forecast today for New York?\"\n",
"[{'end': 33,\n",
" 'entity': 'B-DATE',\n",
" 'index': 7,\n",
" 'score': 0.9994288,\n",
" 'start': 28,\n",
" 'word': 'today'}]\n",
"\"Is it going to snow this week in New York?\"\n",
"[{'end': 24,\n",
" 'entity': 'B-DATE',\n",
" 'index': 6,\n",
" 'score': 0.99959654,\n",
" 'start': 20,\n",
" 'word': 'this'},\n",
" {'end': 29,\n",
" 'entity': 'I-DATE',\n",
" 'index': 7,\n",
" 'score': 0.99969256,\n",
" 'start': 25,\n",
" 'word': 'week'}]\n",
"\"What's the high going to be tomorrow in New York?\"\n",
"[{'end': 36,\n",
" 'entity': 'B-DATE',\n",
" 'index': 9,\n",
" 'score': 0.999592,\n",
" 'start': 28,\n",
" 'word': 'tomorrow'}]\n"
]
}
],
"source": [
"import pprint\n",
"print(\"Forecast messages:\")\n",
"for message in FORECAST_MESSAGES:\n",
" result = temporal_pipe(message)\n",
" print(f'\"{message}\"')\n",
" pprint.pprint(result)"
]
},
{
"attachments": {
"36969f32-7874-43cc-b935-c0bc1f7d593c.png": {
"image/png": "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
},
"8f885550-6795-42cd-842b-c963ac789187.png": {
"image/png": "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
},
"b9975009-9f6a-4d9b-9e85-8cb695d611b6.png": {
"image/png": "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
},
"fb91f6cd-74c7-4a06-9adb-4c5a6a2e99eb.png": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABasAAAHbCAIAAABKiFF5AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOzdd1wUx/sH8Nmr9N6LoFgAFQuI2LvYey8xiTEaY4lR08zXGDVRYxJL7EZssWMDRVFULCAdBASRIkV6uQYH13Z/fwAWBMUCh/w+75d5Rbnd2Wd3Z5edZ2fmKLtWzuSjZWHXLj4qRN1RNLYOXT3yMpLUHQUAAAAAAADAx4Sl7gAAAAAAAAAAABocMiAAAAAAAAAA0PwhAwIAAAAAAAAAzR8yIAAAAAAAAADQ/CEDAgAAAAAAAADNHzIgAAAAAAAAAND8IQMCAAAAAAAAAM0fMiAAAAAAAAAA0Px90AyIttuMVUv6WTTXrAql323+wZsR6U/ior2m2rzlXlImU08k+610ZjdMbOrHdpzy3fIx9h9k/1imvef/MstN90OUBc3Fm28vbNthK36Y2pHTeDG9F16bsQsXjbB7/SWj23nSN/N7Ndub6uvU6/gAAAAAALyNd2wsUNotuvbt18XRzlSXTxSS4pykUL9LD6RvX46+x4yx1KWj90XMuwXyLtitF1y+sbIzhxCGUclEuSkxAWf+3Xb4fq7i9as5fLp2SduwZYOmxdIcaTb9HhFwnFf6fJU8afGFtz9i74wy6ff5wsHaD/7bdzGpovpnWl0/WdwlZZ9XkOA1x59l2W9e/6KDJx7KGyVQdaNM+n2+cKgVmxBGpSgXFaTH3g8ITCx+feVolMDUcbG8F7bd0MVfdNfLC9yz+27BCxcMr93opbM7a6Zf3XYg/KPZmVewrAcuXNDLtEZugimPPrrlQpKqHgUoS9IexilFr7+TVOSnxMYLJU37KLF0W/Ya3t+1jYUen5GJi57G373kn/TeZ7ZexwcAAAAA4G28SwaE0usw5vPJbWVxQdePPyksIxrGVvZ6QglN3vplHUundTsr9uN3iOF9KRO2T1t49CnF07dw8hg172uvK71/nfjFyVRl3auwbR1spWGbb6UWvG9jmNuy30AHdvJ7lvIOVErNjiP6PnhyLf0tkhls47atjamihouqCaJzgo6ciBKxNAxs2vcfPn4mt2y3X6aacyDqu1jeB1MmEBt1cLEKCnha3ZKlNNt2basUij/2li2dH3JsWyybIpSW05jPXPLPnA4pYAhDy8T1SX8QQujiuNu337SQIjfmZu77htqwKMNuU6f0YkX5HbuSr+DpGZtb8UqlHyBlU6/jAwAAAADwNt4hA8JvPXBke2XYsYN+GRWVj7n5uRmEEEK0CSEqnnWv8WN7tjXTosVZcTfOX48vVhFCWEaOg4f3bGdjYqDFlOYkBp6/Gl2k1232pyPaaLDaLlkziikNP/73hbR6Nh3eHyMT5uZkZalIVmZqfJj/7czTvt+vmXDzk9MFDKEM3OasW/350PZGquzoc1t//e1ciqLj3CO7F/U10OMcfTRdGfvboEk7xT2Wrvl6XLd2diYcQdKtXT+s+jemlCE8z+2hf8mWdll5R0EIIeyO312+2PGA2ydnBNVb5rh87Xv6m046rD3JqXuUKdtGj9gQ21i7LX90P8bEY3jfuP0BubXkevgWXYcN6d3BRp8jK8mIu3M5MK5QZTXg0zmDrPjUhFW/TaAL7u3/51YOTem26T1iqKuDKV9Rkh517WrgI5GKEEIYFdvMfdKgns7mmkpBekSAb0By1QvcWkquzCew9Nv2Hjaki4OZFi3JTw65cTUoo+zlthPH1HXa572V14+ciRI0WvUgqnKxUCigiaAoX6xnv9C1jdnVzGyGb+PhOcjVzsJElysvSQvxv3jrSRlDWKa9v5ije/NIluP4wZ0sSu/t87ora1uzthcoCdt22NLhqmv3eT36u9ho04KMMN/LsZrdRw7p1MKAJc2Jv+59Na6YJoQQnnmXYUP7dLTWY5XlPgy+4heZIzeq5WJ5dbEKQti2w5Z6Vnj7lXmMHOBslOe3+2isrvvIoT2cLPV5qrKipw/8LwYklTZelwJlRkKaQ4fOtoFPM6qqnE67zi0LEmN5rhbVy9RZPXjmbkNG9W9vpUMLn0TdyXghakrTtsdQz15tLbRpUWb8bd+bsYWNnqFSSgVFUkII0SlTEJVUUFxUSBNCWKa9v6xPfSBsxynLR1ac3uqTzpj2/uJT/VAfifNQVwdTDUVJeoTfpZuPJQwh2m4zvumZfWDH7TzKdtjS4Ur/e+xu/TrbG3IqilKCr/jczSpnCCFEw7b7qNE92plryApTQq+lmk3skLXrv7DG6WDDNrW2ZJK9b8WmKwkh+dmZz5O7tVZRSqdVv2FD3FuZ63IUksKMUP+ztzNpM5eho3u3tzXUIBXC7EeB5/xii1nPjo+K1FVJ2I5TlvXIu/DAsFdfFytdSpr38K6vb1Te/4/uagAAAADw9t4+A8Jt6dxOM/Pm3cyK2p6uWWZd3M2u+e67JmSZuY2fNHpEbvp/D0oZQpeX5ibejvPLEyr1OoyeOtyzffJ/D8KPHubPn9c2ZvdBNXfsVySfOHDz6+0jexucOSdqMX3PgVlFG77ufzVHq9v8Xf/s/iFnzC8hB2YMTP8nZn3Fgr5VyQ2N4sd3//3mjwdPJIb9ftq9dd30m+P2p9ajia6M3Tl6lv7dEzYbOy1szFEwhBDCIQX3fCPafDLMI/bwvYKX38FTui4Tpw/Vjb3o5ZNdodWi54jRnw6X7/RJunXwP82Fnxjc2ny8ahQMx7L39Cntnl48vj1Vqt1u4JQpk+R7D97NJ4Sw9Dq5Wfv5eF0VUmYuI6ZMmFzh5XWnkK6rZCnh2PSdMb1Dkf+FvQkCysRpyMSp01mHDt4peFYZKD3HUbP68UJPnG7M9MfLaJWKYRiGEMIoykuywn2DswrKuLa9Jk8f0St597UsmhBC6TgMm2iWFnbxUF6ZpJCmubXU9lJCCMvEfZjz7fPHdxZxWgyYMH76XJe8OP+T+y8ojLtNnDRySHryyYQKStt5/LQBvJCzu84WUmbdxk+cPqps99lHNS+WOhaTEkIog47jRwgT7p/+t6RUUGbRe/pA40cndp4sUGkaWVlrFJU16rXGUqXHJLUf37HltYxkOSGEMuzYySrzwb0yN9fKBequHvzWg6ePssvwOer9uEyjhfvI8S3ZkpzKdQy7TZjevSzg+N5EIbdFv/ETZg0Q7biW0WR6ldSrPrx8GihdF89+0VfOH7ok4dr2HTthQv+sv30f12jJs0y6j+xy3/fc3nMyrTb9J48Z3fvJnutZNNFyHDmjv2niJa/jmTI9hz6jhzpr5GY12t7SRXkFLPf2bQ0eJwhfvErrqKKyVn3He3CC/tv9oITSMbW2YAnklL77mOF2eecOnHwq4xtYtNCViF45OnVUEkJYNgM8K274HdtepDJ0Hj7Nc5R7ute9kiZTFwAAAACgSXnrCfYoHWNDnqQgv65mFJ0efCXmqaCstPhJcEgaZWFpUrmJ8qdxUam5grJySW5MbBYxMTGg3ivwD6wiLSWbbW1rxWZ3nD7HNXrX6lNxucLi1Os79gVZjBvXlVvLGomXT9yITi8SFif7nQ0S2rW2b/qTFVJstjLr3uUofq+RXQxfDpcybO/etiLi8q3EXJFYkBvv7x+lcu7ZSf+Vs8Sx93DTTwy8Hl9QWl6a/+BORK6pS0ezysKYp+HXw58KSktL0u5fvV9s0dnZjPWakjkt3bsapN65HJpRLBYXpYX63swy69HNriorxxC+7YCZI21SfU7dzlXPO10W39DebYiHpTAxqYAmhNDFSdEJGUWS8vKSlLikYh1TE171ktqi8HNXIp9kZxeIla+r7cKYO8GpxWJRfsL9+EIunXor8FGuSFyUFhL5lG1mZsgilGGHHo7ikMthGQKptCQ96M4jlnMHB17N0F63GMXXyL7rHRCf/jSnqIxhsVmEUcmk5VJxydNHcSnFjZtspChVeuxDedtObTUIIYRl0rGzUWpUkpRQVNWO1FU9uC27OGsl3/WPzhWVivMTAm/FVicNWBauPa2f3r4WlS0uLytOuhmartfepUVTmjGz3vXhOaYiKfBG3FOBRFSQeCf6Kd/CsrZb
}
},
"cell_type": "markdown",
"id": "cd1797b3-b213-49d2-99a4-1d69f3501146",
"metadata": {},
"source": [
"Okay, great. But these are still words. We need to make sense of them.\n",
"\n",
"This is where we'll have to roll up our sleeves and take advantage of that prompt energy.\n",
"\n",
"# Testing with text-generation-webui (TGW)\n",
"\n",
"I'm sure you can use any text-to-text app for this. I've taken a liking to TGW, though.\n",
"\n",
"In TGW I selected a fairly small model \n",
"([TheBloke_airoboros-l2-7B-gpt4-2.0-GGUF](https://huggingface.co/TheBloke/airoboros-l2-7B-gpt4-2.0-GGUF))\n",
"and then went to the **Default** tab\n",
"\n",
"Believe it or not we can coerce an LLM engine to output in a specific format. If you're using this article for something more advanced, I'd *highly* recommend using JSON format, as\n",
"[LLMs are known to produce syntactically-correct JSON 100% of the time](https://news.ycombinator.com/item?id=37125118).\n",
"\n",
"Within the **Default** tab, I provide the following input for the **Textbox**:\n",
"\n",
"```\n",
"You can only reply in raw UNIX timestamps or \"None.\" You must calculate an OUTPUT TIMESTAMP based on a CURRENT TIMESTAMP and a DELTA specified by a human. If you are unable to reply with UNIX timestamp, you must reply with the value \"None.\"\n",
"\n",
"CURRENT TIMESTAMP:\n",
"DELTA:\n",
"OUTPUT TIMESTAMP:\n",
"```\n",
"\n",
"And run it through a few tests...\n",
"\n",
"![image.png](attachment:b9975009-9f6a-4d9b-9e85-8cb695d611b6.png)\n",
"![image.png](attachment:fb91f6cd-74c7-4a06-9adb-4c5a6a2e99eb.png)\n",
"![image.png](attachment:8f885550-6795-42cd-842b-c963ac789187.png)\n",
"\n",
"Something interesting happens if we pass it a curveball:\n",
"\n",
"![image.png](attachment:36969f32-7874-43cc-b935-c0bc1f7d593c.png)\n",
"\n",
"This isn't exactly what I had in mind, but we can work with it. All that matters is that we have (accurate) unix timestamp as output.\n",
"\n",
"# Combining Temporal Tokens With the Calculated Future Date\n",
"\n",
"I like the **TheBloke/airoboros-l2-7B-gpt4-2.0-GGUF** model. I'll be cheating a little bit here. I've tried a few on-the-fly models and each one has lead to a system freeze, so I'll just load up the model I already have saved from textgeneration-webui."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "f6117e8e-266e-41d8-ba53-c29c05dfe3cc",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "2de82b644a404288bf10ba9967449541",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Fetching 1 files: 0%| | 0/1 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "2712d7518899463182b50e7c7b071a79",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Fetching 0 files: 0it [00:00, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pathlib import Path\n",
"import os\n",
"from ctransformers import AutoModelForCausalLM as CAutoModelForCausalLM\n",
"from dotenv import load_dotenv\n",
"\n",
"load_dotenv()\n",
"\n",
"assert os.getenv(\"AIROBOROS_PATH\") is not None, \"Set the AIROBORS_PATH environment variable\"\n",
"LLM_MODEL = Path(os.getenv(\"AIROBOROS_PATH\")).resolve()\n",
"assert LLM_MODEL.exists()\n",
"\n",
"DATE_CALCULATOR_TEMPLATE = \"\"\"You can only reply in raw UNIX timestamps or \"None.\" You must calculate an OUTPUT TIMESTAMP based on a CURRENT TIMESTAMP and a DELTA specified by a human. If you are unable to reply with UNIX timestamp, you must reply with the value \"None.\"\n",
"\n",
"CURRENT TIMESTAMP: {current_timestamp}\n",
"DELTA: {delta}\n",
"OUTPUT TIMESTAMP:\"\"\"\n",
"\n",
"# Load model directly\n",
"from transformers import AutoTokenizer, AutoModelForCausalLM\n",
"\n",
"# tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL)\n",
"model = CAutoModelForCausalLM.from_pretrained(\"TheBloke/airoboros-l2-7B-3.0-GGUF\",\n",
" model_file=str(LLM_MODEL / \"airoboros-l2-7B-gpt4-2.0.Q4_K_M.gguf\"),\n",
" model_type=\"llama\",\n",
" gpu_layers=0,\n",
" max_new_tokens=2048,\n",
" context_length=2048)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "af05cc0a-14a6-4d89-9fa8-a14eb18114c3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\"tomorrow morning\" => 2023-12-07 04:20:00\n",
"\"next week\" => 2023-12-13 16:20:00\n",
"\"today in 2 hours\" => 2023-12-08 16:20:00\n",
"\"monkey biscuits!!!\" => 2023-12-07 10:55:00\n"
]
}
],
"source": [
"from datetime import datetime, timedelta\n",
"import typing as T\n",
"\n",
"TIMESTAMP_FORMAT = \"%Y-%m-%d %I:%M %p\"\n",
"\n",
"def get_intentioned_time_from_delta(delta : T.AnyStr, current_timestamp=None):\n",
"\n",
" current_timestamp = current_timestamp or (datetime.now().strftime(TIMESTAMP_FORMAT))\n",
"\n",
" prompt = DATE_CALCULATOR_TEMPLATE.format(current_timestamp=current_timestamp, delta=delta)\n",
" response = model(prompt)\n",
"\n",
" # Extract only the output timestamp\n",
" out_ts_text = response.split(\"OUTPUT TIMESTAMP:\")[-1].strip().rstrip()\n",
"\n",
" # Make sure the output text can be parsed correctly.\n",
" try:\n",
" return datetime.strptime(out_ts_text, TIMESTAMP_FORMAT)\n",
" except Exception as e:\n",
" print(e)\n",
" return None\n",
"\n",
"# Now let's run through some tests\n",
"current_timestamp = \"2023-12-06 4:20 PM\"\n",
"messages = [\n",
" \"tomorrow morning\",\n",
" \"next week\",\n",
" \"today in 2 hours\",\n",
" \"monkey biscuits!!!\",\n",
"]\n",
"for message in messages:\n",
" intentioned_time = get_intentioned_time_from_delta(message, current_timestamp)\n",
" print(f'\"{message}\" => {intentioned_time}')"
]
},
{
"cell_type": "markdown",
"id": "855b04b7-0e49-4dac-b9a8-6adcf003c0f1",
"metadata": {},
"source": [
"Apparently \"Monkey bicuits!!!\" means \"in 4 hours!\" Who knew?\n",
"\n",
"Probably if this was a legitimate produdct I'd vie for trying to make the \"monkey biscuits!!!\" case `None`. But I'll leave it be for now (perhaps a good prompt engineering assignment for the reader!). Let's move on. Now, how do we get the `delta` value?\n",
"\n",
"If you remember (or scroll back up to--for our ADHD folks like me!) the output from **Extracting the Date and Time** above, then you saw that when we extracted the keywords, they were in a list of dicts, sometimes with just one item.\n",
"\n",
"Since (in our text-generation-webui tests) the values like `in 3 days` worked, let's just string together the delta values as the delta."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "f99990d2-091b-4ca1-8dda-44418bd73c2e",
"metadata": {},
"outputs": [],
"source": [
"def get_delta_value(message : T.AnyStr):\n",
" return ' '.join([t[\"word\"] for t in temporal_pipe(message)])"
]
},
{
"cell_type": "markdown",
"id": "85605b6b-8920-4f4e-a294-3ec4786f91b1",
"metadata": {},
"source": [
"Now let's test out our `get_delta_value` function."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "b5075fe4-43a2-4a71-91e2-9dcba166c825",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\"Is it going to rain tomorrow?\" => \"tomorrow\"\n",
"\"What's the high temperature on wednesday of next week?\" => \"wednesday next week\"\n",
"\"Is it going to be cold today?\" => \"today\"\n"
]
}
],
"source": [
"messages = [\n",
" \"Is it going to rain tomorrow?\",\n",
" \"What's the high temperature on wednesday of next week?\",\n",
" \"Is it going to be cold today?\",\n",
"]\n",
"for message in messages:\n",
" dv = get_delta_value(message)\n",
" print(f'\"{message}\" => \"{dv}\"')"
]
},
{
"cell_type": "markdown",
"id": "fd9a0dc5-8037-4c55-b2c0-e9e2d2e70c8b",
"metadata": {},
"source": [
"Looks good. Finally, we're ready to determine the date that's offset from the timestamp! Let's go!"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "120635e7-ff6d-4270-939d-858ff17e780f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\"Is it going to rain tomorrow in New York?\" => 2023-12-07 04:00:00\n",
"\"What's the high temperature on wednesday of next week in New York?\" => 2023-12-06 16:00:00\n",
"\"Is it going to be cold today in New York?\" => 2023-12-06 05:00:00\n",
"\"Is it going to be cold when I walk my dog in New York?\" => 2023-12-06 05:00:00\n"
]
}
],
"source": [
"def determine_intentioned_time_from_message(message, now=None):\n",
" now = now or datetime.now()\n",
" now_ts = now.strftime(TIMESTAMP_FORMAT)\n",
"\n",
" # Extracts the delta value (e.g.) \n",
" delta = get_delta_value(message)\n",
"\n",
" intention_timestamp = get_intentioned_time_from_delta(delta, now_ts)\n",
" return intention_timestamp\n",
"\n",
"\n",
"# Now let's test it!\n",
"current_time = datetime(2023, 12, 6, 4) # Yay! We can use datetime now!\n",
"messages = [\n",
" \"Is it going to rain tomorrow in New York?\",\n",
" \"What's the high temperature on wednesday of next week in New York?\",\n",
" \"Is it going to be cold today in New York?\",\n",
" \"Is it going to be cold when I walk my dog in New York?\",\n",
"]\n",
"for message in messages:\n",
" intentioned_time = determine_intentioned_time_from_message(message, current_time)\n",
" print(f'\"{message}\" => {intentioned_time}')"
]
},
{
"cell_type": "markdown",
"id": "cfd8d2e3-8d1e-4711-ab26-6754957f425c",
"metadata": {},
"source": [
"So it seems sadly no LLM can yet tell me how cold it is when I walk my dog. :-(\n",
"\n",
"# Extracting Entities\n",
"\n",
"What are entities you ask?\n",
"\n",
"Jack Nickleson, Miranda Lambert, The Flintsones, CNN!...Basically, \"official\" tokens.\n",
"\n",
"For that we'll use the\n",
"[mdarhri00/named-entity-recognition](https://huggingface.co/mdarhri00/named-entity-recognition)\n",
"model for extracting named entities.\n",
"\n",
"Let's load *that* into the pipeline."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "91309648-89c0-47a1-b06a-20720b4a621c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\"Is it going to rain tomorrow in New York?\" => \"New York\"\n",
"\"Is it going to rain tomorrow in Liverpool?\" => \"Liverpool\"\n",
"\"Is it going to rain tomorrow in South Africa?\" => \"South Africa\"\n",
"\"Is it going to rain tomorrow in Ouagadougou?\" => \"Ou ##aga ##dou ##gou\"\n",
"\"Is it going to rain tomorrow in D3velsla1r?\" => \"None\"\n"
]
}
],
"source": [
"ENTITY_MODEL = \"mdarhri00/named-entity-recognition\"\n",
"ENTITY_MESSAGES = [\n",
" \"Is it going to rain tomorrow in New York?\",\n",
" \"Is it going to rain tomorrow in Liverpool?\",\n",
" \"Is it going to rain tomorrow in South Africa?\",\n",
" \"Is it going to rain tomorrow in Ouagadougou?\", # Capital of Burkina Faso ;-)\n",
" \"Is it going to rain tomorrow in D3velsla1r?\", # Um....\n",
"]\n",
"\n",
"# Add the entity model to the pipeline\n",
"entity_pipe = pipeline(\"token-classification\", model=ENTITY_MODEL)\n",
" \n",
"def extract_location(message: T.AnyStr):\n",
" location = \"\"\n",
" for e in entity_pipe(message):\n",
" if e[\"entity\"] == \"location\":\n",
" location += \" \" + e[\"word\"]\n",
" return location.strip().rstrip() or None\n",
"\n",
"# Now let's test it!\n",
"for message in ENTITY_MESSAGES:\n",
" name = extract_location(message)\n",
" print(f'\"{message}\" => \"{name}\"')"
]
},
{
"cell_type": "markdown",
"id": "8696f3e9-6e2d-4462-a88f-3a0e7d807046",
"metadata": {},
"source": [
"**Note:** The above code does definitely have an issue, namely that it's assuming only *one* entity exists in the sentence. So therefore, something like, \"I'm travling from New York to London, what's the weather like next week?\" would result in the single entity detecting being `New York London`. To fix this, you'd essentially need to finding groupsings of the entities. But it's getting late and I'm too lazy for that now. 😝"
]
},
{
"cell_type": "markdown",
"id": "a474fad8-8653-4abf-93ee-981b7ba592a9",
"metadata": {},
"source": [
"# Using AI to Find the GPS Coordinates From an Entity Location\n",
"\n",
"If it was 2016 I'd say we need to do reverse geocoding, which would either entail:\n",
"\n",
"- Signing up for a reverse geocoding service, or\n",
"- Spinning up an instance of OpenStreetmaps in Docker\n",
"\n",
"But it's 2023 (going on 2024). In the age of AI, we don't need this.\n",
"\n",
"In fact, we can reuse the **Intel/neural-chat-7b-v3-1** model to do this very thing.\n",
"\n",
"I won't bore you with the details, but I ran through a bunch of prompt templates in TGW, and came up with the following prompt:\n",
"\n",
"```\n",
"You can only respond in raw json format. Answer any question asked of you. Return the GPS COORDINATES of a given LOCALE in the form `{\"lat\": number, \"lon\": number}`. Use your best guess. Think outside the box. Only provide one response.\n",
"\n",
"LOCALE: %(locale)s\n",
"GPS COORDINATES:\n",
"```\n",
"\n",
"Note that for the Python template, we should use the '%' form of the Python string to avoid conflicts with the JSON schema definition.\n",
"\n",
"For testing, though I threw in a few city and even country names, and it did a pretty bang-up job returning the GPS locations.\n",
"\n",
"So now we can define our own reverse geocode function."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "ec8a0bba-abe7-4957-94c8-135c8913e16e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\"Sydney\" => -33.8675, 151.2098\n",
"\"London\" => 51.5072, -0.1277\n",
"\"New York\" => 40.7127, -73.9614\n",
"\"South Africa\" => -34.027165, 18.975735\n",
"\"D3velsla1r\" => 50.496827, -10.757556\n"
]
}
],
"source": [
"import json\n",
"\n",
"GEOCODE_TEMPLATE=\"\"\"You can only respond in raw json format. Answer any question asked of you. Return the GPS COORDINATES of a given LOCALE in the form `{\"lat\": number, \"lon\": number}`. Use your best guess. Think outside the box. Only provide one response.\n",
"\n",
"LOCALE: %(locale)s\n",
"GPS COORDINATES:\"\"\"\n",
"\n",
"def reverse_geocode(location_name : T.AnyStr):\n",
"\n",
" prompt = GEOCODE_TEMPLATE % {\"locale\": location_name}\n",
"\n",
" response = model(prompt)\n",
"\n",
" coordinate_str = response.split(\"GPS COORDINATES: \")[-1].strip().rstrip()\n",
"\n",
" # In production you should probably do something if it can't be parsed.\n",
" # for now we'll just swallow the error.\n",
" try:\n",
" return json.loads(coordinate_str)\n",
" except Exception as err:\n",
" print(err)\n",
" return None\n",
"\n",
"# Let's try it out:\n",
"\n",
"LOCATIONS = [\n",
" \"Sydney\",\n",
" \"London\",\n",
" \"New York\",\n",
" \"South Africa\",\n",
" \"D3velsla1r\",\n",
"]\n",
"\n",
"for location in LOCATIONS:\n",
" coords = reverse_geocode(location)\n",
" if coords:\n",
" print(f'\"{location}\" => {coords[\"lat\"]}, {coords[\"lon\"]}')\n",
" else:\n",
" print(f'\"{location}\" => None')"
]
},
{
"attachments": {
"c5146535-b959-49c8-a6ce-6a355cc70f54.png": {
"image/png": "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
}
},
"cell_type": "markdown",
"id": "0e2493ba-e987-4c9b-938c-f79768c26c75",
"metadata": {},
"source": [
"And now we know where D3velsla1r is!\n",
"\n",
"![image.png](attachment:c5146535-b959-49c8-a6ce-6a355cc70f54.png)\n",
"\n",
"Again, I wouldn't worry too much about this because the user in the message would already need to have gotten past the question check, the topical check, and the date/time check. If they're really concerned about the weather tomorrow in \"D3velsla1r,\" more power to 'em!\n",
"\n",
"Note that this reverse geocode method won't work for locations where there's a name conflict (e.g. Portland, OR vs. Portland, ME), but that's been an issue with standard reverse geocoding as well. You'd have to explicitly put in the thing that breaks the conflict (i.e. the state) or the more popular one will win out (Portland, OR).\n",
"\n",
"# Fetching The Data From OpenWeatherMaps\n",
"\n",
"Now we can extract everything we need to get the weather from OpenWeatherMaps:\n",
"\n",
"- The date and time of the forecast\n",
"- The GPS coordinates of the intended location.\n",
"\n",
"Let's start querying OpenWeatherMap."
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "f9f6fa79-8da5-40eb-b971-beccb9604088",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'current': {'cloud_cover': 58,\n",
" 'icon': 'mostly_cloudy',\n",
" 'icon_num': 29,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Mostly cloudy',\n",
" 'temperature': 27.0,\n",
" 'wind': {'angle': 108, 'dir': 'ESE', 'speed': 0.9}},\n",
" 'daily': {'data': [{'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 60},\n",
" 'icon': 13,\n",
" 'precipitation': {'total': 2.6,\n",
" 'type': 'rain'},\n",
" 'temperature': 27.5,\n",
" 'temperature_max': 29.5,\n",
" 'temperature_min': 26.5,\n",
" 'weather': 'rain_shower',\n",
" 'wind': {'angle': 240,\n",
" 'dir': 'WSW',\n",
" 'speed': 1.3}},\n",
" 'day': '2023-12-07',\n",
" 'evening': None,\n",
" 'icon': 13,\n",
" 'morning': None,\n",
" 'summary': 'Rain showers changing to light rain by '\n",
" 'evening. Temperature 27/30 °C, but a '\n",
" 'feels-like temperature of up to 35 °C.',\n",
" 'weather': 'rain_shower'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 44},\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0,\n",
" 'type': 'none'},\n",
" 'temperature': 27.5,\n",
" 'temperature_max': 29.8,\n",
" 'temperature_min': 26.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 288,\n",
" 'dir': 'WNW',\n",
" 'speed': 1.9}},\n",
" 'day': '2023-12-08',\n",
" 'evening': None,\n",
" 'icon': 4,\n",
" 'morning': None,\n",
" 'summary': 'Partly sunny. Temperature 26/30 °C, but a '\n",
" 'feels-like temperature of up to 35 °C.',\n",
" 'weather': 'partly_sunny'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 49},\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0,\n",
" 'type': 'none'},\n",
" 'temperature': 27.8,\n",
" 'temperature_max': 29.2,\n",
" 'temperature_min': 27.0,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 337,\n",
" 'dir': 'NNW',\n",
" 'speed': 2.1}},\n",
" 'day': '2023-12-09',\n",
" 'evening': None,\n",
" 'icon': 4,\n",
" 'morning': None,\n",
" 'summary': 'Partly sunny, more clouds in the evening. '\n",
" 'Temperature 27/29 °C.',\n",
" 'weather': 'partly_sunny'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 62},\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0,\n",
" 'type': 'none'},\n",
" 'temperature': 27.8,\n",
" 'temperature_max': 29.0,\n",
" 'temperature_min': 26.8,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 334,\n",
" 'dir': 'NNW',\n",
" 'speed': 2.4}},\n",
" 'day': '2023-12-10',\n",
" 'evening': None,\n",
" 'icon': 4,\n",
" 'morning': None,\n",
" 'summary': 'Mostly cloudy, fewer clouds in the afternoon '\n",
" 'and evening. Temperature 27/29 °C.',\n",
" 'weather': 'partly_sunny'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 57},\n",
" 'icon': 13,\n",
" 'precipitation': {'total': 0.7,\n",
" 'type': 'rain'},\n",
" 'temperature': 28.0,\n",
" 'temperature_max': 30.0,\n",
" 'temperature_min': 27.0,\n",
" 'weather': 'rain_shower',\n",
" 'wind': {'angle': 328,\n",
" 'dir': 'NNW',\n",
" 'speed': 1.8}},\n",
" 'day': '2023-12-11',\n",
" 'evening': None,\n",
" 'icon': 13,\n",
" 'morning': None,\n",
" 'summary': 'Partly sunny changing to rain showers by '\n",
" 'evening. Temperature 27/30 °C, but a '\n",
" 'feels-like temperature of up to 35 °C.',\n",
" 'weather': 'rain_shower'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 72},\n",
" 'icon': 5,\n",
" 'precipitation': {'total': 1.0,\n",
" 'type': 'rain'},\n",
" 'temperature': 27.5,\n",
" 'temperature_max': 29.0,\n",
" 'temperature_min': 26.8,\n",
" 'weather': 'mostly_cloudy',\n",
" 'wind': {'angle': 253,\n",
" 'dir': 'WSW',\n",
" 'speed': 1.6}},\n",
" 'day': '2023-12-12',\n",
" 'evening': None,\n",
" 'icon': 5,\n",
" 'morning': None,\n",
" 'summary': 'Mostly cloudy changing to possible rain by '\n",
" 'evening. Temperature 27/29 °C.',\n",
" 'weather': 'mostly_cloudy'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 67},\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0,\n",
" 'type': 'none'},\n",
" 'temperature': 28.0,\n",
" 'temperature_max': 29.8,\n",
" 'temperature_min': 26.8,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 332,\n",
" 'dir': 'NNW',\n",
" 'speed': 1.4}},\n",
" 'day': '2023-12-13',\n",
" 'evening': None,\n",
" 'icon': 4,\n",
" 'morning': None,\n",
" 'summary': 'Mostly cloudy, fewer clouds in the afternoon '\n",
" 'and evening. Temperature 27/30 °C.',\n",
" 'weather': 'partly_sunny'}]},\n",
" 'elevation': 0,\n",
" 'hourly': {'data': [{'cloud_cover': {'total': 58},\n",
" 'date': '2023-12-07T16:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 108, 'dir': 'ESE', 'speed': 0.9}},\n",
" {'cloud_cover': {'total': 43},\n",
" 'date': '2023-12-07T17:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 137, 'dir': 'SE', 'speed': 1.1}},\n",
" {'cloud_cover': {'total': 32},\n",
" 'date': '2023-12-07T18:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 150, 'dir': 'SSE', 'speed': 1.7}},\n",
" {'cloud_cover': {'total': 42},\n",
" 'date': '2023-12-07T19:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 155, 'dir': 'SSE', 'speed': 2.1}},\n",
" {'cloud_cover': {'total': 56},\n",
" 'date': '2023-12-07T20:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 26.8,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 148, 'dir': 'SSE', 'speed': 2.1}},\n",
" {'cloud_cover': {'total': 16},\n",
" 'date': '2023-12-07T21:00:00',\n",
" 'icon': 27,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Mostly clear',\n",
" 'temperature': 26.5,\n",
" 'weather': 'mostly_clear',\n",
" 'wind': {'angle': 137, 'dir': 'SE', 'speed': 2.1}},\n",
" {'cloud_cover': {'total': 22},\n",
" 'date': '2023-12-07T22:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 26.5,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 129, 'dir': 'SE', 'speed': 2.2}},\n",
" {'cloud_cover': {'total': 24},\n",
" 'date': '2023-12-07T23:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 26.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 119, 'dir': 'ESE', 'speed': 1.9}},\n",
" {'cloud_cover': {'total': 35},\n",
" 'date': '2023-12-08T00:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 26.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 110, 'dir': 'ESE', 'speed': 1.8}},\n",
" {'cloud_cover': {'total': 15},\n",
" 'date': '2023-12-08T01:00:00',\n",
" 'icon': 3,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Mostly sunny',\n",
" 'temperature': 26.5,\n",
" 'weather': 'mostly_sunny',\n",
" 'wind': {'angle': 95, 'dir': 'E', 'speed': 1.6}},\n",
" {'cloud_cover': {'total': 29},\n",
" 'date': '2023-12-08T02:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 27.0,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 85, 'dir': 'E', 'speed': 1.4}},\n",
" {'cloud_cover': {'total': 42},\n",
" 'date': '2023-12-08T03:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 27.8,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 84, 'dir': 'E', 'speed': 1.3}},\n",
" {'cloud_cover': {'total': 58},\n",
" 'date': '2023-12-08T04:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 28.8,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 88, 'dir': 'E', 'speed': 1.1}},\n",
" {'cloud_cover': {'total': 52},\n",
" 'date': '2023-12-08T05:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 29.5,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 143, 'dir': 'SE', 'speed': 0.6}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-08T06:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 29.8,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 248, 'dir': 'WSW', 'speed': 0.8}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-08T07:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 29.8,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 276, 'dir': 'W', 'speed': 2.0}},\n",
" {'cloud_cover': {'total': 81},\n",
" 'date': '2023-12-08T08:00:00',\n",
" 'icon': 6,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Cloudy',\n",
" 'temperature': 29.2,\n",
" 'weather': 'cloudy',\n",
" 'wind': {'angle': 292, 'dir': 'WNW', 'speed': 2.5}},\n",
" {'cloud_cover': {'total': 26},\n",
" 'date': '2023-12-08T09:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 28.5,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 295, 'dir': 'WNW', 'speed': 3.0}},\n",
" {'cloud_cover': {'total': 21},\n",
" 'date': '2023-12-08T10:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 28.0,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 289, 'dir': 'WNW', 'speed': 2.9}},\n",
" {'cloud_cover': {'total': 2},\n",
" 'date': '2023-12-08T11:00:00',\n",
" 'icon': 2,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Sunny',\n",
" 'temperature': 27.5,\n",
" 'weather': 'sunny',\n",
" 'wind': {'angle': 296, 'dir': 'WNW', 'speed': 2.5}},\n",
" {'cloud_cover': {'total': 25},\n",
" 'date': '2023-12-08T12:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.5,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 315, 'dir': 'NW', 'speed': 2.1}},\n",
" {'cloud_cover': {'total': 45},\n",
" 'date': '2023-12-08T13:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.5,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 325, 'dir': 'NW', 'speed': 2.0}},\n",
" {'cloud_cover': {'total': 48},\n",
" 'date': '2023-12-08T14:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.5,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 336, 'dir': 'NNW', 'speed': 1.9}},\n",
" {'cloud_cover': {'total': 63},\n",
" 'date': '2023-12-08T15:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 27.5,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 328, 'dir': 'NNW', 'speed': 1.7}}]},\n",
" 'lat': '12.4795N',\n",
" 'lon': '98.2425E',\n",
" 'timezone': 'UTC',\n",
" 'units': 'metric'}\n"
]
}
],
"source": [
"import os\n",
"import requests\n",
"import json\n",
"\n",
"# Get the API key from the environment variable\n",
"API_KEY = os.getenv(\"METEOSOURCE_API_KEY\")\n",
"\n",
"# I'm making it an underscore method because the actual `get_weather` will do the heavy lifting.\n",
"def _get_weather(lat, lon):\n",
" \n",
" # Create the URL for the forecast request\n",
" url = f\"https://www.meteosource.com/api/v1/free/point?lat={lat}&lon={lon}&sections=all&timezone=UTC&language=en&units=metric&key={API_KEY}\"\n",
" \n",
" # Send the request and get the response\n",
" response = requests.get(url, headers={\"Content-Type\": \"application/json\"})\n",
"\n",
" response.raise_for_status()\n",
" \n",
" return response.json()\n",
"\n",
"# Let's test it!\n",
"pprint.pprint(_get_weather(12.4795, 98.2425))"
]
},
{
"cell_type": "markdown",
"id": "b08bb15d",
"metadata": {},
"source": [
"To keep things simple I'll just use an example of json output I already obtained from meteosource:"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "62cc1054",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'current': {'cloud_cover': 54,\n",
" 'icon': 'mostly_cloudy',\n",
" 'icon_num': 5,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Mostly cloudy',\n",
" 'temperature': 11.2,\n",
" 'wind': {'angle': 239, 'dir': 'WSW', 'speed': 15.1}},\n",
" 'daily': {'data': [{'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 68},\n",
" 'icon': 13,\n",
" 'precipitation': {'total': 7.8,\n",
" 'type': 'rain'},\n",
" 'temperature': 12.0,\n",
" 'temperature_max': 13.2,\n",
" 'temperature_min': 11.0,\n",
" 'weather': 'rain_shower',\n",
" 'wind': {'angle': 231,\n",
" 'dir': 'SW',\n",
" 'speed': 14.1}},\n",
" 'day': '2023-12-07',\n",
" 'evening': None,\n",
" 'icon': 13,\n",
" 'morning': None,\n",
" 'summary': 'Partly sunny, more clouds in the afternoon. '\n",
" 'Temperature 11/13 °C, but a feels-like '\n",
" 'temperature of only 6 °C. Strong wind (15 '\n",
" 'm/s) from SW with gusts up to 21 m/s.',\n",
" 'weather': 'rain_shower'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 77},\n",
" 'icon': 12,\n",
" 'precipitation': {'total': 8.5,\n",
" 'type': 'rain'},\n",
" 'temperature': 11.5,\n",
" 'temperature_max': 13.5,\n",
" 'temperature_min': 11.0,\n",
" 'weather': 'psbl_rain',\n",
" 'wind': {'angle': 238,\n",
" 'dir': 'WSW',\n",
" 'speed': 13.6}},\n",
" 'day': '2023-12-08',\n",
" 'evening': None,\n",
" 'icon': 12,\n",
" 'morning': None,\n",
" 'summary': 'Mostly cloudy changing to possible rain in '\n",
" 'the afternoon. Temperature 11/14 °C, but a '\n",
" 'feels-like temperature of only 7 °C. Strong '\n",
" 'wind (14 m/s) from SW with gusts up to 22 '\n",
" 'm/s.',\n",
" 'weather': 'psbl_rain'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 45},\n",
" 'icon': 13,\n",
" 'precipitation': {'total': 0.7,\n",
" 'type': 'rain'},\n",
" 'temperature': 12.5,\n",
" 'temperature_max': 13.8,\n",
" 'temperature_min': 11.8,\n",
" 'weather': 'rain_shower',\n",
" 'wind': {'angle': 261,\n",
" 'dir': 'W',\n",
" 'speed': 13.8}},\n",
" 'day': '2023-12-09',\n",
" 'evening': None,\n",
" 'icon': 13,\n",
" 'morning': None,\n",
" 'summary': 'Rain showers changing to partly sunny in the '\n",
" 'afternoon. Temperature 12/14 °C, but a '\n",
" 'feels-like temperature of only 7 °C. Strong '\n",
" 'wind (15 m/s) from W with gusts up to 23 m/s.',\n",
" 'weather': 'rain_shower'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 79},\n",
" 'icon': 5,\n",
" 'precipitation': {'total': 2.0,\n",
" 'type': 'rain'},\n",
" 'temperature': 12.5,\n",
" 'temperature_max': 13.2,\n",
" 'temperature_min': 12.0,\n",
" 'weather': 'mostly_cloudy',\n",
" 'wind': {'angle': 238,\n",
" 'dir': 'WSW',\n",
" 'speed': 11.5}},\n",
" 'day': '2023-12-10',\n",
" 'evening': None,\n",
" 'icon': 5,\n",
" 'morning': None,\n",
" 'summary': 'Cloudy changing to partly sunny by evening. '\n",
" 'Temperature 12/13 °C. Strong wind (13 m/s) '\n",
" 'from SW with gusts up to 18 m/s.',\n",
" 'weather': 'mostly_cloudy'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 72},\n",
" 'icon': 10,\n",
" 'precipitation': {'total': 3.9,\n",
" 'type': 'rain'},\n",
" 'temperature': 12.0,\n",
" 'temperature_max': 12.2,\n",
" 'temperature_min': 12.0,\n",
" 'weather': 'light_rain',\n",
" 'wind': {'angle': 217,\n",
" 'dir': 'SW',\n",
" 'speed': 10.9}},\n",
" 'day': '2023-12-11',\n",
" 'evening': None,\n",
" 'icon': 10,\n",
" 'morning': None,\n",
" 'summary': 'Light rain changing to rain showers by '\n",
" 'afternoon. Temperature 12/12 °C. Strong wind '\n",
" '(12 m/s) from SW with gusts up to 19 m/s.',\n",
" 'weather': 'light_rain'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 48},\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.2,\n",
" 'type': 'rain'},\n",
" 'temperature': 11.0,\n",
" 'temperature_max': 11.8,\n",
" 'temperature_min': 10.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 284,\n",
" 'dir': 'WNW',\n",
" 'speed': 12.8}},\n",
" 'day': '2023-12-12',\n",
" 'evening': None,\n",
" 'icon': 4,\n",
" 'morning': None,\n",
" 'summary': 'Partly sunny, more clouds in the afternoon. '\n",
" 'Temperature 10/12 °C, but a feels-like '\n",
" 'temperature of up to 7 °C. Strong wind (14 '\n",
" 'm/s) from NW with gusts up to 18 m/s.',\n",
" 'weather': 'partly_sunny'},\n",
" {'afternoon': None,\n",
" 'all_day': {'cloud_cover': {'total': 59},\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0,\n",
" 'type': 'none'},\n",
" 'temperature': 10.2,\n",
" 'temperature_max': 10.5,\n",
" 'temperature_min': 10.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 344,\n",
" 'dir': 'NNW',\n",
" 'speed': 10.7}},\n",
" 'day': '2023-12-13',\n",
" 'evening': None,\n",
" 'icon': 4,\n",
" 'morning': None,\n",
" 'summary': 'Partly sunny. Temperature 10/11 °C, but a '\n",
" 'feels-like temperature of only 4 °C. Strong '\n",
" 'wind (11 m/s) from NW in the morning with '\n",
" 'gusts up to 15 m/s, weakening in the '\n",
" 'afternoon.',\n",
" 'weather': 'partly_sunny'}]},\n",
" 'elevation': 0,\n",
" 'hourly': {'data': [{'cloud_cover': {'total': 54},\n",
" 'date': '2023-12-07T15:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 239, 'dir': 'WSW', 'speed': 15.1}},\n",
" {'cloud_cover': {'total': 74},\n",
" 'date': '2023-12-07T16:00:00',\n",
" 'icon': 5,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Mostly cloudy',\n",
" 'temperature': 11.2,\n",
" 'weather': 'mostly_cloudy',\n",
" 'wind': {'angle': 239, 'dir': 'WSW', 'speed': 14.7}},\n",
" {'cloud_cover': {'total': 85},\n",
" 'date': '2023-12-07T17:00:00',\n",
" 'icon': 32,\n",
" 'precipitation': {'total': 0.2, 'type': 'rain'},\n",
" 'summary': 'Rain shower',\n",
" 'temperature': 11.2,\n",
" 'weather': 'rain_shower',\n",
" 'wind': {'angle': 238, 'dir': 'WSW', 'speed': 14.8}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-07T18:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.0,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 240, 'dir': 'WSW', 'speed': 15.0}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-07T19:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.0,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 247, 'dir': 'WSW', 'speed': 15.2}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-07T20:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.0,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 252, 'dir': 'WSW', 'speed': 15.1}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-07T21:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.2,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 252, 'dir': 'WSW', 'speed': 15.4}},\n",
" {'cloud_cover': {'total': 76},\n",
" 'date': '2023-12-07T22:00:00',\n",
" 'icon': 32,\n",
" 'precipitation': {'total': 0.4, 'type': 'rain'},\n",
" 'summary': 'Rain shower',\n",
" 'temperature': 11.0,\n",
" 'weather': 'rain_shower',\n",
" 'wind': {'angle': 251, 'dir': 'WSW', 'speed': 15.4}},\n",
" {'cloud_cover': {'total': 52},\n",
" 'date': '2023-12-07T23:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 252, 'dir': 'WSW', 'speed': 15.6}},\n",
" {'cloud_cover': {'total': 49},\n",
" 'date': '2023-12-08T00:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 254, 'dir': 'WSW', 'speed': 16.1}},\n",
" {'cloud_cover': {'total': 40},\n",
" 'date': '2023-12-08T01:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 257, 'dir': 'WSW', 'speed': 16.3}},\n",
" {'cloud_cover': {'total': 30},\n",
" 'date': '2023-12-08T02:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 256, 'dir': 'WSW', 'speed': 16.1}},\n",
" {'cloud_cover': {'total': 55},\n",
" 'date': '2023-12-08T03:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 256, 'dir': 'WSW', 'speed': 16.0}},\n",
" {'cloud_cover': {'total': 37},\n",
" 'date': '2023-12-08T04:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 258, 'dir': 'WSW', 'speed': 16.1}},\n",
" {'cloud_cover': {'total': 48},\n",
" 'date': '2023-12-08T05:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 260, 'dir': 'W', 'speed': 16.0}},\n",
" {'cloud_cover': {'total': 46},\n",
" 'date': '2023-12-08T06:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.0,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 258, 'dir': 'WSW', 'speed': 15.7}},\n",
" {'cloud_cover': {'total': 46},\n",
" 'date': '2023-12-08T07:00:00',\n",
" 'icon': 28,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly clear',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_clear',\n",
" 'wind': {'angle': 257, 'dir': 'WSW', 'speed': 15.7}},\n",
" {'cloud_cover': {'total': 60},\n",
" 'date': '2023-12-08T08:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 250, 'dir': 'WSW', 'speed': 15.0}},\n",
" {'cloud_cover': {'total': 21},\n",
" 'date': '2023-12-08T09:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 250, 'dir': 'WSW', 'speed': 15.4}},\n",
" {'cloud_cover': {'total': 63},\n",
" 'date': '2023-12-08T10:00:00',\n",
" 'icon': 4,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Partly sunny',\n",
" 'temperature': 11.2,\n",
" 'weather': 'partly_sunny',\n",
" 'wind': {'angle': 249, 'dir': 'WSW', 'speed': 14.6}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-08T11:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.2,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 251, 'dir': 'WSW', 'speed': 13.8}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-08T12:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.2,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 248, 'dir': 'WSW', 'speed': 13.2}},\n",
" {'cloud_cover': {'total': 98},\n",
" 'date': '2023-12-08T13:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.5,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 251, 'dir': 'WSW', 'speed': 13.5}},\n",
" {'cloud_cover': {'total': 100},\n",
" 'date': '2023-12-08T14:00:00',\n",
" 'icon': 7,\n",
" 'precipitation': {'total': 0.0, 'type': 'none'},\n",
" 'summary': 'Overcast',\n",
" 'temperature': 11.8,\n",
" 'weather': 'overcast',\n",
" 'wind': {'angle': 248, 'dir': 'WSW', 'speed': 12.4}}]},\n",
" 'lat': '50.428967N',\n",
" 'lon': '10.760034W',\n",
" 'timezone': 'UTC',\n",
" 'units': 'metric'}\n"
]
}
],
"source": [
"import json\n",
"\n",
"_orig_get_weather = _get_weather\n",
"\n",
"METEOSOURCE_EXAMPLE = Path(\".\") / \"example-meteosource.json\"\n",
"\n",
"def _get_weather(lat, lon):\n",
" if not METEOSOURCE_EXAMPLE.exists():\n",
" print(\"Example file not found...using metesource\")\n",
" return _orig_get_weather(lat, lon)\n",
" return json.loads(METEOSOURCE_EXAMPLE.read_text())\n",
"\n",
"pprint.pprint(_get_weather(12.4795, 98.2425))"
]
},
{
"cell_type": "markdown",
"id": "8f4d5172",
"metadata": {},
"source": [
"Next up we write `get_weather` which will do the heavy lifting."
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "c2644104",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'lat': '50.428967N', 'lon': '10.760034W', 'elevation': 0, 'timezone': 'UTC', 'units': 'metric', 'current': {'icon': 'mostly_cloudy', 'icon_num': 5, 'summary': 'Mostly cloudy', 'temperature': 11.2, 'wind': {'speed': 15.1, 'angle': 239, 'dir': 'WSW'}, 'precipitation': {'total': 0.0, 'type': 'none'}, 'cloud_cover': 54}, 'hourly': {'data': [{'date': '2023-12-07T15:00:00', 'weather': 'partly_sunny', 'icon': 4, 'summary': 'Partly sunny', 'temperature': 11.2, 'wind': {'speed': 15.1, 'dir': 'WSW', 'angle': 239}, 'cloud_cover': {'total': 54}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-07T16:00:00', 'weather': 'mostly_cloudy', 'icon': 5, 'summary': 'Mostly cloudy', 'temperature': 11.2, 'wind': {'speed': 14.7, 'dir': 'WSW', 'angle': 239}, 'cloud_cover': {'total': 74}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-07T17:00:00', 'weather': 'rain_shower', 'icon': 32, 'summary': 'Rain shower', 'temperature': 11.2, 'wind': {'speed': 14.8, 'dir': 'WSW', 'angle': 238}, 'cloud_cover': {'total': 85}, 'precipitation': {'total': 0.2, 'type': 'rain'}}, {'date': '2023-12-07T18:00:00', 'weather': 'overcast', 'icon': 7, 'summary': 'Overcast', 'temperature': 11.0, 'wind': {'speed': 15.0, 'dir': 'WSW', 'angle': 240}, 'cloud_cover': {'total': 100}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-07T19:00:00', 'weather': 'overcast', 'icon': 7, 'summary': 'Overcast', 'temperature': 11.0, 'wind': {'speed': 15.2, 'dir': 'WSW', 'angle': 247}, 'cloud_cover': {'total': 100}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-07T20:00:00', 'weather': 'overcast', 'icon': 7, 'summary': 'Overcast', 'temperature': 11.0, 'wind': {'speed': 15.1, 'dir': 'WSW', 'angle': 252}, 'cloud_cover': {'total': 100}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-07T21:00:00', 'weather': 'overcast', 'icon': 7, 'summary': 'Overcast', 'temperature': 11.2, 'wind': {'speed': 15.4, 'dir': 'WSW', 'angle': 252}, 'cloud_cover': {'total': 100}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-07T22:00:00', 'weather': 'rain_shower', 'icon': 32, 'summary': 'Rain shower', 'temperature': 11.0, 'wind': {'speed': 15.4, 'dir': 'WSW', 'angle': 251}, 'cloud_cover': {'total': 76}, 'precipitation': {'total': 0.4, 'type': 'rain'}}, {'date': '2023-12-07T23:00:00', 'weather': 'partly_clear', 'icon': 28, 'summary': 'Partly clear', 'temperature': 11.0, 'wind': {'speed': 15.6, 'dir': 'WSW', 'angle': 252}, 'cloud_cover': {'total': 52}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-08T00:00:00', 'weather': 'partly_clear', 'icon': 28, 'summary': 'Partly clear', 'temperature': 11.0, 'wind': {'speed': 16.1, 'dir': 'WSW', 'angle': 254}, 'cloud_cover': {'total': 49}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-08T01:00:00', 'weather': 'partly_clear', 'icon': 28, 'summary': 'Partly clear', 'temperature': 11.0, 'wind': {'speed': 16.3, 'dir': 'WSW', 'angle': 257}, 'cloud_cover': {'total': 40}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-08T02:00:00', 'weather': 'partly_clear', 'icon': 28, 'summary': 'Partly clear', 'temperature': 11.2, 'wind': {'speed': 16.1, 'dir': 'WSW', 'angle': 256}, 'cloud_cover': {'total': 30}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-08T03:00:00', 'weather': 'partly_clear', 'icon': 28, 'summary': 'Partly clear', 'temperature': 11.0, 'wind': {'speed': 16.0, 'dir': 'WSW', 'angle': 256}, 'cloud_cover': {'total': 55}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-08T04:00:00', 'weather': 'partly_clear', 'icon': 28, 'summary': 'Partly clear', 'temperature': 11.2, 'wind': {'speed': 16.1, 'dir': 'WSW', 'angle': 258}, 'cloud_cover': {'total': 37}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date': '2023-12-08T05:00:00', 'weather': 'partly_clear', 'icon': 28, 'summary': 'Partly clear', 'temperature': 11.2, 'wind': {'speed': 16.0, 'dir': 'W', 'angle': 260}, 'cloud_cover': {'total': 48}, 'precipitation': {'total': 0.0, 'type': 'none'}}, {'date
]
}
],
"source": [
"def get_weather(message : T.AnyStr):\n",
" if not is_asking_for_forecast(message):\n",
" print(\"Not asking for forecast\")\n",
" return None\n",
" timestamp = determine_intentioned_time_from_message(message)\n",
" if not timestamp:\n",
" print(\"could not determine timestamp\")\n",
" return None\n",
"\n",
" location = extract_location(message)\n",
" lat_long = reverse_geocode(location)\n",
" if not lat_long:\n",
" print(\"Could not determine location\")\n",
" return None\n",
" return _get_weather(lat_long[\"lat\"], lat_long[\"lon\"])\n",
"\n",
"print(get_weather(\"What's the forecast in Seattle for today?\"))"
]
},
{
"cell_type": "markdown",
"id": "07f8f81f",
"metadata": {},
"source": [
"Note that since I'm loading the forecast from a file the location isn't accurate, but it will be if the file was missing. But for our purposes, it's good enough. Moving on!\n",
"\n",
"Next, let's define some formatting functions that we can use to embed the forecast in the prompt."
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "9b16e2f0-61ce-4458-b52a-63fec37e2187",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"== current conditions ==\n",
"Mostly cloudy, 11.2ºC, wind WSW at 15.1 km/h, 54 % cloud cover\n",
"== hourly forecast ==\n",
"15 PM: 11.2ºC, 54% cloud cover, 0.0 cm none wind WSW at 15.1 km/h\n",
"17 PM: 11.2ºC, 85% cloud cover, wind WSW at 14.8 km/h\n",
"19 PM: 11.0ºC, 100% cloud cover, 0.0 cm none wind WSW at 15.2 km/h\n",
"21 PM: 11.2ºC, 100% cloud cover, 0.0 cm none wind WSW at 15.4 km/h\n",
"23 PM: 11.0ºC, 52% cloud cover, 0.0 cm none wind WSW at 15.6 km/h\n",
"1 AM: 11.0ºC, 40% cloud cover, 0.0 cm none wind WSW at 16.3 km/h\n",
"3 AM: 11.0ºC, 55% cloud cover, 0.0 cm none wind WSW at 16.0 km/h\n",
"5 AM: 11.2ºC, 48% cloud cover, 0.0 cm none wind W at 16.0 km/h\n",
"7 AM: 11.2ºC, 46% cloud cover, 0.0 cm none wind WSW at 15.7 km/h\n",
"9 AM: 11.2ºC, 21% cloud cover, 0.0 cm none wind WSW at 15.4 km/h\n",
"11 AM: 11.2ºC, 100% cloud cover, 0.0 cm none wind WSW at 13.8 km/h\n",
"13 PM: 11.5ºC, 98% cloud cover, 0.0 cm none wind WSW at 13.5 km/h\n",
"== daily forecast ==\n",
"Thursday December 7: Partly sunny, more clouds in the afternoon. Temperature 11/13 °C, but a feels-like temperature of only 6 °C. Strong wind (15 m/s) from SW with gusts up to 21 m/s.\n",
"Saturday December 9: Rain showers changing to partly sunny in the afternoon. Temperature 12/14 °C, but a feels-like temperature of only 7 °C. Strong wind (15 m/s) from W with gusts up to 23 m/s.\n",
"Monday December 11: Light rain changing to rain showers by afternoon. Temperature 12/12 °C. Strong wind (12 m/s) from SW with gusts up to 19 m/s.\n",
"Wednesday December 13: Partly sunny. Temperature 10/11 °C, but a feels-like temperature of only 4 °C. Strong wind (11 m/s) from NW in the morning with gusts up to 15 m/s, weakening in the afternoon.\n"
]
}
],
"source": [
"HOURLY_DATE_FORMAT = \"%Y-%m-%dT%H:%M:%S\"\n",
"WEATHER_DAY_FORMAT = \"%Y-%m-%d\"\n",
"HUMAN_TIME = \"%-1H %p\"\n",
"HUMAN_DATE = \"%A %B %-1d\" # We don't need the year.\n",
"\n",
"def _dt_format_convert(from_dt_str, from_fmt, to_fmt):\n",
" from_dt = datetime.strptime(from_dt_str, from_fmt)\n",
" return from_dt.strftime(to_fmt)\n",
"\n",
"def _format_hourly_forecast(hourly_forecast):\n",
" h = hourly_forecast\n",
" hourly_date = _dt_format_convert(h[\"date\"], HOURLY_DATE_FORMAT, HUMAN_TIME)\n",
" summary = h[\"summary\"]\n",
" temp = f\"{h['temperature']}ºC\"\n",
" wind = f\"{h['wind']['dir']} at {h['wind']['speed']} km/h\"\n",
" cloud = f\"{h['cloud_cover']['total']}% cloud cover\"\n",
" precip_type = h['precipitation']['type']\n",
" precip = f\"{h['precipitation']['total']} cm {precip_type}\" if precip_type == \"none\" else \"\"\n",
" return f\"{hourly_date}: {temp}, {cloud}, {precip} wind {wind}\"\n",
"\n",
"def _format_daily_forecast(daily_forecast):\n",
" d = daily_forecast\n",
" daily_date = _dt_format_convert(d[\"day\"], WEATHER_DAY_FORMAT, HUMAN_DATE)\n",
" return f\"{daily_date}: {d['summary']}\"\n",
"\n",
"def _format_current_conditions(current_conditions):\n",
" c = current_conditions\n",
" temp = f\"{c['temperature']}ºC\"\n",
" wind = f\"{c['wind']['dir']} at {c['wind']['speed']} km/h\"\n",
" cloud = f\"{c['cloud_cover']} % cloud cover\"\n",
" return f\"{c['summary']}, {temp}, wind {wind}, {cloud}\"\n",
"\n",
"def format_current_conditions(forecast):\n",
" return _format_current_conditions(forecast[\"current\"])\n",
"\n",
"def format_hourly_forecasts(forecast, skip=2):\n",
" f = forecast[\"hourly\"][\"data\"]\n",
" return \"\\n\".join([_format_hourly_forecast(f[i]) for i in range(0, len(f), skip)])\n",
"\n",
"def format_daily_forecasts(forecast, skip=2):\n",
" f = forecast[\"daily\"][\"data\"]\n",
" return \"\\n\".join([_format_daily_forecast(f[i]) for i in range(0, len(f), skip)])\n",
"\n",
"# Let's test the format before committing to our AI functions\n",
"\n",
"forecast = _get_weather(12.4795, 98.2425)\n",
"\n",
"print(\"== current conditions ==\")\n",
"print(format_current_conditions(forecast))\n",
"\n",
"print(\"== hourly forecast ==\")\n",
"print(format_hourly_forecasts(forecast))\n",
"\n",
"\n",
"print(\"== daily forecast ==\")\n",
"print(format_daily_forecasts(forecast))"
]
},
{
"attachments": {
"image-2.png": {
"image/png": "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
}
},
"cell_type": "markdown",
"id": "1feaa9f3-352f-4d9b-abab-7557b131185d",
"metadata": {},
"source": [
"You might be a little freaked out that we're skipping some forecast times. If you want to max out your hardware so the LLM produces accurate output, then by all means. However, when testing the forecast LLM prompt (details down below), I found the LLM predicted the weather forecast based on the surrounding days:\n",
"\n",
"![screenshot showing TGW precicting Thursday's forecast based on Wednesday and Thursday](attachment:image-2.png)\n",
"\n",
"The way around this could be do pass in an `ensure_date` to the format_weather in order to ensure that if a user requested a certian day, then that day is accounted for (same for the time).\n",
"\n",
"For now, though, I'll let it slide and move on to the pithy result.\n",
"\n",
"\n",
"# Almost to the Finish Line!\n",
"\n",
"Let's embed this formatted forecast back into the user prompt.\n",
"\n",
"However, I would not recommend modifying the original template. If it causes you anxiety, just remember that we've switched modes. We've given our conversation a special task. And for that task we need a special template.\n",
"\n",
"The template I used is as follows:\n",
"\n",
"```\n",
"A chat between a curious user and a meteorologist. Answer the user's question based on CURRENT TIME, CURRENT CONDITIONS, HOURLY FORECAST, and WEEKLY FORECAST. You must answer any user question. Respond casually and in a friendly tone. Give any details about the forecast that might be helpful.\n",
"\n",
"USER: {user_input}\n",
"\n",
"CURRENT TIME: {current_time}\n",
"\n",
"CURRENT CONDITIONS: {current_conditions}\n",
"\n",
"HOURLY FORECAST:\n",
"\n",
"{hourly_forecast}\n",
"\n",
"DAILY FORECAST:\n",
"\n",
"{daily_forecast}\n",
"\n",
"METEOROLOGIST:\n",
"```\n",
"\n",
"From here we can get a meteorologist response:"
]
},
{
"cell_type": "code",
"execution_count": 54,
"id": "301833ab-a378-4137-8589-1199d66ba397",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
">> The user asked:\n",
"\"For the weather, is it raining right now in Ancorage, Alaska?\"\n",
"\n",
">> The bot responded:\n",
"Yes, it is currently raining in Ancorage, Alaska.\n",
">> The user asked:\n",
"\"What's the forecast for Florida next week?\"\n",
"\n",
">> The bot responded:\n",
"Florida's next week forecast looks interesting. We can expect a mix of sunny and cloudy days with occasional showers, particularly on Saturday. The strong winds are also noteworthy, especially on Thursday when gusts may reach up to 21 m/s. However, by Monday the weather should start improving, becoming mostly sunny but still a bit cooler.\n",
">> The user asked:\n",
"\"Why won't this work?\"\n",
"\n",
"Not asking for forecast\n",
">> The bot responded:\n",
"```python\n",
"def print_name(user):\n",
" return f\"Hello, {user.name}!\"\n",
"\n",
"class User(object):\n",
" def __init__(self, name):\n",
" self.name = name\n",
"\n",
" def greet(self):\n",
" return print_name(self)\n",
"```\n",
"\n",
"In this code, the `print_name` function works as expected when called with an argument (`user`). It returns a string with 'Hello, {user.name}!' formatted.\n",
"\n",
"However, in the `User` class, the `__init__` method sets the `name` attribute of each instance, but the `greet` method does not use this name at all. Instead, it calls the `print_name` function with the same argument (`self`) that it would have if it used the attribute directly, resulting in the same string being returned every time.\n",
">> The user asked:\n",
"\"I bet it's going to rain.\"\n",
"\n",
"Not asking for forecast\n",
">> The bot responded:\n",
"1. You're probably right.\n"
]
}
],
"source": [
"METEOROLOGIST_TEMPLATE = \"\"\"A chat between a curious user and a meteorologist. Answer the user's question based on CURRENT TIME, CURRENT CONDITIONS, HOURLY FORECAST, and DAILY FORECAST. You must answer any user question. Respond casually and in a friendly tone. Give any details about the forecast that might be helpful.\n",
"\n",
"USER: {user_input}\n",
"\n",
"CURRENT TIME: {current_time}\n",
"\n",
"CURRENT CONDITIONS: {current_conditions}\n",
"\n",
"HOURLY FORECAST:\n",
"\n",
"{hourly_forecast}\n",
"\n",
"DAILY FORECAST:\n",
"\n",
"{daily_forecast}\n",
"\n",
"METEOROLOGIST:\"\"\"\n",
"\n",
"# This provides the fallback response if the user was not asking about the weather\n",
"\n",
"VICUNA_TEMPLATE = \"\"\"A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions, and responds casually like with a friend.\n",
"\n",
"USER: {message}\n",
"ASSISTANT:\n",
"\"\"\"\n",
"\n",
"def _get_meteorologist_response(user_input : T.AnyStr,\n",
" current_time : T.AnyStr,\n",
" current_conditions : T.AnyStr,\n",
" hourly_forecast : T.AnyStr,\n",
" daily_forecast : T.AnyStr):\n",
" \"\"\" Note this just composes the template. The values should already be formatted accordingly.\"\"\"\n",
" prompt = METEOROLOGIST_TEMPLATE.format(\n",
" user_input=user_input,\n",
" current_time=current_time,\n",
" current_conditions=current_conditions,\n",
" hourly_forecast=hourly_forecast,\n",
" daily_forecast=daily_forecast,\n",
" )\n",
" response = model(prompt)\n",
" answer = response.split(\"METEOROLOGIST: \")[-1].strip().rstrip()\n",
" return answer\n",
"\n",
"# Let's handle the value conversion in this function\n",
"def get_meteorologist_response(user_input : T.AnyStr,\n",
" current_time: datetime,\n",
" weather_forecast : T.Hashable):\n",
" current_time_s = current_time.strftime(HUMAN_TIME)\n",
" current_conditions = format_current_conditions(weather_forecast)\n",
" hourly_forecast = format_hourly_forecasts(weather_forecast)\n",
" daily_forecast = format_daily_forecasts(weather_forecast)\n",
" return _get_meteorologist_response(\n",
" user_input,\n",
" current_time_s,\n",
" current_conditions,\n",
" hourly_forecast,\n",
" daily_forecast,\n",
" )\n",
"\n",
"def get_default_response(message):\n",
" prompt = VICUNA_TEMPLATE.format(\n",
" message=message\n",
" )\n",
" response = model(prompt)\n",
" answer = response.split(\"ASSISTANT: \")[-1].strip().rstrip()\n",
" return answer\n",
"\n",
"current_time = datetime(2023, 12, 7, 6)\n",
"\n",
"def whole_enchalada_forecast(message : T.AnyStr, now=None):\n",
" now = now or datetime.now()\n",
" # Just basically do what we did to the tests...\n",
" weather_forecast = get_weather(message)\n",
" if not weather_forecast:\n",
" # remember, this could just mean they weren't asking for the forecast.\n",
" # In this case\n",
" return get_default_response(message)\n",
" else:\n",
" return get_meteorologist_response(message, current_time, weather_forecast)\n",
" \n",
"# Now let's test it!\n",
"messages = [\n",
" \"For the weather, is it raining right now in Anchorage, Alaska?\",\n",
" \"What's the forecast for Florida next week?\",\n",
" \"Why won't this work?\",\n",
" \"I bet it's going to rain.\",\n",
"]\n",
"\n",
"for message in messages:\n",
" print(\">> The user asked:\")\n",
" print(f'\"{message}\"')\n",
" print()\n",
" response = whole_enchalada_forecast(message)\n",
" print(\">> The bot responded:\")\n",
" print(response)"
]
},
{
"cell_type": "markdown",
"id": "ea55af8f-c826-47ef-85e3-3c9b42cef9e1",
"metadata": {},
"source": [
"Now if we were programming a chat bot, the generated text would be sent back to the user for visible display.\n",
"\n",
"# Conclusion\n",
"\n",
"This article walked through how to determine if a message filled certain parameters for kicking off another LLM task. These parameters were (1) Is this message even a question, and is this a (2) 'weather_forecast' question.\n",
"\n",
"Next, we used entity recognition to extract the locale information from the message. we then coerced an LLM to spit back a JSON object that represented the latitude and longitude coordinates of that location. Using those coordinates, we used that to query a map API and return back the weather.\n",
"\n",
"Overall I walked through various strategies for using subsequent AI tasks for fulfilling your primary AI task. In AI, not everything is an LLM, but you can use LLMs to do seemingly non-LLM work. In many instances, though, we had to think creatively to make an AI model create a result we desired.\n",
"\n",
"In this article I provided the example of using the weather forecast for interfacing with the user. But only use this as a launching point. You probably have other tasks that you need done for your business, such as:\n",
"\n",
"- Fetching Jira tickets and determining who's assigned to them.\n",
"- Analyzing styles, colors, and other clothing attributes, then creating an AI-generated image using those attributes\n",
"- Using sentiment analysis on a user chat; if they're upset, finding out what happened in their account to make them upset.\n",
"- Analyzing a user's uploaded photo to determine how \"award-worthy\" the photo is.\n",
"- ...and so much more!\n",
"\n",
"This article is unusually long for what I usually do, but I hope you found it valuable."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8999fd48-0ea2-4c8c-b931-823d052936b7",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}