diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 55d20255..ff261bad 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
USER vscode
-RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
+RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash
ENV PATH=/home/vscode/.rye/shims:$PATH
RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c8a8a4f7..3b286e5a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Install dependencies
@@ -42,7 +42,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Bootstrap
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index e206653d..85424099 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -21,7 +21,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Publish to PyPI
diff --git a/.inline-snapshot/external/.gitignore b/.inline-snapshot/external/.gitignore
new file mode 100644
index 00000000..45bef68b
--- /dev/null
+++ b/.inline-snapshot/external/.gitignore
@@ -0,0 +1,2 @@
+# ignore all snapshots which are not refered in the source
+*-new.*
diff --git a/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.openai b/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.openai
new file mode 100644
index 00000000..49c6dce9
--- /dev/null
+++ b/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.openai
@@ -0,0 +1,28 @@
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":","},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" request"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":11,"total_tokens":90,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.writer b/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.writer
new file mode 100644
index 00000000..49c6dce9
--- /dev/null
+++ b/.inline-snapshot/external/173417d553406f034f643e5db3f8d591fb691ebac56f5ae39a22cc7d455c5353.writer
@@ -0,0 +1,28 @@
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":","},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" request"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw4IfQfCCrcuybFm41wJyxjbkz7","object":"chat.completion.chunk","created":1727346172,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":11,"total_tokens":90,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.openai b/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.openai
new file mode 100644
index 00000000..87197067
--- /dev/null
+++ b/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.openai
@@ -0,0 +1,22 @@
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_4XzlGBLtUe9dy3GVNV4jhq7h","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"New"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" York"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" City"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[],"usage":{"prompt_tokens":44,"completion_tokens":16,"total_tokens":60,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.writer b/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.writer
new file mode 100644
index 00000000..87197067
--- /dev/null
+++ b/.inline-snapshot/external/2018feb66ae13fcf5333d61b95849decc68d3f63bd38172889367e1afb1e04f7.writer
@@ -0,0 +1,22 @@
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_4XzlGBLtUe9dy3GVNV4jhq7h","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"New"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" York"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" City"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfwERreu9s99xXsVuOWtIB2UOx62","object":"chat.completion.chunk","created":1727346182,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_143bb8492c","choices":[],"usage":{"prompt_tokens":44,"completion_tokens":16,"total_tokens":60,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.openai b/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.openai
new file mode 100644
index 00000000..c3392883
--- /dev/null
+++ b/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.openai
@@ -0,0 +1,10 @@
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"length"}]}
+
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":1,"total_tokens":80,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.writer b/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.writer
new file mode 100644
index 00000000..c3392883
--- /dev/null
+++ b/.inline-snapshot/external/4cc50a6135d254573a502310e6af1246f55edb6ad95fa24059f160996b68866d.writer
@@ -0,0 +1,10 @@
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"length"}]}
+
+data: {"id":"chatcmpl-ABfw3Oqj8RD0z6aJiiX37oTjV2HFh","object":"chat.completion.chunk","created":1727346171,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":1,"total_tokens":80,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.openai b/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.openai
new file mode 100644
index 00000000..47dd7315
--- /dev/null
+++ b/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.openai
@@ -0,0 +1,30 @@
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":{"content":null,"refusal":[]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":{"content":null,"refusal":[{"token":"I'm","logprob":-0.0012038043,"bytes":[73,39,109],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" very"},"logprobs":{"content":null,"refusal":[{"token":" very","logprob":-0.8438816,"bytes":[32,118,101,114,121],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":{"content":null,"refusal":[{"token":" sorry","logprob":-3.4121115e-6,"bytes":[32,115,111,114,114,121],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":","},"logprobs":{"content":null,"refusal":[{"token":",","logprob":-0.000033809047,"bytes":[44],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" but"},"logprobs":{"content":null,"refusal":[{"token":" but","logprob":-0.038048144,"bytes":[32,98,117,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":{"content":null,"refusal":[{"token":" I","logprob":-0.0016109125,"bytes":[32,73],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":{"content":null,"refusal":[{"token":" can't","logprob":-0.0073532974,"bytes":[32,99,97,110,39,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":{"content":null,"refusal":[{"token":" assist","logprob":-0.0020837625,"bytes":[32,97,115,115,105,115,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":{"content":null,"refusal":[{"token":" with","logprob":-0.00318354,"bytes":[32,119,105,116,104],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":{"content":null,"refusal":[{"token":" that","logprob":-0.0017186158,"bytes":[32,116,104,97,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":{"content":null,"refusal":[{"token":".","logprob":-0.57687104,"bytes":[46],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":12,"total_tokens":91,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.writer b/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.writer
new file mode 100644
index 00000000..47dd7315
--- /dev/null
+++ b/.inline-snapshot/external/569c877e69429d4cbc1577d2cd6dd33878095c68badc6b6654a69769b391a1c1.writer
@@ -0,0 +1,30 @@
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":{"content":null,"refusal":[]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":{"content":null,"refusal":[{"token":"I'm","logprob":-0.0012038043,"bytes":[73,39,109],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" very"},"logprobs":{"content":null,"refusal":[{"token":" very","logprob":-0.8438816,"bytes":[32,118,101,114,121],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":{"content":null,"refusal":[{"token":" sorry","logprob":-3.4121115e-6,"bytes":[32,115,111,114,114,121],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":","},"logprobs":{"content":null,"refusal":[{"token":",","logprob":-0.000033809047,"bytes":[44],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" but"},"logprobs":{"content":null,"refusal":[{"token":" but","logprob":-0.038048144,"bytes":[32,98,117,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":{"content":null,"refusal":[{"token":" I","logprob":-0.0016109125,"bytes":[32,73],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":{"content":null,"refusal":[{"token":" can't","logprob":-0.0073532974,"bytes":[32,99,97,110,39,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":{"content":null,"refusal":[{"token":" assist","logprob":-0.0020837625,"bytes":[32,97,115,115,105,115,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":{"content":null,"refusal":[{"token":" with","logprob":-0.00318354,"bytes":[32,119,105,116,104],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":" that"},"logprobs":{"content":null,"refusal":[{"token":" that","logprob":-0.0017186158,"bytes":[32,116,104,97,116],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"refusal":"."},"logprobs":{"content":null,"refusal":[{"token":".","logprob":-0.57687104,"bytes":[46],"top_logprobs":[]}]},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw5GEVqPbLY576l46FZDQoNJ2KC","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":12,"total_tokens":91,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.openai b/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.openai
new file mode 100644
index 00000000..801db2ad
--- /dev/null
+++ b/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.openai
@@ -0,0 +1,36 @@
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"61"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":14,"total_tokens":93,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.writer b/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.writer
new file mode 100644
index 00000000..801db2ad
--- /dev/null
+++ b/.inline-snapshot/external/7e5ea4d12e7cc064399b6631415e65923f182256b6e6b752950a3aaa2ad2320a.writer
@@ -0,0 +1,36 @@
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"61"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF","object":"chat.completion.chunk","created":1727346169,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":14,"total_tokens":93,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.openai b/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.openai
new file mode 100644
index 00000000..e9f34b63
--- /dev/null
+++ b/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.openai
@@ -0,0 +1,12 @@
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":{"content":[],"refusal":null},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Foo"},"logprobs":{"content":[{"token":"Foo","logprob":-0.0025094282,"bytes":[70,111,111],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"!"},"logprobs":{"content":[{"token":"!","logprob":-0.26638845,"bytes":[33],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":9,"completion_tokens":2,"total_tokens":11,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.writer b/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.writer
new file mode 100644
index 00000000..e9f34b63
--- /dev/null
+++ b/.inline-snapshot/external/83b060bae42eb41c4f1edbb7c1542b954b37d9dfd1910b964ddebc9677e6ae85.writer
@@ -0,0 +1,12 @@
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":{"content":[],"refusal":null},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Foo"},"logprobs":{"content":[{"token":"Foo","logprob":-0.0025094282,"bytes":[70,111,111],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"!"},"logprobs":{"content":[{"token":"!","logprob":-0.26638845,"bytes":[33],"top_logprobs":[]}],"refusal":null},"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw5EzoqmfXjnnsXY7Yd8OC6tb3c","object":"chat.completion.chunk","created":1727346173,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":9,"completion_tokens":2,"total_tokens":11,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.openai b/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.openai
new file mode 100644
index 00000000..b44d334a
--- /dev/null
+++ b/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.openai
@@ -0,0 +1,28 @@
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_CTf1nWJLqSeRgDqaCG27xZ74","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"San"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Francisco"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"state"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"CA"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[],"usage":{"prompt_tokens":48,"completion_tokens":19,"total_tokens":67,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.writer b/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.writer
new file mode 100644
index 00000000..b44d334a
--- /dev/null
+++ b/.inline-snapshot/external/a247c49c5fcd492bfb7a02a3306ad615ed8d8f649888ebfddfbc3ee151f44d46.writer
@@ -0,0 +1,28 @@
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_CTf1nWJLqSeRgDqaCG27xZ74","type":"function","function":{"name":"get_weather","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"San"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Francisco"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"state"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"CA"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfwCgi41eStOcARjZq97ohCEGBPO","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[],"usage":{"prompt_tokens":48,"completion_tokens":19,"total_tokens":67,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.openai b/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.openai
new file mode 100644
index 00000000..160e65de
--- /dev/null
+++ b/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.openai
@@ -0,0 +1,100 @@
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"65"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"61"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"59"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":42,"total_tokens":121,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.writer b/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.writer
new file mode 100644
index 00000000..160e65de
--- /dev/null
+++ b/.inline-snapshot/external/a491adda08c3d4fde95f5b2ee3f60f7f745f1a56d82e62f58031cc2add502380.writer
@@ -0,0 +1,100 @@
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"city"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"65"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"61"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"59"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":",\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"units"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\":\""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"f"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{"content":"\"}"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":1,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[{"index":2,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw2KKFuVXmEJgVwYfBvejMAdWtq","object":"chat.completion.chunk","created":1727346170,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_b40fb1c6fb","choices":[],"usage":{"prompt_tokens":79,"completion_tokens":42,"total_tokens":121,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.openai b/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.openai
new file mode 100644
index 00000000..f20333fb
--- /dev/null
+++ b/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.openai
@@ -0,0 +1,36 @@
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_c91SqDXlYFuETYv8mUHzz6pp","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Ed"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"inburgh"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"country"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"UK"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"units"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[],"usage":{"prompt_tokens":76,"completion_tokens":24,"total_tokens":100,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.writer b/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.writer
new file mode 100644
index 00000000..f20333fb
--- /dev/null
+++ b/.inline-snapshot/external/c6aa7e397b7123c3501f25df3a05d4daf7e8ad6d61ffa406ab5361fe36a8d5b1.writer
@@ -0,0 +1,36 @@
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_c91SqDXlYFuETYv8mUHzz6pp","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}],"refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"city"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Ed"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"inburgh"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"country"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"UK"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\",\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"units"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfw8AOXnoa2kzy11vVTSjuQhHCQr","object":"chat.completion.chunk","created":1727346176,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_7568d46099","choices":[],"usage":{"prompt_tokens":76,"completion_tokens":24,"total_tokens":100,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.openai b/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.openai
new file mode 100644
index 00000000..aee8650c
--- /dev/null
+++ b/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.openai
@@ -0,0 +1,362 @@
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"location"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" CA"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"18"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Part"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"ly"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Cloud"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"humidity"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"72"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"%\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"wind"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Speed"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"15"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" km"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"/h"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"wind"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Direction"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"NW"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"forecast"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" [\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Monday"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"20"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"14"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Sunny"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Tuesday"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"19"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"15"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Mostly"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Cloud"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Wednesday"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"18"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"14"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Cloud"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" }\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" ]\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" }\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":19,"completion_tokens":177,"total_tokens":196,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.writer b/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.writer
new file mode 100644
index 00000000..aee8650c
--- /dev/null
+++ b/.inline-snapshot/external/d615580118391ee13492193e3a8bb74642d23ac1ca13fe37cb6e889b66f759f6.writer
@@ -0,0 +1,362 @@
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"location"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" CA"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"temperature"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"18"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Part"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"ly"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Cloud"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"humidity"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"72"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"%\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"wind"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Speed"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"15"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" km"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"/h"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"wind"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Direction"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"NW"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"forecast"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" [\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Monday"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"20"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"14"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Sunny"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Tuesday"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"19"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"15"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Mostly"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Cloud"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" },\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" {\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"day"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Wednesday"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"high"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"18"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"low"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"14"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"°C"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\",\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"condition"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\":"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" \""},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"Cloud"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"y"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"\"\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" }\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" ]\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" "},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" }\n"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfwCjPMi0ubw56UyMIIeNfJzyogq","object":"chat.completion.chunk","created":1727346180,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":19,"completion_tokens":177,"total_tokens":196,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.openai b/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.openai
new file mode 100644
index 00000000..b68ca8a3
--- /dev/null
+++ b/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.openai
@@ -0,0 +1,68 @@
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"I'm"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" unable"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" provide"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" real"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"-time"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" updates"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" To"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" get"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" current"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" I"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" recommend"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" checking"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" reliable"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" website"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" or"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" app"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":30,"total_tokens":44,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.writer b/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.writer
new file mode 100644
index 00000000..7f94ec5a
--- /dev/null
+++ b/.inline-snapshot/external/e2aad469b71d1d4894ff833ea147020a9d875eb7ce644a0ff355581690a4cbfd.writer
@@ -0,0 +1,67 @@
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":"","refusal":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"I'm"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" unable"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" to"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" provide"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" real"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"-time"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" updates"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" To"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" get"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" the"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" current"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" in"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" San"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" Francisco"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" I"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" recommend"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" checking"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" reliable"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" website"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" or"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" a"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" weather"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":" app"},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+
+data: {"id":"chatcmpl-ABfw031mOJeYCSHe4yI2ZjOA6kMJL","object":"chat.completion.chunk","created":1727346168,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":30,"total_tokens":44,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+
diff --git a/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.openai b/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.openai
new file mode 100644
index 00000000..3b111d5e
--- /dev/null
+++ b/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.openai
@@ -0,0 +1,52 @@
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_JMW1whyEaYG438VE1OIflxA2","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"ci"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ty\": "}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Edinb"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"urgh"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\", \"c"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ountry"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"GB\", "}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"units"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"call_DNYTawLBoN8fj3KN6qU9N1Ou","type":"function","function":{"name":"get_stock_price","arguments":""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\"ti"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"cker\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":": \"AAP"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"L\", "}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\"exch"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"ange\":"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":" \"NA"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"SDAQ\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":149,"completion_tokens":60,"total_tokens":209,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.writer b/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.writer
new file mode 100644
index 00000000..3b111d5e
--- /dev/null
+++ b/.inline-snapshot/external/f82268f2fefd5cfbc7eeb59c297688be2f6ca0849a6e4f17851b517310841d9b.writer
@@ -0,0 +1,52 @@
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"role":"assistant","content":null},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_JMW1whyEaYG438VE1OIflxA2","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"ci"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ty\": "}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Edinb"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"urgh"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\", \"c"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ountry"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"GB\", "}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"units"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\": \""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c\"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"call_DNYTawLBoN8fj3KN6qU9N1Ou","type":"function","function":{"name":"get_stock_price","arguments":""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\"ti"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"cker\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":": \"AAP"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"L\", "}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"\"exch"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"ange\":"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":" \"NA"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"SDAQ\""}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"}"}}]},"logprobs":null,"finish_reason":null}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}
+
+data: {"id":"chatcmpl-ABfwAwrNePHUgBBezonVC6MX3zd63","object":"chat.completion.chunk","created":1727346178,"model":"gpt-4o-2024-08-06","system_fingerprint":"fp_5050236cbd","choices":[],"usage":{"prompt_tokens":149,"completion_tokens":60,"total_tokens":209,"completion_tokens_details":{"reasoning_tokens":0}}}
+
+data: [DONE]
+
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 65f558e7..e466ebdd 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.0.0"
-}
\ No newline at end of file
+ ".": "2.1.0-rc1"
+}
diff --git a/.stats.yml b/.stats.yml
index 9ae4d8b3..79dab344 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,4 @@
-configured_endpoints: 29
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-3d3b2fe43375eac35441f6de554253a7dc3eaa5d97c00a5a351ec72d6587eb32.yml
+configured_endpoints: 30
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-d15316b8a3a086ae9ec8eea0d436b0885262df9bcf23b9587ad059e50357c220.yml
+openapi_spec_hash: 4f81a4f4840438f80eff345e76ead962
+config_hash: b3310cd2944d74a3599e847847226a42
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea6a2250..1ae6d301 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,44 @@
# Changelog
+## 2.1.0-rc1 (2025-04-01)
+
+Full Changelog: [v2.0.0...v2.1.0-rc1](https://github.com/writer/writer-python/compare/v2.0.0...v2.1.0-rc1)
+
+### Features
+
+* add chat streaming helpers ([#209](https://github.com/writer/writer-python/issues/209)) ([7b65fc1](https://github.com/writer/writer-python/commit/7b65fc1d54432bba101f1f23a83d532644a298be))
+* **api:** Add Vision endpoint. ([#220](https://github.com/writer/writer-python/issues/220)) ([886b828](https://github.com/writer/writer-python/commit/886b82895a8dd336debafa588ac1e14da359677d))
+* **api:** Add Vision endpoint. ([#221](https://github.com/writer/writer-python/issues/221)) ([967073d](https://github.com/writer/writer-python/commit/967073dec1241af76c0d11b4805c64f5ee7b7844))
+
+
+### Bug Fixes
+
+* **ci:** ensure pip is always available ([#214](https://github.com/writer/writer-python/issues/214)) ([7285bc1](https://github.com/writer/writer-python/commit/7285bc105f39631ce52d7877bc335d49cbe9294c))
+* **ci:** remove publishing patch ([#215](https://github.com/writer/writer-python/issues/215)) ([26c3993](https://github.com/writer/writer-python/commit/26c39939db099741785c7f2cb013a8a5105c2273))
+* **types:** handle more discriminated union shapes ([#213](https://github.com/writer/writer-python/issues/213)) ([d1a067c](https://github.com/writer/writer-python/commit/d1a067ce68afa9fb59832c291b1d9fbd30aae763))
+
+
+### Chores
+
+* **docs:** update client docstring ([#203](https://github.com/writer/writer-python/issues/203)) ([3779210](https://github.com/writer/writer-python/commit/3779210ed12a9332f86682059b4aec4320cf7e7c))
+* fix typos ([#222](https://github.com/writer/writer-python/issues/222)) ([2ad9218](https://github.com/writer/writer-python/commit/2ad92187d04daa64d4dd0720d8cd941523c411c9))
+* **internal:** bump rye to 0.44.0 ([#211](https://github.com/writer/writer-python/issues/211)) ([7efe8a4](https://github.com/writer/writer-python/commit/7efe8a4b003ea1d4edeee785e9f9ac805b914289))
+* **internal:** codegen related update ([#204](https://github.com/writer/writer-python/issues/204)) ([f5e9ce7](https://github.com/writer/writer-python/commit/f5e9ce7c1a763fd59547b7eb1f5c0856dc80562b))
+* **internal:** Fix README code samples. ([#219](https://github.com/writer/writer-python/issues/219)) ([2efd5ea](https://github.com/writer/writer-python/commit/2efd5eafe21865ea6406e463cdac2231c0f810ea))
+* **internal:** Fix README samples. ([#217](https://github.com/writer/writer-python/issues/217)) ([a06f969](https://github.com/writer/writer-python/commit/a06f9697adf77ee2be877279179977270581d393))
+* **internal:** remove extra empty newlines ([#210](https://github.com/writer/writer-python/issues/210)) ([3b5fd4c](https://github.com/writer/writer-python/commit/3b5fd4c429c740ca2f460b6f974769336831e80e))
+* **internal:** version bump ([#200](https://github.com/writer/writer-python/issues/200)) ([a7b5f67](https://github.com/writer/writer-python/commit/a7b5f670838042af9a51c6e916ba0dff122635ce))
+
+
+### Documentation
+
+* **api:** updates to API spec ([#208](https://github.com/writer/writer-python/issues/208)) ([829e77a](https://github.com/writer/writer-python/commit/829e77ae9fb6e899fbdc6075586491404ef910da))
+* **api:** updates to API spec ([#216](https://github.com/writer/writer-python/issues/216)) ([3d29c55](https://github.com/writer/writer-python/commit/3d29c5598266b8da7fe9aae0614dc4b43c4986f3))
+* **api:** updates to API spec ([#223](https://github.com/writer/writer-python/issues/223)) ([c72a478](https://github.com/writer/writer-python/commit/c72a4781f221ecaba62519c1c08831ab78ee2169))
+* **api:** updates to API spec ([#224](https://github.com/writer/writer-python/issues/224)) ([7a990d7](https://github.com/writer/writer-python/commit/7a990d7af74adaac7b68b0d2319e8abc3704c757))
+* revise readme docs about nested params ([#206](https://github.com/writer/writer-python/issues/206)) ([ce4dfcc](https://github.com/writer/writer-python/commit/ce4dfcc857ae82c2d4cab789d32844d6643b2d56))
+* update URLs from stainlessapi.com to stainless.com ([#202](https://github.com/writer/writer-python/issues/202)) ([5895a7a](https://github.com/writer/writer-python/commit/5895a7a2c077f50c74ee56c2e985f3ac7fa63382))
+
## 2.0.0 (2025-02-26)
Full Changelog: [v2.0.0-rc1...v2.0.0](https://github.com/writer/writer-python/compare/v2.0.0-rc1...v2.0.0)
diff --git a/README.md b/README.md
index bca571dc..7a358504 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ The Writer Python library provides access to the Writer REST API from any Python
application. It includes a set of tools and utilities that make it easy to integrate the capabilities
of Writer into your projects.
-It is generated with [Stainless](https://www.stainlessapi.com/).
+It is generated with [Stainless](https://www.stainless.com/).
## Documentation
@@ -70,7 +70,7 @@ client = Writer()
chat_completion = client.chat.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -92,7 +92,7 @@ async def main() -> None:
chat_completion = await client.chat.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -120,7 +120,7 @@ client = Writer()
stream = client.chat.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -148,7 +148,7 @@ client = AsyncWriter()
stream = await client.chat.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -167,6 +167,22 @@ print(output_text)
For non-streaming responses, the library returns a single response object.
+### Streaming Helpers
+
+The SDK also includes helpers to process streams and handle incoming events.
+
+```python
+with client.chat.stream(
+ model="palmyra-x-004",
+ messages=[{"role": "user", "content": prompt}],
+) as stream:
+ for event in stream:
+ if event.type == "content.delta":
+ print(event.delta, flush=True, end="")
+```
+
+More information on streaming helpers can be found in the dedicated documentation: [helpers.md](helpers.md)
+
## Pagination
List methods in the Writer API are paginated.
@@ -226,6 +242,23 @@ for graph in first_page.data:
print(graph.id)
```
+## Nested params
+
+Nested parameters are dictionaries, typed using `TypedDict`, for example:
+
+```python
+from writerai import Writer
+
+client = Writer()
+
+chat_completion = client.chat.chat(
+ messages=[{"role": "user"}],
+ model="model",
+ stream_options={"include_usage": True},
+)
+print(chat_completion.stream_options)
+```
+
## File uploads
You can pass file upload parameters as `bytes`, a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`.
@@ -267,7 +300,7 @@ try:
client.chat.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -318,7 +351,7 @@ client = Writer(
client.with_options(max_retries=5).chat.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -350,7 +383,7 @@ client = Writer(
client.with_options(timeout=5.0).chat.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -400,7 +433,7 @@ from writerai import Writer
client = Writer()
response = client.chat.with_raw_response.chat(
messages=[{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}],
model="palmyra-x-004",
@@ -423,7 +456,7 @@ To stream the raw response body, use `.with_streaming_response`, which requires
with client.chat.with_streaming_response.chat(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
diff --git a/SECURITY.md b/SECURITY.md
index f08c9053..b7b1acaa 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,9 +2,9 @@
## Reporting Security Issues
-This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
+This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
-To report a security issue, please contact the Stainless team at security@stainlessapi.com.
+To report a security issue, please contact the Stainless team at security@stainless.com.
## Responsible Disclosure
diff --git a/api.md b/api.md
index 2749ea06..5cb0e727 100644
--- a/api.md
+++ b/api.md
@@ -181,3 +181,15 @@ from writerai.types.tools import ComprehendMedicalResponse
Methods:
- client.tools.comprehend.medical(\*\*params) -> ComprehendMedicalResponse
+
+# Vision
+
+Types:
+
+```python
+from writerai.types import VisionRequest, VisionResponse
+```
+
+Methods:
+
+- client.vision.analyze(\*\*params) -> VisionResponse
diff --git a/bin/publish-pypi b/bin/publish-pypi
index 05bfccbb..826054e9 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -3,7 +3,4 @@
set -eux
mkdir -p dist
rye build --clean
-# Patching importlib-metadata version until upstream library version is updated
-# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
-"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
rye publish --yes --token=$PYPI_TOKEN
diff --git a/examples/chat_streaming_helper.py b/examples/chat_streaming_helper.py
new file mode 100644
index 00000000..40bf1078
--- /dev/null
+++ b/examples/chat_streaming_helper.py
@@ -0,0 +1,61 @@
+# This example demonstrates writer.com chat streaming helper
+#
+# To run it locally you will need:
+# - An API key from writer.com
+# - Rye installed, see https://rye-up.com
+#
+# Run the following commands from the root of the repository:
+# $ rye sync --all-features
+# $ WRITERAI_API_KEY="" rye run python examples/chat_streaming_helper.py
+
+from writerai import Client, AsyncClient, BadRequestError
+
+
+def sync_example() -> None:
+ client = Client()
+
+ print("Let's complete a prompt:")
+ prompt = "Hi, today I want to write about"
+ print(f"> {prompt}")
+ try:
+ with client.chat.stream(
+ model="palmyra-x-004",
+ messages=[{"role": "user", "content": prompt}],
+ ) as stream:
+ for event in stream:
+ if event.type == "content.delta":
+ print(event.delta, flush=True, end="")
+
+ except BadRequestError as e:
+ print(f"Error: {e.body}")
+
+
+async def async_example() -> None:
+ client = AsyncClient()
+
+ print("Let's complete a prompt:")
+ prompt = "Hi, today I want to write about"
+ print(f"> {prompt}")
+ try:
+ async with client.chat.stream(
+ model="palmyra-x-004",
+ messages=[{"role": "user", "content": prompt}],
+ ) as stream:
+ async for event in stream:
+ if event.type == "content.delta":
+ print(event.delta, flush=True, end="")
+
+ except BadRequestError as e:
+ print(f"Error: {e.body}")
+
+
+if __name__ == "__main__":
+ print("=== Synchronous example ===")
+ sync_example()
+ print()
+ print()
+
+ print("=== Asynchronous example ===")
+ import asyncio
+
+ asyncio.run(async_example())
diff --git a/helpers.md b/helpers.md
new file mode 100644
index 00000000..76f18ef5
--- /dev/null
+++ b/helpers.md
@@ -0,0 +1,130 @@
+# Streaming Helpers
+
+Writer supports streaming responses when interacting with the [Chat Completion API](#chat-completion-api).
+
+## Chat Completion API
+
+The SDK provides a `client.chat.stream()` method that wraps the `client.chat.chat(stream=True)` stream providing a more granular event API and automatic accumulation of each delta.
+
+Unlike `client.chat.chat(stream=True)`, the `stream()` method requires usage within a context manager to prevent accidental leak of the response:
+
+```py
+from writerai import AsyncWriter
+
+client = AsyncWriter()
+
+async with client.chat.stream(
+ model='palmyra-x-004',
+ messages=[...],
+) as stream:
+ async for event in stream:
+ if event.type == 'content.delta':
+ print(event.content, flush=True, end='')
+```
+
+When the context manager is entered, a `ChatCompletionStream` / `AsyncChatCompletionStream` instance is returned which, like `.create(stream=True)` is an iterator in the sync client and an async iterator in the async client. The full list of events that are yielded by the iterator are outlined [below](#chat-completions-events).
+
+When the context manager exits, the response will be closed, however the `stream` instance is still available outside
+the context manager.
+
+### Text Generation Events
+
+These events allow you to track the progress of the chat completion generation, access partial results, and handle different aspects of the stream separately.
+
+Below is a list of the different event types you may encounter:
+
+#### ChunkEvent
+
+Emitted for every chunk received from the API.
+
+- `type`: `"chunk"`
+- `chunk`: The raw `ChatCompletionChunk` object received from the API
+- `snapshot`: The current accumulated state of the chat completion
+
+#### ContentDeltaEvent
+
+Emitted for every chunk containing new content.
+
+- `type`: `"content.delta"`
+- `delta`: The new content string received in this chunk
+- `snapshot`: The accumulated content so far
+- `parsed`: The partially parsed content (if applicable)
+
+#### ContentDoneEvent
+
+Emitted when the content generation is complete. May be fired multiple times if there are multiple choices.
+
+- `type`: `"content.done"`
+- `content`: The full generated content
+- `parsed`: The fully parsed content (if applicable)
+
+#### RefusalDeltaEvent
+
+Emitted when a chunk contains part of a content refusal.
+
+- `type`: `"refusal.delta"`
+- `delta`: The new refusal content string received in this chunk
+- `snapshot`: The accumulated refusal content string so far
+
+#### RefusalDoneEvent
+
+Emitted when the refusal content is complete.
+
+- `type`: `"refusal.done"`
+- `refusal`: The full refusal content
+
+#### LogprobsContentDeltaEvent
+
+Emitted when a chunk contains new content log probabilities.
+
+- `type`: `"logprobs.content.delta"`
+- `content`: A list of the new log probabilities received in this chunk
+- `snapshot`: A list of the accumulated log probabilities so far
+
+#### LogprobsContentDoneEvent
+
+Emitted when all content log probabilities have been received.
+
+- `type`: `"logprobs.content.done"`
+- `content`: The full list of token log probabilities for the content
+
+#### LogprobsRefusalDeltaEvent
+
+Emitted when a chunk contains new refusal log probabilities.
+
+- `type`: `"logprobs.refusal.delta"`
+- `refusal`: A list of the new log probabilities received in this chunk
+- `snapshot`: A list of the accumulated log probabilities so far
+
+#### LogprobsRefusalDoneEvent
+
+Emitted when all refusal log probabilities have been received.
+
+- `type`: `"logprobs.refusal.done"`
+- `refusal`: The full list of token log probabilities for the refusal
+
+### Helper methods
+
+A handful of helper methods are provided on the stream class for additional convenience,
+
+**`.get_final_completion()`**
+
+Returns the accumulated `ParsedChatCompletion` object
+
+```py
+async with client.chat.stream(...) as stream:
+ ...
+
+completion = await stream.get_final_completion()
+print(completion.choices[0].message)
+```
+
+**`.until_done()`**
+
+If you want to wait for the stream to complete, you can use the `.until_done()` method.
+
+```py
+async with client.chat.stream(...) as stream:
+ await stream.until_done()
+ # stream is now finished
+```
diff --git a/pyproject.toml b/pyproject.toml
index 3cc82969..7ce8e640 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "writer-sdk"
-version = "2.0.0"
+version = "2.1.0-rc1"
description = "The official Python library for the writer API"
dynamic = ["readme"]
license = "Apache-2.0"
@@ -14,6 +14,8 @@ dependencies = [
"anyio>=3.5.0, <5",
"distro>=1.7.0, <2",
"sniffio",
+ "cached-property; python_version < '3.8'",
+ "jiter>=0.4.0, <1",
]
requires-python = ">= 3.8"
classifiers = [
@@ -38,7 +40,6 @@ Homepage = "https://github.com/writer/writer-python"
Repository = "https://github.com/writer/writer-python"
-
[tool.rye]
managed = true
# version pins are in requirements-dev.lock
@@ -54,7 +55,8 @@ dev-dependencies = [
"dirty-equals>=0.6.0",
"importlib-metadata>=6.7.0",
"rich>=13.7.1",
- "nest_asyncio==1.6.0",
+ "inline-snapshot >=0.7.0",
+ "nest_asyncio==1.6.0"
]
[tool.rye.scripts]
@@ -87,7 +89,7 @@ typecheck = { chain = [
"typecheck:mypy" = "mypy ."
[build-system]
-requires = ["hatchling", "hatch-fancy-pypi-readme"]
+requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[tool.hatch.build]
@@ -152,7 +154,6 @@ reportImplicitOverride = true
reportImportCycles = false
reportPrivateUsage = false
-
[tool.ruff]
line-length = 120
output-format = "grouped"
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 857dd051..a11423c2 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -17,6 +17,8 @@ anyio==4.4.0
# via writer-sdk
argcomplete==3.1.2
# via nox
+asttokens==3.0.0
+ # via inline-snapshot
certifi==2023.7.22
# via httpcore
# via httpx
@@ -30,6 +32,8 @@ distro==1.8.0
exceptiongroup==1.2.2
# via anyio
# via pytest
+executing==2.2.0
+ # via inline-snapshot
filelock==3.12.4
# via virtualenv
h11==0.14.0
@@ -45,6 +49,9 @@ idna==3.4
importlib-metadata==7.0.0
iniconfig==2.0.0
# via pytest
+inline-snapshot==0.20.5
+jiter==0.8.2
+ # via writer-sdk
markdown-it-py==3.0.0
# via rich
mdurl==0.1.2
@@ -79,6 +86,7 @@ pytz==2023.3.post1
# via dirty-equals
respx==0.22.0
rich==13.7.1
+ # via inline-snapshot
ruff==0.9.4
setuptools==68.2.2
# via nodeenv
@@ -89,6 +97,7 @@ sniffio==1.3.0
# via writer-sdk
time-machine==2.9.0
tomli==2.0.2
+ # via inline-snapshot
# via mypy
# via pytest
typing-extensions==4.12.2
diff --git a/requirements.lock b/requirements.lock
index 8bd96abb..f9197fb2 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -31,6 +31,8 @@ httpx==0.28.1
idna==3.4
# via anyio
# via httpx
+jiter==0.8.2
+ # via writer-sdk
pydantic==2.10.3
# via writer-sdk
pydantic-core==2.27.1
diff --git a/scripts/test b/scripts/test
index 4fa5698b..2b878456 100755
--- a/scripts/test
+++ b/scripts/test
@@ -52,6 +52,8 @@ else
echo
fi
+export DEFER_PYDANTIC_BUILD=false
+
echo "==> Running tests"
rye run pytest "$@"
diff --git a/src/writerai/__init__.py b/src/writerai/__init__.py
index 77e4947c..9f0a8dcd 100644
--- a/src/writerai/__init__.py
+++ b/src/writerai/__init__.py
@@ -8,6 +8,7 @@
from ._version import __title__, __version__
from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
+from .lib._tools import pydantic_function_tool
from ._exceptions import (
APIError,
WriterError,
@@ -21,8 +22,10 @@
AuthenticationError,
InternalServerError,
PermissionDeniedError,
+ LengthFinishReasonError,
UnprocessableEntityError,
APIResponseValidationError,
+ ContentFilterFinishReasonError,
)
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
from ._utils._logs import setup_logging as _setup_logging
@@ -51,6 +54,8 @@
"UnprocessableEntityError",
"RateLimitError",
"InternalServerError",
+ "LengthFinishReasonError",
+ "ContentFilterFinishReasonError",
"Timeout",
"RequestOptions",
"Client",
@@ -66,6 +71,7 @@
"DEFAULT_CONNECTION_LIMITS",
"DefaultHttpxClient",
"DefaultAsyncHttpxClient",
+ "pydantic_function_tool",
]
_setup_logging()
diff --git a/src/writerai/_base_client.py b/src/writerai/_base_client.py
index 276f85af..70e528b6 100644
--- a/src/writerai/_base_client.py
+++ b/src/writerai/_base_client.py
@@ -9,7 +9,6 @@
import inspect
import logging
import platform
-import warnings
import email.utils
from types import TracebackType
from random import random
@@ -36,7 +35,7 @@
import httpx
import distro
import pydantic
-from httpx import URL, Limits
+from httpx import URL
from pydantic import PrivateAttr
from . import _exceptions
@@ -52,13 +51,10 @@
NotGiven,
FileTypes,
ResponseT,
- Transport,
AnyMapping,
PostParser,
- ProxiesTypes,
RequestFiles,
HttpxSendArgs,
- AsyncTransport,
RequestOptions,
HttpxRequestFiles,
ModelBuilderProtocol,
@@ -338,9 +334,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
_base_url: URL
max_retries: int
timeout: Union[float, Timeout, None]
- _limits: httpx.Limits
- _proxies: ProxiesTypes | None
- _transport: Transport | AsyncTransport | None
_strict_response_validation: bool
_idempotency_header: str | None
_default_stream_cls: type[_DefaultStreamT] | None = None
@@ -353,9 +346,6 @@ def __init__(
_strict_response_validation: bool,
max_retries: int = DEFAULT_MAX_RETRIES,
timeout: float | Timeout | None = DEFAULT_TIMEOUT,
- limits: httpx.Limits,
- transport: Transport | AsyncTransport | None,
- proxies: ProxiesTypes | None,
custom_headers: Mapping[str, str] | None = None,
custom_query: Mapping[str, object] | None = None,
) -> None:
@@ -363,9 +353,6 @@ def __init__(
self._base_url = self._enforce_trailing_slash(URL(base_url))
self.max_retries = max_retries
self.timeout = timeout
- self._limits = limits
- self._proxies = proxies
- self._transport = transport
self._custom_headers = custom_headers or {}
self._custom_query = custom_query or {}
self._strict_response_validation = _strict_response_validation
@@ -803,46 +790,11 @@ def __init__(
base_url: str | URL,
max_retries: int = DEFAULT_MAX_RETRIES,
timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
- transport: Transport | None = None,
- proxies: ProxiesTypes | None = None,
- limits: Limits | None = None,
http_client: httpx.Client | None = None,
custom_headers: Mapping[str, str] | None = None,
custom_query: Mapping[str, object] | None = None,
_strict_response_validation: bool,
) -> None:
- kwargs: dict[str, Any] = {}
- if limits is not None:
- warnings.warn(
- "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead",
- category=DeprecationWarning,
- stacklevel=3,
- )
- if http_client is not None:
- raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`")
- else:
- limits = DEFAULT_CONNECTION_LIMITS
-
- if transport is not None:
- kwargs["transport"] = transport
- warnings.warn(
- "The `transport` argument is deprecated. The `http_client` argument should be passed instead",
- category=DeprecationWarning,
- stacklevel=3,
- )
- if http_client is not None:
- raise ValueError("The `http_client` argument is mutually exclusive with `transport`")
-
- if proxies is not None:
- kwargs["proxies"] = proxies
- warnings.warn(
- "The `proxies` argument is deprecated. The `http_client` argument should be passed instead",
- category=DeprecationWarning,
- stacklevel=3,
- )
- if http_client is not None:
- raise ValueError("The `http_client` argument is mutually exclusive with `proxies`")
-
if not is_given(timeout):
# if the user passed in a custom http client with a non-default
# timeout set then we use that timeout.
@@ -863,12 +815,9 @@ def __init__(
super().__init__(
version=version,
- limits=limits,
# cast to a valid type because mypy doesn't understand our type narrowing
timeout=cast(Timeout, timeout),
- proxies=proxies,
base_url=base_url,
- transport=transport,
max_retries=max_retries,
custom_query=custom_query,
custom_headers=custom_headers,
@@ -878,9 +827,6 @@ def __init__(
base_url=base_url,
# cast to a valid type because mypy doesn't understand our type narrowing
timeout=cast(Timeout, timeout),
- limits=limits,
- follow_redirects=True,
- **kwargs, # type: ignore
)
def is_closed(self) -> bool:
@@ -1384,45 +1330,10 @@ def __init__(
_strict_response_validation: bool,
max_retries: int = DEFAULT_MAX_RETRIES,
timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
- transport: AsyncTransport | None = None,
- proxies: ProxiesTypes | None = None,
- limits: Limits | None = None,
http_client: httpx.AsyncClient | None = None,
custom_headers: Mapping[str, str] | None = None,
custom_query: Mapping[str, object] | None = None,
) -> None:
- kwargs: dict[str, Any] = {}
- if limits is not None:
- warnings.warn(
- "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead",
- category=DeprecationWarning,
- stacklevel=3,
- )
- if http_client is not None:
- raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`")
- else:
- limits = DEFAULT_CONNECTION_LIMITS
-
- if transport is not None:
- kwargs["transport"] = transport
- warnings.warn(
- "The `transport` argument is deprecated. The `http_client` argument should be passed instead",
- category=DeprecationWarning,
- stacklevel=3,
- )
- if http_client is not None:
- raise ValueError("The `http_client` argument is mutually exclusive with `transport`")
-
- if proxies is not None:
- kwargs["proxies"] = proxies
- warnings.warn(
- "The `proxies` argument is deprecated. The `http_client` argument should be passed instead",
- category=DeprecationWarning,
- stacklevel=3,
- )
- if http_client is not None:
- raise ValueError("The `http_client` argument is mutually exclusive with `proxies`")
-
if not is_given(timeout):
# if the user passed in a custom http client with a non-default
# timeout set then we use that timeout.
@@ -1444,11 +1355,8 @@ def __init__(
super().__init__(
version=version,
base_url=base_url,
- limits=limits,
# cast to a valid type because mypy doesn't understand our type narrowing
timeout=cast(Timeout, timeout),
- proxies=proxies,
- transport=transport,
max_retries=max_retries,
custom_query=custom_query,
custom_headers=custom_headers,
@@ -1458,9 +1366,6 @@ def __init__(
base_url=base_url,
# cast to a valid type because mypy doesn't understand our type narrowing
timeout=cast(Timeout, timeout),
- limits=limits,
- follow_redirects=True,
- **kwargs, # type: ignore
)
def is_closed(self) -> bool:
diff --git a/src/writerai/_client.py b/src/writerai/_client.py
index 0a6f5a0f..a7b48cb6 100644
--- a/src/writerai/_client.py
+++ b/src/writerai/_client.py
@@ -8,7 +8,8 @@
import httpx
-from . import _exceptions
+from writerai import _exceptions
+
from ._qs import Querystring
from ._types import (
NOT_GIVEN,
@@ -24,7 +25,7 @@
get_async_library,
)
from ._version import __version__
-from .resources import chat, files, graphs, models, completions
+from .resources import chat, files, graphs, models, vision, completions
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import WriterError, APIStatusError
from ._base_client import (
@@ -46,6 +47,7 @@ class Writer(SyncAPIClient):
graphs: graphs.GraphsResource
files: files.FilesResource
tools: tools.ToolsResource
+ vision: vision.VisionResource
with_raw_response: WriterWithRawResponse
with_streaming_response: WriterWithStreamedResponse
@@ -75,7 +77,7 @@ def __init__(
# part of our public interface in the future.
_strict_response_validation: bool = False,
) -> None:
- """Construct a new synchronous writer client instance.
+ """Construct a new synchronous Writer client instance.
This automatically infers the `api_key` argument from the `WRITER_API_KEY` environment variable if it is not provided.
"""
@@ -112,6 +114,7 @@ def __init__(
self.graphs = graphs.GraphsResource(self)
self.files = files.FilesResource(self)
self.tools = tools.ToolsResource(self)
+ self.vision = vision.VisionResource(self)
self.with_raw_response = WriterWithRawResponse(self)
self.with_streaming_response = WriterWithStreamedResponse(self)
@@ -228,6 +231,7 @@ class AsyncWriter(AsyncAPIClient):
graphs: graphs.AsyncGraphsResource
files: files.AsyncFilesResource
tools: tools.AsyncToolsResource
+ vision: vision.AsyncVisionResource
with_raw_response: AsyncWriterWithRawResponse
with_streaming_response: AsyncWriterWithStreamedResponse
@@ -257,7 +261,7 @@ def __init__(
# part of our public interface in the future.
_strict_response_validation: bool = False,
) -> None:
- """Construct a new async writer client instance.
+ """Construct a new async AsyncWriter client instance.
This automatically infers the `api_key` argument from the `WRITER_API_KEY` environment variable if it is not provided.
"""
@@ -294,6 +298,7 @@ def __init__(
self.graphs = graphs.AsyncGraphsResource(self)
self.files = files.AsyncFilesResource(self)
self.tools = tools.AsyncToolsResource(self)
+ self.vision = vision.AsyncVisionResource(self)
self.with_raw_response = AsyncWriterWithRawResponse(self)
self.with_streaming_response = AsyncWriterWithStreamedResponse(self)
@@ -411,6 +416,7 @@ def __init__(self, client: Writer) -> None:
self.graphs = graphs.GraphsResourceWithRawResponse(client.graphs)
self.files = files.FilesResourceWithRawResponse(client.files)
self.tools = tools.ToolsResourceWithRawResponse(client.tools)
+ self.vision = vision.VisionResourceWithRawResponse(client.vision)
class AsyncWriterWithRawResponse:
@@ -422,6 +428,7 @@ def __init__(self, client: AsyncWriter) -> None:
self.graphs = graphs.AsyncGraphsResourceWithRawResponse(client.graphs)
self.files = files.AsyncFilesResourceWithRawResponse(client.files)
self.tools = tools.AsyncToolsResourceWithRawResponse(client.tools)
+ self.vision = vision.AsyncVisionResourceWithRawResponse(client.vision)
class WriterWithStreamedResponse:
@@ -433,6 +440,7 @@ def __init__(self, client: Writer) -> None:
self.graphs = graphs.GraphsResourceWithStreamingResponse(client.graphs)
self.files = files.FilesResourceWithStreamingResponse(client.files)
self.tools = tools.ToolsResourceWithStreamingResponse(client.tools)
+ self.vision = vision.VisionResourceWithStreamingResponse(client.vision)
class AsyncWriterWithStreamedResponse:
@@ -444,6 +452,7 @@ def __init__(self, client: AsyncWriter) -> None:
self.graphs = graphs.AsyncGraphsResourceWithStreamingResponse(client.graphs)
self.files = files.AsyncFilesResourceWithStreamingResponse(client.files)
self.tools = tools.AsyncToolsResourceWithStreamingResponse(client.tools)
+ self.vision = vision.AsyncVisionResourceWithStreamingResponse(client.vision)
Client = Writer
diff --git a/src/writerai/_compat.py b/src/writerai/_compat.py
index 92d9ee61..87fc3707 100644
--- a/src/writerai/_compat.py
+++ b/src/writerai/_compat.py
@@ -164,6 +164,18 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
return model.parse_obj(data) # pyright: ignore[reportDeprecated]
+def model_parse_json(model: type[_ModelT], data: str | bytes) -> _ModelT:
+ if PYDANTIC_V2:
+ return model.model_validate_json(data)
+ return model.parse_raw(data) # pyright: ignore[reportDeprecated]
+
+
+def model_json_schema(model: type[_ModelT]) -> dict[str, Any]:
+ if PYDANTIC_V2:
+ return model.model_json_schema()
+ return model.schema() # pyright: ignore[reportDeprecated]
+
+
# generic models
if TYPE_CHECKING:
diff --git a/src/writerai/_exceptions.py b/src/writerai/_exceptions.py
index 684af996..83d7179b 100644
--- a/src/writerai/_exceptions.py
+++ b/src/writerai/_exceptions.py
@@ -2,10 +2,14 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
from typing_extensions import Literal
import httpx
+if TYPE_CHECKING:
+ from .types.chat_completion import ChatCompletion
+
__all__ = [
"BadRequestError",
"AuthenticationError",
@@ -15,6 +19,8 @@
"UnprocessableEntityError",
"RateLimitError",
"InternalServerError",
+ "LengthFinishReasonError",
+ "ContentFilterFinishReasonError",
]
@@ -106,3 +112,26 @@ class RateLimitError(APIStatusError):
class InternalServerError(APIStatusError):
pass
+
+
+class LengthFinishReasonError(WriterError):
+ completion: ChatCompletion
+ """The completion that caused this error.
+ Note: this will *not* be a complete `ChatCompletion` object when streaming as `usage`
+ will not be included.
+ """
+
+ def __init__(self, *, completion: ChatCompletion) -> None:
+ msg = "Could not parse response content as the length limit was reached"
+ if completion.usage:
+ msg += f" - {completion.usage}"
+
+ super().__init__(msg)
+ self.completion = completion
+
+
+class ContentFilterFinishReasonError(WriterError):
+ def __init__(self) -> None:
+ super().__init__(
+ f"Could not parse response content as the request was rejected by the content filter",
+ )
diff --git a/src/writerai/_models.py b/src/writerai/_models.py
index 2084439d..4528004c 100644
--- a/src/writerai/_models.py
+++ b/src/writerai/_models.py
@@ -66,7 +66,7 @@
from ._constants import RAW_RESPONSE_HEADER
if TYPE_CHECKING:
- from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema
+ from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
__all__ = ["BaseModel", "GenericModel"]
@@ -647,15 +647,18 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
schema = model.__pydantic_core_schema__
+ if schema["type"] == "definitions":
+ schema = schema["schema"]
+
if schema["type"] != "model":
return None
+ schema = cast("ModelSchema", schema)
fields_schema = schema["schema"]
if fields_schema["type"] != "model-fields":
return None
fields_schema = cast("ModelFieldsSchema", fields_schema)
-
field = fields_schema["fields"].get(field_name)
if not field:
return None
@@ -679,7 +682,7 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None:
setattr(typ, "__pydantic_config__", config) # noqa: B010
-# our use of subclasssing here causes weirdness for type checkers,
+# our use of subclassing here causes weirdness for type checkers,
# so we just pretend that we don't subclass
if TYPE_CHECKING:
GenericModel = BaseModel
diff --git a/src/writerai/_utils/_transform.py b/src/writerai/_utils/_transform.py
index 18afd9d8..7ac2e17f 100644
--- a/src/writerai/_utils/_transform.py
+++ b/src/writerai/_utils/_transform.py
@@ -126,7 +126,7 @@ def _get_annotated_type(type_: type) -> type | None:
def _maybe_transform_key(key: str, type_: type) -> str:
"""Transform the given `data` based on the annotations provided in `type_`.
- Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata.
+ Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
"""
annotated_type = _get_annotated_type(type_)
if annotated_type is None:
diff --git a/src/writerai/_version.py b/src/writerai/_version.py
index a7140c47..ff36ac1c 100644
--- a/src/writerai/_version.py
+++ b/src/writerai/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "writerai"
-__version__ = "2.0.0" # x-release-please-version
+__version__ = "2.1.0-rc1" # x-release-please-version
diff --git a/src/writerai/lib/__init__.py b/src/writerai/lib/__init__.py
new file mode 100644
index 00000000..5c6cb782
--- /dev/null
+++ b/src/writerai/lib/__init__.py
@@ -0,0 +1,2 @@
+from ._tools import pydantic_function_tool as pydantic_function_tool
+from ._parsing import ResponseFormatT as ResponseFormatT
diff --git a/src/writerai/lib/_parsing/__init__.py b/src/writerai/lib/_parsing/__init__.py
new file mode 100644
index 00000000..4d454c3a
--- /dev/null
+++ b/src/writerai/lib/_parsing/__init__.py
@@ -0,0 +1,12 @@
+from ._completions import (
+ ResponseFormatT as ResponseFormatT,
+ has_parseable_input,
+ has_parseable_input as has_parseable_input,
+ maybe_parse_content as maybe_parse_content,
+ validate_input_tools as validate_input_tools,
+ parse_chat_completion as parse_chat_completion,
+ get_input_tool_by_name as get_input_tool_by_name,
+ solve_response_format_t as solve_response_format_t,
+ parse_function_tool_arguments as parse_function_tool_arguments,
+ type_to_response_format_param as type_to_response_format_param,
+)
diff --git a/src/writerai/lib/_parsing/_completions.py b/src/writerai/lib/_parsing/_completions.py
new file mode 100644
index 00000000..0938f919
--- /dev/null
+++ b/src/writerai/lib/_parsing/_completions.py
@@ -0,0 +1,278 @@
+from __future__ import annotations
+
+import json
+from typing import TYPE_CHECKING, Any, Iterable, cast
+from typing_extensions import TypeVar, TypeGuard, assert_never
+
+import pydantic
+
+from writerai.types.shared_params.tool_param import ToolParam
+
+from .._tools import PydanticFunctionTool
+from ..._types import NOT_GIVEN, NotGiven
+from ..._utils import is_given
+from ..._compat import PYDANTIC_V2, model_parse_json
+from ..._models import construct_type_unchecked
+from .._pydantic import is_basemodel_type, is_dataclass_like_type
+from ..._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError
+from ...types.parsed_chat import (
+ ParsedChatCompletion,
+ ParsedChatCompletionChoice,
+ ParsedChatCompletionMessage,
+)
+from ...types.shared_params import FunctionDefinition
+from ...types.chat_completion import ChatCompletion
+from ...types.shared.tool_call import Function
+from ...types.chat_completion_message import ChatCompletionMessage
+from ...types.parsed_function_tool_call import (
+ ParsedFunction,
+ ParsedFunctionToolCall,
+)
+
+ResponseFormatT = TypeVar(
+ "ResponseFormatT",
+ # if it isn't given then we don't do any parsing
+ default=None,
+)
+_default_response_format: None = None
+
+
+def validate_input_tools(
+ tools: Iterable[ToolParam] | NotGiven = NOT_GIVEN,
+) -> None:
+ if not is_given(tools):
+ return
+
+ for tool in tools:
+ if tool["type"] != "function":
+ raise ValueError(
+ f"Currently only `function` tool types support auto-parsing; Received `{tool['type']}`",
+ )
+
+ strict = tool["function"].get("strict")
+ if strict is not True:
+ raise ValueError(
+ f"`{tool['function']['name']}` is not strict. Only `strict` function tools can be auto-parsed"
+ )
+
+
+def parse_chat_completion(
+ *,
+ # response_format: type[ResponseFormatT] | chat_chat_params.ResponseFormat | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ input_tools: Iterable[ToolParam] | NotGiven,
+ chat_completion: ChatCompletion | ParsedChatCompletion[object],
+) -> ParsedChatCompletion[ResponseFormatT]:
+ if is_given(input_tools):
+ input_tools = [t for t in input_tools]
+ else:
+ input_tools = []
+
+ choices: list[ParsedChatCompletionChoice[ResponseFormatT]] = []
+ for choice in chat_completion.choices:
+ if choice.finish_reason == "length":
+ raise LengthFinishReasonError(completion=chat_completion)
+
+ if choice.finish_reason == "content_filter":
+ raise ContentFilterFinishReasonError()
+
+ message = choice.message
+
+ tool_calls: list[ParsedFunctionToolCall] = []
+ if message.tool_calls:
+ for tool_call in message.tool_calls:
+ if tool_call.type == "function":
+ tool_call_dict = tool_call.to_dict()
+ tool_calls.append(
+ construct_type_unchecked(
+ value={
+ **tool_call_dict,
+ "function": {
+ **cast(Any, tool_call_dict["function"]),
+ "parsed_arguments": parse_function_tool_arguments(
+ input_tools=input_tools, function=tool_call.function
+ ),
+ },
+ },
+ type_=ParsedFunctionToolCall,
+ )
+ )
+ elif TYPE_CHECKING: # type: ignore[unreachable]
+ assert_never(tool_call)
+ else:
+ tool_calls.append(tool_call)
+
+ choices.append(
+ construct_type_unchecked(
+ type_=cast(Any, ParsedChatCompletionChoice)[solve_response_format_t(response_format)],
+ value={
+ **choice.to_dict(),
+ "message": {
+ **message.to_dict(),
+ "parsed": maybe_parse_content(
+ response_format=response_format,
+ message=message,
+ ),
+ "tool_calls": tool_calls,
+ },
+ },
+ )
+ )
+
+ return cast(
+ ParsedChatCompletion[ResponseFormatT],
+ construct_type_unchecked(
+ type_=cast(Any, ParsedChatCompletion)[solve_response_format_t(response_format)],
+ value={
+ **chat_completion.to_dict(),
+ "choices": choices,
+ },
+ ),
+ )
+
+
+def get_input_tool_by_name(*, input_tools: list[ToolParam], name: str) -> ToolParam | None:
+ return next((t for t in input_tools if t.get("function", {}).get("name") == name), None)
+
+
+def parse_function_tool_arguments(*, input_tools: list[ToolParam], function: Function | ParsedFunction) -> object:
+ assert function.name is not None
+ input_tool = get_input_tool_by_name(input_tools=input_tools, name=function.name)
+ if not input_tool:
+ return None
+
+ input_fn = cast(object, input_tool.get("function"))
+ if isinstance(input_fn, PydanticFunctionTool):
+ return model_parse_json(input_fn.model, function.arguments)
+
+ input_fn = cast(FunctionDefinition, input_fn)
+
+ if not input_fn.get("strict"):
+ return None
+
+ return json.loads(function.arguments)
+
+
+def maybe_parse_content(
+ *,
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ message: ChatCompletionMessage | ParsedChatCompletionMessage[object],
+) -> ResponseFormatT | None:
+ if has_rich_response_format(response_format) and message.content and not message.refusal:
+ return _parse_content(response_format, message.content)
+
+ return None
+
+
+def solve_response_format_t(
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+) -> type[ResponseFormatT]:
+ """Return the runtime type for the given response format.
+
+ If no response format is given, or if we won't auto-parse the response format
+ then we default to `None`.
+ """
+ if has_rich_response_format(response_format):
+ return response_format
+
+ return cast("type[ResponseFormatT]", _default_response_format)
+
+
+def has_parseable_input(
+ *,
+ # response_format: type | ResponseFormatParam | NotGiven,
+ response_format: type | NotGiven,
+ input_tools: Iterable[ToolParam] | NotGiven = NOT_GIVEN,
+) -> bool:
+ if has_rich_response_format(response_format):
+ return True
+
+ for input_tool in input_tools or []:
+ if is_parseable_tool(input_tool):
+ return True
+
+ return False
+
+
+def has_rich_response_format(
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+) -> TypeGuard[type[ResponseFormatT]]:
+ if not is_given(response_format):
+ return False
+
+ # if is_response_format_param(response_format):
+ # return False
+
+ return True
+
+
+# def is_response_format_param(response_format: object) -> TypeGuard[ResponseFormatParam]:
+# return is_dict(response_format)
+
+
+def is_parseable_tool(input_tool: ToolParam) -> bool:
+ input_fn = cast(object, input_tool.get("function"))
+ if isinstance(input_fn, PydanticFunctionTool):
+ return True
+
+ # FIXME: `strict` currently missing in the schema definition
+ # return cast(FunctionDefinition, input_fn).get("strict") or False
+ return False
+
+
+def _parse_content(response_format: type[ResponseFormatT], content: str) -> ResponseFormatT:
+ if is_basemodel_type(response_format):
+ return cast(ResponseFormatT, model_parse_json(response_format, content))
+
+ if is_dataclass_like_type(response_format):
+ if not PYDANTIC_V2:
+ raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {response_format}")
+
+ return pydantic.TypeAdapter(response_format).validate_json(content)
+
+ raise TypeError(f"Unable to automatically parse response format type {response_format}")
+
+
+# def type_to_response_format_param(
+# response_format: type | completion_create_params.ResponseFormat | NotGiven,
+# ) -> ResponseFormatParam | NotGiven:
+# if not is_given(response_format):
+# return NOT_GIVEN
+
+# if is_response_format_param(response_format):
+# return response_format
+
+# # type checkers don't narrow the negation of a `TypeGuard` as it isn't
+# # a safe default behaviour but we know that at this point the `response_format`
+# # can only be a `type`
+# response_format = cast(type, response_format)
+
+# json_schema_type: type[pydantic.BaseModel] | pydantic.TypeAdapter[Any] | None = None
+
+# if is_basemodel_type(response_format):
+# name = response_format.__name__
+# json_schema_type = response_format
+# elif is_dataclass_like_type(response_format):
+# name = response_format.__name__
+# json_schema_type = pydantic.TypeAdapter(response_format)
+# else:
+# raise TypeError(f"Unsupported response_format type - {response_format}")
+
+
+# return {
+# "type": "json_schema",
+# "json_schema": {
+# "schema": to_strict_json_schema(json_schema_type),
+# "name": name,
+# "strict": True,
+# },
+# }
+def type_to_response_format_param(
+ response_format: type | NotGiven,
+) -> NotGiven:
+ if is_given(response_format):
+ raise NotImplementedError("Support for response_format is not implemented yet")
+ return NOT_GIVEN
diff --git a/src/writerai/lib/_pydantic.py b/src/writerai/lib/_pydantic.py
new file mode 100644
index 00000000..22c7a1f3
--- /dev/null
+++ b/src/writerai/lib/_pydantic.py
@@ -0,0 +1,150 @@
+from __future__ import annotations
+
+import inspect
+from typing import Any, TypeVar
+from typing_extensions import TypeGuard
+
+import pydantic
+
+from .._types import NOT_GIVEN
+from .._utils import is_dict as _is_dict, is_list
+from .._compat import PYDANTIC_V2, model_json_schema
+
+_T = TypeVar("_T")
+
+
+def to_strict_json_schema(model: type[pydantic.BaseModel] | pydantic.TypeAdapter[Any]) -> dict[str, Any]:
+ if inspect.isclass(model) and is_basemodel_type(model):
+ schema = model_json_schema(model)
+ elif PYDANTIC_V2 and isinstance(model, pydantic.TypeAdapter):
+ schema = model.json_schema()
+ else:
+ raise TypeError(f"Non BaseModel types are only supported with Pydantic v2 - {model}")
+
+ return _ensure_strict_json_schema(schema, path=(), root=schema)
+
+
+def _ensure_strict_json_schema(
+ json_schema: object,
+ *,
+ path: tuple[str, ...],
+ root: dict[str, object],
+) -> dict[str, Any]:
+ """Mutates the given JSON schema to ensure it conforms to the `strict` standard
+ that the API expects.
+ """
+ if not is_dict(json_schema):
+ raise TypeError(f"Expected {json_schema} to be a dictionary; path={path}")
+
+ defs = json_schema.get("$defs")
+ if is_dict(defs):
+ for def_name, def_schema in defs.items():
+ _ensure_strict_json_schema(def_schema, path=(*path, "$defs", def_name), root=root)
+
+ definitions = json_schema.get("definitions")
+ if is_dict(definitions):
+ for definition_name, definition_schema in definitions.items():
+ _ensure_strict_json_schema(definition_schema, path=(*path, "definitions", definition_name), root=root)
+
+ typ = json_schema.get("type")
+ if typ == "object" and "additionalProperties" not in json_schema:
+ json_schema["additionalProperties"] = False
+
+ # object types
+ # { 'type': 'object', 'properties': { 'a': {...} } }
+ properties = json_schema.get("properties")
+ if is_dict(properties):
+ json_schema["required"] = [prop for prop in properties.keys()]
+ json_schema["properties"] = {
+ key: _ensure_strict_json_schema(prop_schema, path=(*path, "properties", key), root=root)
+ for key, prop_schema in properties.items()
+ }
+
+ # arrays
+ # { 'type': 'array', 'items': {...} }
+ items = json_schema.get("items")
+ if is_dict(items):
+ json_schema["items"] = _ensure_strict_json_schema(items, path=(*path, "items"), root=root)
+
+ # unions
+ any_of = json_schema.get("anyOf")
+ if is_list(any_of):
+ json_schema["anyOf"] = [
+ _ensure_strict_json_schema(variant, path=(*path, "anyOf", str(i)), root=root)
+ for i, variant in enumerate(any_of)
+ ]
+
+ # intersections
+ all_of = json_schema.get("allOf")
+ if is_list(all_of):
+ if len(all_of) == 1:
+ json_schema.update(_ensure_strict_json_schema(all_of[0], path=(*path, "allOf", "0"), root=root))
+ json_schema.pop("allOf")
+ else:
+ json_schema["allOf"] = [
+ _ensure_strict_json_schema(entry, path=(*path, "allOf", str(i)), root=root)
+ for i, entry in enumerate(all_of)
+ ]
+
+ # strip `None` defaults as there's no meaningful distinction here
+ # the schema will still be `nullable` and the model will default
+ # to using `None` anyway
+ if json_schema.get("default", NOT_GIVEN) is None:
+ json_schema.pop("default")
+
+ # we can't use `$ref`s if there are also other properties defined, e.g.
+ # `{"$ref": "...", "description": "my description"}`
+ #
+ # so we unravel the ref
+ # `{"type": "string", "description": "my description"}`
+ ref = json_schema.get("$ref")
+ if ref and has_more_than_n_keys(json_schema, 1):
+ assert isinstance(ref, str), f"Received non-string $ref - {ref}"
+
+ resolved = resolve_ref(root=root, ref=ref)
+ if not is_dict(resolved):
+ raise ValueError(f"Expected `$ref: {ref}` to resolved to a dictionary but got {resolved}")
+
+ # properties from the json schema take priority over the ones on the `$ref`
+ json_schema.update({**resolved, **json_schema})
+ json_schema.pop("$ref")
+
+ return json_schema
+
+
+def resolve_ref(*, root: dict[str, object], ref: str) -> object:
+ if not ref.startswith("#/"):
+ raise ValueError(f"Unexpected $ref format {ref!r}; Does not start with #/")
+
+ path = ref[2:].split("/")
+ resolved = root
+ for key in path:
+ value = resolved[key]
+ assert is_dict(value), f"encountered non-dictionary entry while resolving {ref} - {resolved}"
+ resolved = value
+
+ return resolved
+
+
+def is_basemodel_type(typ: type) -> TypeGuard[type[pydantic.BaseModel]]:
+ return issubclass(typ, pydantic.BaseModel)
+
+
+def is_dataclass_like_type(typ: type) -> bool:
+ """Returns True if the given type likely used `@pydantic.dataclass`"""
+ return hasattr(typ, "__pydantic_config__")
+
+
+def is_dict(obj: object) -> TypeGuard[dict[str, object]]:
+ # just pretend that we know there are only `str` keys
+ # as that check is not worth the performance cost
+ return _is_dict(obj)
+
+
+def has_more_than_n_keys(obj: dict[str, object], n: int) -> bool:
+ i = 0
+ for _ in obj.keys():
+ i += 1
+ if i > n:
+ return True
+ return False
diff --git a/src/writerai/lib/_tools.py b/src/writerai/lib/_tools.py
new file mode 100644
index 00000000..a7b1ccac
--- /dev/null
+++ b/src/writerai/lib/_tools.py
@@ -0,0 +1,54 @@
+from __future__ import annotations
+
+from typing import Any, Dict, cast
+
+import pydantic
+
+from ._pydantic import to_strict_json_schema
+from ..types.shared_params import ToolParam, FunctionDefinition
+
+
+class PydanticFunctionTool(Dict[str, Any]):
+ """Dictionary wrapper so we can pass the given base model
+ throughout the entire request stack without having to special
+ case it.
+ """
+
+ model: type[pydantic.BaseModel]
+
+ def __init__(self, defn: FunctionDefinition, model: type[pydantic.BaseModel]) -> None:
+ super().__init__(defn)
+ self.model = model
+
+ def cast(self) -> FunctionDefinition:
+ return cast(FunctionDefinition, self)
+
+
+def pydantic_function_tool(
+ model: type[pydantic.BaseModel],
+ *,
+ name: str | None = None, # inferred from class name by default
+ description: str | None = None, # inferred from class docstring by default
+) -> ToolParam:
+ if description is None:
+ # note: we intentionally don't use `.getdoc()` to avoid
+ # including pydantic's docstrings
+ description = model.__doc__
+
+ function = PydanticFunctionTool(
+ {
+ "name": name or model.__name__,
+ # FIXME: `strict` currently missing in the schema definition
+ # "strict": True,
+ "parameters": to_strict_json_schema(model),
+ },
+ model,
+ ).cast()
+
+ if description is not None:
+ function["description"] = description
+
+ return {
+ "type": "function",
+ "function": function,
+ }
diff --git a/src/writerai/lib/streaming/__init__.py b/src/writerai/lib/streaming/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/writerai/lib/streaming/_deltas.py b/src/writerai/lib/streaming/_deltas.py
new file mode 100644
index 00000000..7e750823
--- /dev/null
+++ b/src/writerai/lib/streaming/_deltas.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+from ..._utils import is_dict, is_list
+
+
+def accumulate_delta(acc: dict[object, object], delta: dict[object, object]) -> dict[object, object]:
+ for key, delta_value in delta.items():
+ if key not in acc:
+ acc[key] = delta_value
+ continue
+
+ acc_value = acc[key]
+ if acc_value is None:
+ acc[key] = delta_value
+ continue
+
+ # the `index` property is used in arrays of objects so it should
+ # not be accumulated like other values e.g.
+ # [{'foo': 'bar', 'index': 0}]
+ #
+ # the same applies to `type` properties as they're used for
+ # discriminated unions
+ if key == "index" or key == "type" or key == "role":
+ acc[key] = delta_value
+ continue
+
+ if isinstance(acc_value, str) and isinstance(delta_value, str):
+ acc_value += delta_value
+ elif isinstance(acc_value, (int, float)) and isinstance(delta_value, (int, float)):
+ acc_value += delta_value
+ elif is_dict(acc_value) and is_dict(delta_value):
+ acc_value = accumulate_delta(acc_value, delta_value)
+ elif is_list(acc_value) and is_list(delta_value):
+ # for lists of non-dictionary items we'll only ever get new entries
+ # in the array, existing entries will never be changed
+ if all(isinstance(x, (str, int, float)) for x in acc_value):
+ acc_value.extend(delta_value)
+ continue
+
+ for delta_entry in delta_value:
+ if not is_dict(delta_entry):
+ raise TypeError(f"Unexpected list delta entry is not a dictionary: {delta_entry}")
+
+ try:
+ index = delta_entry["index"]
+ except KeyError as exc:
+ raise RuntimeError(f"Expected list delta entry to have an `index` key; {delta_entry}") from exc
+
+ if not isinstance(index, int):
+ raise TypeError(f"Unexpected, list delta entry `index` value is not an integer; {index}")
+
+ try:
+ acc_entry = acc_value[index]
+ except IndexError:
+ acc_value.insert(index, delta_entry)
+ else:
+ if not is_dict(acc_entry):
+ raise TypeError("not handled yet")
+
+ acc_value[index] = accumulate_delta(acc_entry, delta_entry)
+
+ acc[key] = acc_value
+
+ return acc
diff --git a/src/writerai/lib/streaming/chat/__init__.py b/src/writerai/lib/streaming/chat/__init__.py
new file mode 100644
index 00000000..c5739784
--- /dev/null
+++ b/src/writerai/lib/streaming/chat/__init__.py
@@ -0,0 +1,26 @@
+from ._types import (
+ ParsedChatCompletionSnapshot as ParsedChatCompletionSnapshot,
+ ParsedChatCompletionChoiceSnapshot as ParsedChatCompletionChoiceSnapshot,
+ ParsedChatCompletionMessageSnapshot as ParsedChatCompletionMessageSnapshot,
+)
+from ._events import (
+ ChunkEvent as ChunkEvent,
+ ContentDoneEvent as ContentDoneEvent,
+ RefusalDoneEvent as RefusalDoneEvent,
+ ContentDeltaEvent as ContentDeltaEvent,
+ RefusalDeltaEvent as RefusalDeltaEvent,
+ LogprobsContentDoneEvent as LogprobsContentDoneEvent,
+ LogprobsRefusalDoneEvent as LogprobsRefusalDoneEvent,
+ ChatCompletionStreamEvent as ChatCompletionStreamEvent,
+ LogprobsContentDeltaEvent as LogprobsContentDeltaEvent,
+ LogprobsRefusalDeltaEvent as LogprobsRefusalDeltaEvent,
+ ParsedChatCompletionSnapshot as ParsedChatCompletionSnapshot,
+ FunctionToolCallArgumentsDoneEvent as FunctionToolCallArgumentsDoneEvent,
+ FunctionToolCallArgumentsDeltaEvent as FunctionToolCallArgumentsDeltaEvent,
+)
+from ._completions import (
+ ChatCompletionStream as ChatCompletionStream,
+ AsyncChatCompletionStream as AsyncChatCompletionStream,
+ ChatCompletionStreamManager as ChatCompletionStreamManager,
+ AsyncChatCompletionStreamManager as AsyncChatCompletionStreamManager,
+)
diff --git a/src/writerai/lib/streaming/chat/_completions.py b/src/writerai/lib/streaming/chat/_completions.py
new file mode 100644
index 00000000..8015f5eb
--- /dev/null
+++ b/src/writerai/lib/streaming/chat/_completions.py
@@ -0,0 +1,741 @@
+from __future__ import annotations
+
+import inspect
+from types import TracebackType
+from typing import TYPE_CHECKING, Any, Generic, Callable, Iterable, Awaitable, AsyncIterator, cast
+from typing_extensions import Self, Iterator, assert_never
+
+from jiter import from_json
+
+from ._types import (
+ ParsedChatCompletionSnapshot,
+ ParsedChatCompletionChoiceSnapshot,
+ ParsedChatCompletionMessageSnapshot,
+)
+from ._events import (
+ ChunkEvent,
+ ContentDoneEvent,
+ RefusalDoneEvent,
+ ContentDeltaEvent,
+ RefusalDeltaEvent,
+ LogprobsContentDoneEvent,
+ LogprobsRefusalDoneEvent,
+ ChatCompletionStreamEvent,
+ LogprobsContentDeltaEvent,
+ LogprobsRefusalDeltaEvent,
+ FunctionToolCallArgumentsDoneEvent,
+ FunctionToolCallArgumentsDeltaEvent,
+)
+from .._deltas import accumulate_delta
+from ...._types import NOT_GIVEN, IncEx, NotGiven
+from ...._utils import is_given, consume_sync_iterator, consume_async_iterator
+from ...._compat import model_dump
+from ...._models import build, construct_type
+from ..._parsing import (
+ ResponseFormatT,
+ has_parseable_input,
+ maybe_parse_content,
+ parse_chat_completion,
+ get_input_tool_by_name,
+ solve_response_format_t,
+ parse_function_tool_arguments,
+)
+from ...._streaming import Stream, AsyncStream
+from ...._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError
+from ....types.parsed_chat import ParsedChatCompletion
+from ....types.shared_params import ToolParam
+from ....types.shared.logprobs import Logprobs
+from ....types.chat_completion_chunk import Choice as ChoiceChunk, ChatCompletionChunk
+
+
+class ChatCompletionStream(Generic[ResponseFormatT]):
+ """Wrapper over the Chat Completions streaming API that adds helpful
+ events such as `content.done`, supports automatically parsing
+ responses and tool calls and accumulates a `ChatCompletion` object
+ from each individual chunk.
+ """
+
+ def __init__(
+ self,
+ *,
+ raw_stream: Stream[ChatCompletionChunk],
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ input_tools: Iterable[ToolParam] | NotGiven,
+ ) -> None:
+ self._raw_stream = raw_stream
+ self._response = raw_stream.response
+ self._iterator = self.__stream__()
+ self._state = ChatCompletionStreamState(response_format=response_format, input_tools=input_tools)
+
+ def __next__(self) -> ChatCompletionStreamEvent[ResponseFormatT]:
+ return self._iterator.__next__()
+
+ def __iter__(self) -> Iterator[ChatCompletionStreamEvent[ResponseFormatT]]:
+ for item in self._iterator:
+ yield item
+
+ def __enter__(self) -> Self:
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> None:
+ self.close()
+
+ def close(self) -> None:
+ """
+ Close the response and release the connection.
+
+ Automatically called if the response body is read to completion.
+ """
+ self._response.close()
+
+ def get_final_completion(self) -> ParsedChatCompletion[ResponseFormatT]:
+ """Waits until the stream has been read to completion and returns
+ the accumulated `ParsedChatCompletion` object.
+
+ If you passed a class type to `.stream()`, the `completion.choices[0].message.parsed`
+ property will be the content deserialised into that class, if there was any content returned
+ by the API.
+ """
+ self.until_done()
+ return self._state.get_final_completion()
+
+ def until_done(self) -> Self:
+ """Blocks until the stream has been consumed."""
+ consume_sync_iterator(self)
+ return self
+
+ @property
+ def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot:
+ return self._state.current_completion_snapshot
+
+ def __stream__(self) -> Iterator[ChatCompletionStreamEvent[ResponseFormatT]]:
+ for sse_event in self._raw_stream:
+ events_to_fire = self._state.handle_chunk(sse_event)
+ for event in events_to_fire:
+ yield event
+
+
+class ChatCompletionStreamManager(Generic[ResponseFormatT]):
+ """Context manager over a `ChatCompletionStream` that is returned by `.stream()`.
+
+ This context manager ensures the response cannot be leaked if you don't read
+ the stream to completion.
+
+ Usage:
+ ```py
+ with client.chat.stream(...) as stream:
+ for event in stream:
+ ...
+ ```
+ """
+
+ def __init__(
+ self,
+ api_request: Callable[[], Stream[ChatCompletionChunk]],
+ *,
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ input_tools: Iterable[ToolParam] | NotGiven,
+ ) -> None:
+ self.__stream: ChatCompletionStream[ResponseFormatT] | None = None
+ self.__api_request = api_request
+ self.__response_format = response_format
+ self.__input_tools = input_tools
+
+ def __enter__(self) -> ChatCompletionStream[ResponseFormatT]:
+ raw_stream = self.__api_request()
+
+ self.__stream = ChatCompletionStream(
+ raw_stream=raw_stream,
+ response_format=self.__response_format,
+ input_tools=self.__input_tools,
+ )
+
+ return self.__stream
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> None:
+ if self.__stream is not None:
+ self.__stream.close()
+
+
+class AsyncChatCompletionStream(Generic[ResponseFormatT]):
+ """Wrapper over the Chat Completions streaming API that adds helpful
+ events such as `content.done`, supports automatically parsing
+ responses and tool calls and accumulates a `ChatCompletion` object
+ from each individual chunk.
+ """
+
+ def __init__(
+ self,
+ *,
+ raw_stream: AsyncStream[ChatCompletionChunk],
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ input_tools: Iterable[ToolParam] | NotGiven,
+ ) -> None:
+ self._raw_stream = raw_stream
+ self._response = raw_stream.response
+ self._iterator = self.__stream__()
+ self._state = ChatCompletionStreamState(response_format=response_format, input_tools=input_tools)
+
+ async def __anext__(self) -> ChatCompletionStreamEvent[ResponseFormatT]:
+ return await self._iterator.__anext__()
+
+ async def __aiter__(self) -> AsyncIterator[ChatCompletionStreamEvent[ResponseFormatT]]:
+ async for item in self._iterator:
+ yield item
+
+ async def __aenter__(self) -> Self:
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> None:
+ await self.close()
+
+ async def close(self) -> None:
+ """
+ Close the response and release the connection.
+
+ Automatically called if the response body is read to completion.
+ """
+ await self._response.aclose()
+
+ async def get_final_completion(self) -> ParsedChatCompletion[ResponseFormatT]:
+ """Waits until the stream has been read to completion and returns
+ the accumulated `ParsedChatCompletion` object.
+
+ If you passed a class type to `.stream()`, the `completion.choices[0].message.parsed`
+ property will be the content deserialised into that class, if there was any content returned
+ by the API.
+ """
+ await self.until_done()
+ return self._state.get_final_completion()
+
+ async def until_done(self) -> Self:
+ """Blocks until the stream has been consumed."""
+ await consume_async_iterator(self)
+ return self
+
+ @property
+ def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot:
+ return self._state.current_completion_snapshot
+
+ async def __stream__(self) -> AsyncIterator[ChatCompletionStreamEvent[ResponseFormatT]]:
+ async for sse_event in self._raw_stream:
+ events_to_fire = self._state.handle_chunk(sse_event)
+ for event in events_to_fire:
+ yield event
+
+
+class AsyncChatCompletionStreamManager(Generic[ResponseFormatT]):
+ """Context manager over a `AsyncChatCompletionStream` that is returned by `.stream()`.
+
+ This context manager ensures the response cannot be leaked if you don't read
+ the stream to completion.
+
+ Usage:
+ ```py
+ async with client.chat.stream(...) as stream:
+ for event in stream:
+ ...
+ ```
+ """
+
+ def __init__(
+ self,
+ api_request: Awaitable[AsyncStream[ChatCompletionChunk]],
+ *,
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ input_tools: Iterable[ToolParam] | NotGiven,
+ ) -> None:
+ self.__stream: AsyncChatCompletionStream[ResponseFormatT] | None = None
+ self.__api_request = api_request
+ self.__response_format = response_format
+ self.__input_tools = input_tools
+
+ async def __aenter__(self) -> AsyncChatCompletionStream[ResponseFormatT]:
+ raw_stream = await self.__api_request
+
+ self.__stream = AsyncChatCompletionStream(
+ raw_stream=raw_stream,
+ response_format=self.__response_format,
+ input_tools=self.__input_tools,
+ )
+
+ return self.__stream
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> None:
+ if self.__stream is not None:
+ await self.__stream.close()
+
+
+class ChatCompletionStreamState(Generic[ResponseFormatT]):
+ def __init__(
+ self,
+ *,
+ input_tools: Iterable[ToolParam] | NotGiven,
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ ) -> None:
+ self.__current_completion_snapshot: ParsedChatCompletionSnapshot | None = None
+ self.__choice_event_states: list[ChoiceEventState] = []
+
+ self._input_tools = [tool for tool in input_tools] if is_given(input_tools) else []
+ self._response_format = response_format
+ self._rich_response_format: type | NotGiven = response_format if inspect.isclass(response_format) else NOT_GIVEN
+
+ def get_final_completion(self) -> ParsedChatCompletion[ResponseFormatT]:
+ return parse_chat_completion(
+ chat_completion=self.current_completion_snapshot,
+ response_format=self._rich_response_format,
+ input_tools=self._input_tools,
+ )
+
+ @property
+ def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot:
+ assert self.__current_completion_snapshot is not None
+ return self.__current_completion_snapshot
+
+ def handle_chunk(self, chunk: ChatCompletionChunk) -> list[ChatCompletionStreamEvent[ResponseFormatT]]:
+ """Accumulate a new chunk into the snapshot and returns a list of events to yield."""
+ self.__current_completion_snapshot = self._accumulate_chunk(chunk)
+
+ return self._build_events(
+ chunk=chunk,
+ completion_snapshot=self.__current_completion_snapshot,
+ )
+
+ def _get_choice_state(self, choice: ChoiceChunk) -> ChoiceEventState:
+ try:
+ return self.__choice_event_states[choice.index]
+ except IndexError:
+ choice_state = ChoiceEventState(input_tools=self._input_tools)
+ self.__choice_event_states.append(choice_state)
+ return choice_state
+
+ def _accumulate_chunk(self, chunk: ChatCompletionChunk) -> ParsedChatCompletionSnapshot:
+ completion_snapshot = self.__current_completion_snapshot
+
+ if completion_snapshot is None:
+ return _convert_initial_chunk_into_snapshot(chunk)
+
+ for choice in chunk.choices:
+ try:
+ choice_snapshot = completion_snapshot.choices[choice.index]
+ previous_tool_calls = choice_snapshot.message.tool_calls or []
+
+ choice_snapshot.message = cast(
+ ParsedChatCompletionMessageSnapshot,
+ construct_type(
+ type_=ParsedChatCompletionMessageSnapshot,
+ value=accumulate_delta(
+ cast(
+ "dict[object, object]",
+ model_dump(
+ choice_snapshot.message,
+ # we don't want to serialise / deserialise our custom properties
+ # as they won't appear in the delta and we don't want to have to
+ # continuously reparse the content
+ exclude=cast(
+ # cast required as mypy isn't smart enough to infer `True` here to `Literal[True]`
+ IncEx,
+ {
+ "parsed": True,
+ "tool_calls": {
+ idx: {"function": {"parsed_arguments": True}}
+ for idx, _ in enumerate(choice_snapshot.message.tool_calls or [])
+ },
+ },
+ ),
+ ),
+ ),
+ cast("dict[object, object]", choice.delta.to_dict()),
+ ),
+ ),
+ )
+
+ # ensure tools that have already been parsed are added back into the newly
+ # constructed message snapshot
+ for tool_index, prev_tool in enumerate(previous_tool_calls):
+ new_tool = (choice_snapshot.message.tool_calls or [])[tool_index]
+
+ if prev_tool.type == "function":
+ assert new_tool.type == "function"
+ new_tool.function.parsed_arguments = prev_tool.function.parsed_arguments
+ elif TYPE_CHECKING: # type: ignore[unreachable]
+ assert_never(prev_tool)
+ except IndexError:
+ choice_snapshot = cast(
+ ParsedChatCompletionChoiceSnapshot,
+ construct_type(
+ type_=ParsedChatCompletionChoiceSnapshot,
+ value={
+ **choice.model_dump(exclude_unset=True, exclude={"delta"}),
+ "message": choice.delta.to_dict(),
+ },
+ ),
+ )
+ completion_snapshot.choices.append(choice_snapshot)
+
+ if choice.finish_reason:
+ choice_snapshot.finish_reason = choice.finish_reason
+
+ if has_parseable_input(response_format=self._response_format, input_tools=self._input_tools):
+ if choice.finish_reason == "length":
+ # at the time of writing, `.usage` will always be `None` but
+ # we include it here in case that is changed in the future
+ raise LengthFinishReasonError(completion=completion_snapshot)
+
+ if choice.finish_reason == "content_filter":
+ raise ContentFilterFinishReasonError()
+
+ if (
+ choice_snapshot.message.content
+ and not choice_snapshot.message.refusal
+ and is_given(self._rich_response_format)
+ ):
+ choice_snapshot.message.parsed = from_json(
+ bytes(choice_snapshot.message.content, "utf-8"),
+ partial_mode=True,
+ )
+
+ for tool_call_chunk in choice.delta.tool_calls or []:
+ tool_call_snapshot = (choice_snapshot.message.tool_calls or [])[tool_call_chunk.index]
+
+ if tool_call_snapshot.type == "function":
+ assert tool_call_snapshot.function.name is not None
+ input_tool = get_input_tool_by_name(
+ input_tools=self._input_tools, name=tool_call_snapshot.function.name
+ )
+
+ if (
+ input_tool
+ and input_tool.get("function", {}).get("strict")
+ and tool_call_snapshot.function.arguments
+ ):
+ tool_call_snapshot.function.parsed_arguments = from_json(
+ bytes(tool_call_snapshot.function.arguments, "utf-8"),
+ partial_mode=True,
+ )
+ elif TYPE_CHECKING: # type: ignore[unreachable]
+ assert_never(tool_call_snapshot)
+
+ if choice.logprobs is not None:
+ if choice_snapshot.logprobs is None:
+ choice_snapshot.logprobs = build(
+ Logprobs,
+ content=choice.logprobs.content,
+ refusal=choice.logprobs.refusal,
+ )
+ else:
+ if choice.logprobs.content:
+ if choice_snapshot.logprobs.content is None:
+ choice_snapshot.logprobs.content = []
+
+ choice_snapshot.logprobs.content.extend(choice.logprobs.content)
+
+ if choice.logprobs.refusal:
+ if choice_snapshot.logprobs.refusal is None:
+ choice_snapshot.logprobs.refusal = []
+
+ choice_snapshot.logprobs.refusal.extend(choice.logprobs.refusal)
+
+ completion_snapshot.usage = chunk.usage
+ completion_snapshot.system_fingerprint = chunk.system_fingerprint
+
+ return completion_snapshot
+
+ def _build_events(
+ self,
+ *,
+ chunk: ChatCompletionChunk,
+ completion_snapshot: ParsedChatCompletionSnapshot,
+ ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]:
+ events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = []
+
+ events_to_fire.append(
+ build(ChunkEvent, type="chunk", chunk=chunk, snapshot=completion_snapshot),
+ )
+
+ for choice in chunk.choices:
+ choice_state = self._get_choice_state(choice)
+ choice_snapshot = completion_snapshot.choices[choice.index]
+
+ if choice.delta.content is not None and choice_snapshot.message.content:
+ events_to_fire.append(
+ build(
+ ContentDeltaEvent,
+ type="content.delta",
+ delta=choice.delta.content,
+ snapshot=choice_snapshot.message.content,
+ parsed=choice_snapshot.message.parsed,
+ )
+ )
+
+ if choice.delta.refusal is not None and choice_snapshot.message.refusal is not None:
+ events_to_fire.append(
+ build(
+ RefusalDeltaEvent,
+ type="refusal.delta",
+ delta=choice.delta.refusal,
+ snapshot=choice_snapshot.message.refusal,
+ )
+ )
+
+ if choice.delta.tool_calls:
+ tool_calls = choice_snapshot.message.tool_calls
+ assert tool_calls is not None
+
+ for tool_call_delta in choice.delta.tool_calls:
+ tool_call = tool_calls[tool_call_delta.index]
+
+ if tool_call.type == "function":
+ assert tool_call_delta.function is not None
+ assert tool_call.function.name is not None
+ events_to_fire.append(
+ build(
+ FunctionToolCallArgumentsDeltaEvent,
+ type="tool_calls.function.arguments.delta",
+ name=tool_call.function.name,
+ index=tool_call_delta.index,
+ arguments=tool_call.function.arguments,
+ parsed_arguments=tool_call.function.parsed_arguments,
+ arguments_delta=tool_call_delta.function.arguments or "",
+ )
+ )
+ elif TYPE_CHECKING: # type: ignore[unreachable]
+ assert_never(tool_call)
+
+ if choice.logprobs is not None and choice_snapshot.logprobs is not None:
+ if choice.logprobs.content and choice_snapshot.logprobs.content:
+ events_to_fire.append(
+ build(
+ LogprobsContentDeltaEvent,
+ type="logprobs.content.delta",
+ content=choice.logprobs.content,
+ snapshot=choice_snapshot.logprobs.content,
+ ),
+ )
+
+ if choice.logprobs.refusal and choice_snapshot.logprobs.refusal:
+ events_to_fire.append(
+ build(
+ LogprobsRefusalDeltaEvent,
+ type="logprobs.refusal.delta",
+ refusal=choice.logprobs.refusal,
+ snapshot=choice_snapshot.logprobs.refusal,
+ ),
+ )
+
+ events_to_fire.extend(
+ choice_state.get_done_events(
+ choice_chunk=choice,
+ choice_snapshot=choice_snapshot,
+ response_format=self._response_format,
+ )
+ )
+
+ return events_to_fire
+
+
+class ChoiceEventState:
+ def __init__(self, *, input_tools: list[ToolParam]) -> None:
+ self._input_tools = input_tools
+
+ self._content_done = False
+ self._refusal_done = False
+ self._logprobs_content_done = False
+ self._logprobs_refusal_done = False
+ self._done_tool_calls: set[int] = set()
+ self.__current_tool_call_index: int | None = None
+
+ def get_done_events(
+ self,
+ *,
+ choice_chunk: ChoiceChunk,
+ choice_snapshot: ParsedChatCompletionChoiceSnapshot,
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]:
+ events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = []
+
+ if choice_snapshot.finish_reason:
+ events_to_fire.extend(
+ self._content_done_events(choice_snapshot=choice_snapshot, response_format=response_format)
+ )
+
+ if (
+ self.__current_tool_call_index is not None
+ and self.__current_tool_call_index not in self._done_tool_calls
+ ):
+ self._add_tool_done_event(
+ events_to_fire=events_to_fire,
+ choice_snapshot=choice_snapshot,
+ tool_index=self.__current_tool_call_index,
+ )
+
+ for tool_call in choice_chunk.delta.tool_calls or []:
+ if self.__current_tool_call_index != tool_call.index:
+ events_to_fire.extend(
+ self._content_done_events(choice_snapshot=choice_snapshot, response_format=response_format)
+ )
+
+ if self.__current_tool_call_index is not None:
+ self._add_tool_done_event(
+ events_to_fire=events_to_fire,
+ choice_snapshot=choice_snapshot,
+ tool_index=self.__current_tool_call_index,
+ )
+
+ self.__current_tool_call_index = tool_call.index
+
+ return events_to_fire
+
+ def _content_done_events(
+ self,
+ *,
+ choice_snapshot: ParsedChatCompletionChoiceSnapshot,
+ # response_format: type[ResponseFormatT] | ResponseFormatParam | NotGiven,
+ response_format: type[ResponseFormatT] | NotGiven,
+ ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]:
+ events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = []
+
+ if choice_snapshot.message.content and not self._content_done:
+ self._content_done = True
+
+ parsed = maybe_parse_content(
+ response_format=response_format,
+ message=choice_snapshot.message,
+ )
+
+ # update the parsed content to now use the richer `response_format`
+ # as opposed to the raw JSON-parsed object as the content is now
+ # complete and can be fully validated.
+ choice_snapshot.message.parsed = parsed
+
+ events_to_fire.append(
+ build(
+ # we do this dance so that when the `ContentDoneEvent` instance
+ # is printed at runtime the class name will include the solved
+ # type variable, e.g. `ContentDoneEvent[MyModelType]`
+ cast( # pyright: ignore[reportUnnecessaryCast]
+ "type[ContentDoneEvent[ResponseFormatT]]",
+ cast(Any, ContentDoneEvent)[solve_response_format_t(response_format)],
+ ),
+ type="content.done",
+ content=choice_snapshot.message.content,
+ parsed=parsed,
+ ),
+ )
+
+ if choice_snapshot.message.refusal is not None and not self._refusal_done:
+ self._refusal_done = True
+ events_to_fire.append(
+ build(RefusalDoneEvent, type="refusal.done", refusal=choice_snapshot.message.refusal),
+ )
+
+ if (
+ choice_snapshot.logprobs is not None
+ and choice_snapshot.logprobs.content is not None
+ and not self._logprobs_content_done
+ ):
+ self._logprobs_content_done = True
+ events_to_fire.append(
+ build(LogprobsContentDoneEvent, type="logprobs.content.done", content=choice_snapshot.logprobs.content),
+ )
+
+ if (
+ choice_snapshot.logprobs is not None
+ and choice_snapshot.logprobs.refusal is not None
+ and not self._logprobs_refusal_done
+ ):
+ self._logprobs_refusal_done = True
+ events_to_fire.append(
+ build(LogprobsRefusalDoneEvent, type="logprobs.refusal.done", refusal=choice_snapshot.logprobs.refusal),
+ )
+
+ return events_to_fire
+
+ def _add_tool_done_event(
+ self,
+ *,
+ events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]],
+ choice_snapshot: ParsedChatCompletionChoiceSnapshot,
+ tool_index: int,
+ ) -> None:
+ if tool_index in self._done_tool_calls:
+ return
+
+ self._done_tool_calls.add(tool_index)
+
+ assert choice_snapshot.message.tool_calls is not None
+ tool_call_snapshot = choice_snapshot.message.tool_calls[tool_index]
+
+ if tool_call_snapshot.type == "function":
+ parsed_arguments = parse_function_tool_arguments(
+ input_tools=self._input_tools, function=tool_call_snapshot.function
+ )
+
+ # update the parsed content to potentially use a richer type
+ # as opposed to the raw JSON-parsed object as the content is now
+ # complete and can be fully validated.
+ tool_call_snapshot.function.parsed_arguments = parsed_arguments
+
+ assert tool_call_snapshot.function.name is not None
+ events_to_fire.append(
+ build(
+ FunctionToolCallArgumentsDoneEvent,
+ type="tool_calls.function.arguments.done",
+ index=tool_index,
+ name=tool_call_snapshot.function.name,
+ arguments=tool_call_snapshot.function.arguments,
+ parsed_arguments=parsed_arguments,
+ tool_call_snapshot_id=tool_call_snapshot.id
+ )
+ )
+ elif TYPE_CHECKING: # type: ignore[unreachable]
+ assert_never(tool_call_snapshot)
+
+
+def _convert_initial_chunk_into_snapshot(chunk: ChatCompletionChunk) -> ParsedChatCompletionSnapshot:
+ data = chunk.to_dict()
+ choices = cast("list[object]", data["choices"])
+
+ for choice in chunk.choices:
+ choices[choice.index] = {
+ **choice.model_dump(exclude_unset=True, exclude={"delta"}),
+ "message": choice.delta.to_dict(),
+ }
+
+ return cast(
+ ParsedChatCompletionSnapshot,
+ construct_type(
+ type_=ParsedChatCompletionSnapshot,
+ value={
+ "system_fingerprint": None,
+ **data,
+ "object": "chat.completion",
+ },
+ ),
+ )
diff --git a/src/writerai/lib/streaming/chat/_events.py b/src/writerai/lib/streaming/chat/_events.py
new file mode 100644
index 00000000..6b4d30e7
--- /dev/null
+++ b/src/writerai/lib/streaming/chat/_events.py
@@ -0,0 +1,126 @@
+from typing import List, Union, Generic, Optional
+from typing_extensions import Literal
+
+from ._types import ParsedChatCompletionSnapshot
+from ...._models import BaseModel, GenericModel
+from ..._parsing import ResponseFormatT
+from ....types.chat_completion_chunk import ChatCompletionChunk
+from ....types.shared.logprobs_token import LogprobsToken
+
+
+class ChunkEvent(BaseModel):
+ type: Literal["chunk"]
+
+ chunk: ChatCompletionChunk
+
+ snapshot: ParsedChatCompletionSnapshot
+
+
+class ContentDeltaEvent(BaseModel):
+ """This event is yielded for every chunk with `choice.delta.content` data."""
+
+ type: Literal["content.delta"]
+
+ delta: str
+
+ snapshot: str
+
+ parsed: Optional[object] = None
+
+
+class ContentDoneEvent(GenericModel, Generic[ResponseFormatT]):
+ type: Literal["content.done"]
+
+ content: str
+
+ parsed: Optional[ResponseFormatT] = None
+
+
+class RefusalDeltaEvent(BaseModel):
+ type: Literal["refusal.delta"]
+
+ delta: str
+
+ snapshot: str
+
+
+class RefusalDoneEvent(BaseModel):
+ type: Literal["refusal.done"]
+
+ refusal: str
+
+
+class FunctionToolCallArgumentsDeltaEvent(BaseModel):
+ type: Literal["tool_calls.function.arguments.delta"]
+
+ name: str
+
+ index: int
+
+ arguments: str
+ """Accumulated raw JSON string"""
+
+ parsed_arguments: object
+ """The parsed arguments so far"""
+
+ arguments_delta: str
+ """The JSON string delta"""
+
+
+class FunctionToolCallArgumentsDoneEvent(BaseModel):
+ type: Literal["tool_calls.function.arguments.done"]
+
+ name: str
+
+ index: int
+
+ tool_call_snapshot_id: str
+
+ arguments: str
+ """Accumulated raw JSON string"""
+
+ parsed_arguments: object
+ """The parsed arguments"""
+
+
+class LogprobsContentDeltaEvent(BaseModel):
+ type: Literal["logprobs.content.delta"]
+
+ content: List[LogprobsToken]
+
+ snapshot: List[LogprobsToken]
+
+
+class LogprobsContentDoneEvent(BaseModel):
+ type: Literal["logprobs.content.done"]
+
+ content: List[LogprobsToken]
+
+
+class LogprobsRefusalDeltaEvent(BaseModel):
+ type: Literal["logprobs.refusal.delta"]
+
+ refusal: List[LogprobsToken]
+
+ snapshot: List[LogprobsToken]
+
+
+class LogprobsRefusalDoneEvent(BaseModel):
+ type: Literal["logprobs.refusal.done"]
+
+ refusal: List[LogprobsToken]
+
+
+ChatCompletionStreamEvent = Union[
+ ChunkEvent,
+ ContentDeltaEvent,
+ ContentDoneEvent[ResponseFormatT],
+ RefusalDeltaEvent,
+ RefusalDoneEvent,
+ FunctionToolCallArgumentsDeltaEvent,
+ FunctionToolCallArgumentsDoneEvent,
+ LogprobsContentDeltaEvent,
+ LogprobsContentDoneEvent,
+ LogprobsRefusalDeltaEvent,
+ LogprobsRefusalDoneEvent,
+]
diff --git a/src/writerai/lib/streaming/chat/_types.py b/src/writerai/lib/streaming/chat/_types.py
new file mode 100644
index 00000000..d85778b8
--- /dev/null
+++ b/src/writerai/lib/streaming/chat/_types.py
@@ -0,0 +1,20 @@
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+from ....types.parsed_chat import ParsedChatCompletion, ParsedChatCompletionChoice, ParsedChatCompletionMessage
+
+ParsedChatCompletionSnapshot: TypeAlias = ParsedChatCompletion[object]
+"""Snapshot type representing an in-progress accumulation of
+a `ParsedChatCompletion` object.
+"""
+
+ParsedChatCompletionMessageSnapshot: TypeAlias = ParsedChatCompletionMessage[object]
+"""Snapshot type representing an in-progress accumulation of
+a `ParsedChatCompletionMessage` object.
+
+If the content has been fully accumulated, the `.parsed` content will be
+the `response_format` instance, otherwise it'll be the raw JSON parsed version.
+"""
+
+ParsedChatCompletionChoiceSnapshot: TypeAlias = ParsedChatCompletionChoice[object]
diff --git a/src/writerai/resources/__init__.py b/src/writerai/resources/__init__.py
index fc4062c7..40417904 100644
--- a/src/writerai/resources/__init__.py
+++ b/src/writerai/resources/__init__.py
@@ -40,6 +40,14 @@
ModelsResourceWithStreamingResponse,
AsyncModelsResourceWithStreamingResponse,
)
+from .vision import (
+ VisionResource,
+ AsyncVisionResource,
+ VisionResourceWithRawResponse,
+ AsyncVisionResourceWithRawResponse,
+ VisionResourceWithStreamingResponse,
+ AsyncVisionResourceWithStreamingResponse,
+)
from .completions import (
CompletionsResource,
AsyncCompletionsResource,
@@ -100,4 +108,10 @@
"AsyncToolsResourceWithRawResponse",
"ToolsResourceWithStreamingResponse",
"AsyncToolsResourceWithStreamingResponse",
+ "VisionResource",
+ "AsyncVisionResource",
+ "VisionResourceWithRawResponse",
+ "AsyncVisionResourceWithRawResponse",
+ "VisionResourceWithStreamingResponse",
+ "AsyncVisionResourceWithStreamingResponse",
]
diff --git a/src/writerai/resources/applications.py b/src/writerai/resources/applications.py
new file mode 100644
index 00000000..a9f031fd
--- /dev/null
+++ b/src/writerai/resources/applications.py
@@ -0,0 +1,178 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+
+import httpx
+
+from ..types import application_generate_content_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import (
+ maybe_transform,
+ async_maybe_transform,
+)
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.application_generate_content_response import ApplicationGenerateContentResponse
+
+__all__ = ["ApplicationsResource", "AsyncApplicationsResource"]
+
+
+class ApplicationsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ApplicationsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return the
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/writer/writer-python#accessing-raw-response-data-eg-headers
+ """
+ return ApplicationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ApplicationsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/writer/writer-python#with_streaming_response
+ """
+ return ApplicationsResourceWithStreamingResponse(self)
+
+ def generate_content(
+ self,
+ application_id: str,
+ *,
+ inputs: Iterable[application_generate_content_params.Input],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> ApplicationGenerateContentResponse:
+ """
+ Generate content from an existing application with inputs.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not application_id:
+ raise ValueError(f"Expected a non-empty value for `application_id` but received {application_id!r}")
+ return self._post(
+ f"/v1/applications/{application_id}",
+ body=maybe_transform(
+ {"inputs": inputs}, application_generate_content_params.ApplicationGenerateContentParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ApplicationGenerateContentResponse,
+ )
+
+
+class AsyncApplicationsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncApplicationsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return the
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/writer/writer-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncApplicationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncApplicationsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/writer/writer-python#with_streaming_response
+ """
+ return AsyncApplicationsResourceWithStreamingResponse(self)
+
+ async def generate_content(
+ self,
+ application_id: str,
+ *,
+ inputs: Iterable[application_generate_content_params.Input],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> ApplicationGenerateContentResponse:
+ """
+ Generate content from an existing application with inputs.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not application_id:
+ raise ValueError(f"Expected a non-empty value for `application_id` but received {application_id!r}")
+ return await self._post(
+ f"/v1/applications/{application_id}",
+ body=await async_maybe_transform(
+ {"inputs": inputs}, application_generate_content_params.ApplicationGenerateContentParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ApplicationGenerateContentResponse,
+ )
+
+
+class ApplicationsResourceWithRawResponse:
+ def __init__(self, applications: ApplicationsResource) -> None:
+ self._applications = applications
+
+ self.generate_content = to_raw_response_wrapper(
+ applications.generate_content,
+ )
+
+
+class AsyncApplicationsResourceWithRawResponse:
+ def __init__(self, applications: AsyncApplicationsResource) -> None:
+ self._applications = applications
+
+ self.generate_content = async_to_raw_response_wrapper(
+ applications.generate_content,
+ )
+
+
+class ApplicationsResourceWithStreamingResponse:
+ def __init__(self, applications: ApplicationsResource) -> None:
+ self._applications = applications
+
+ self.generate_content = to_streamed_response_wrapper(
+ applications.generate_content,
+ )
+
+
+class AsyncApplicationsResourceWithStreamingResponse:
+ def __init__(self, applications: AsyncApplicationsResource) -> None:
+ self._applications = applications
+
+ self.generate_content = async_to_streamed_response_wrapper(
+ applications.generate_content,
+ )
diff --git a/src/writerai/resources/chat.py b/src/writerai/resources/chat.py
index 4edd3bef..b3af9e4a 100644
--- a/src/writerai/resources/chat.py
+++ b/src/writerai/resources/chat.py
@@ -3,6 +3,7 @@
from __future__ import annotations
from typing import List, Union, Iterable
+from functools import partial
from typing_extensions import Literal, overload
import httpx
@@ -24,6 +25,8 @@
)
from .._streaming import Stream, AsyncStream
from .._base_client import make_request_options
+from ..lib._parsing import ResponseFormatT
+from ..lib.streaming.chat import ChatCompletionStreamManager, AsyncChatCompletionStreamManager
from ..types.chat_completion import ChatCompletion
from ..types.chat_completion_chunk import ChatCompletionChunk
from ..types.shared_params.tool_param import ToolParam
@@ -115,9 +118,11 @@ def chat(
automatically choose the best tool, `none` disables tool calling. You can also
pass a specific previously defined function.
- tools: An array of tools described to the model using JSON schema that the model can
- use to generate responses. You can define your own functions or use the built-in
- `graph` or `llm` tools.
+ tools: An array containing tool definitions for tools that the model can use to
+ generate responses. The tool definitions use JSON schema. You can define your
+ own functions or use one of the built-in `graph`, `llm`, or `vision` tools. Note
+ that you can only use one built-in tool type in the array (only one of `graph`,
+ `llm`, or `vision`).
top_p: Sets the threshold for "nucleus sampling," a technique to focus the model's
token generation on the most likely subset of tokens. Only tokens with
@@ -198,9 +203,11 @@ def chat(
automatically choose the best tool, `none` disables tool calling. You can also
pass a specific previously defined function.
- tools: An array of tools described to the model using JSON schema that the model can
- use to generate responses. You can define your own functions or use the built-in
- `graph` or `llm` tools.
+ tools: An array containing tool definitions for tools that the model can use to
+ generate responses. The tool definitions use JSON schema. You can define your
+ own functions or use one of the built-in `graph`, `llm`, or `vision` tools. Note
+ that you can only use one built-in tool type in the array (only one of `graph`,
+ `llm`, or `vision`).
top_p: Sets the threshold for "nucleus sampling," a technique to focus the model's
token generation on the most likely subset of tokens. Only tokens with
@@ -281,9 +288,11 @@ def chat(
automatically choose the best tool, `none` disables tool calling. You can also
pass a specific previously defined function.
- tools: An array of tools described to the model using JSON schema that the model can
- use to generate responses. You can define your own functions or use the built-in
- `graph` or `llm` tools.
+ tools: An array containing tool definitions for tools that the model can use to
+ generate responses. The tool definitions use JSON schema. You can define your
+ own functions or use one of the built-in `graph`, `llm`, or `vision` tools. Note
+ that you can only use one built-in tool type in the array (only one of `graph`,
+ `llm`, or `vision`).
top_p: Sets the threshold for "nucleus sampling," a technique to focus the model's
token generation on the most likely subset of tokens. Only tokens with
@@ -350,6 +359,76 @@ def chat(
stream_cls=Stream[ChatCompletionChunk],
)
+ def stream(
+ self,
+ *,
+ messages: Iterable[chat_chat_params.Message],
+ model: str,
+ logprobs: bool | NotGiven = NOT_GIVEN,
+ max_tokens: int | NotGiven = NOT_GIVEN,
+ n: int | NotGiven = NOT_GIVEN,
+ stop: Union[List[str], str] | NotGiven = NOT_GIVEN,
+ stream_options: chat_chat_params.StreamOptions | NotGiven = NOT_GIVEN,
+ temperature: float | NotGiven = NOT_GIVEN,
+ tool_choice: chat_chat_params.ToolChoice | NotGiven = NOT_GIVEN,
+ tools: Iterable[ToolParam] | NotGiven = NOT_GIVEN,
+ top_p: float | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> ChatCompletionStreamManager[ResponseFormatT]:
+ """Wrapper over the `client.chat.chat(stream=True)` method that provides a more granular event API
+ and automatic accumulation of each delta.
+
+ Unlike `.create(stream=True)`, the `.stream()` method requires usage within a context manager to prevent accidental leakage of the response:
+ ```py
+ with client.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[...],
+ ) as stream:
+ for event in stream:
+ if event.type == "content.delta":
+ print(event.delta, flush=True, end="")
+ ```
+
+ When the context manager is entered, a `ChatCompletionStream` instance is returned which, like `.create(stream=True)` is an iterator. The full list of events that are yielded by the iterator are outlined in [these docs](https://github.com/writer/writer-python/blob/main/helpers.md#chat-completions-events).
+
+ When the context manager exits, the response will be closed, however the `stream` instance is still available outside
+ the context manager.
+ """
+ extra_headers = {
+ "X-Stainless-Helper-Method": "chat.stream",
+ **(extra_headers or {}),
+ }
+
+ api_request: partial[Stream[ChatCompletionChunk]] = partial(
+ self._client.chat.chat,
+ messages=messages,
+ model=model,
+ stream=True,
+ logprobs=logprobs,
+ max_tokens=max_tokens,
+ n=n,
+ stop=stop,
+ stream_options=stream_options,
+ temperature=temperature,
+ tool_choice=tool_choice,
+ tools=tools,
+ top_p=top_p,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ )
+ return ChatCompletionStreamManager(
+ api_request,
+ response_format=NOT_GIVEN,
+ input_tools=tools,
+ )
+
class AsyncChatResource(AsyncAPIResource):
@cached_property
@@ -435,9 +514,11 @@ async def chat(
automatically choose the best tool, `none` disables tool calling. You can also
pass a specific previously defined function.
- tools: An array of tools described to the model using JSON schema that the model can
- use to generate responses. You can define your own functions or use the built-in
- `graph` or `llm` tools.
+ tools: An array containing tool definitions for tools that the model can use to
+ generate responses. The tool definitions use JSON schema. You can define your
+ own functions or use one of the built-in `graph`, `llm`, or `vision` tools. Note
+ that you can only use one built-in tool type in the array (only one of `graph`,
+ `llm`, or `vision`).
top_p: Sets the threshold for "nucleus sampling," a technique to focus the model's
token generation on the most likely subset of tokens. Only tokens with
@@ -518,9 +599,11 @@ async def chat(
automatically choose the best tool, `none` disables tool calling. You can also
pass a specific previously defined function.
- tools: An array of tools described to the model using JSON schema that the model can
- use to generate responses. You can define your own functions or use the built-in
- `graph` or `llm` tools.
+ tools: An array containing tool definitions for tools that the model can use to
+ generate responses. The tool definitions use JSON schema. You can define your
+ own functions or use one of the built-in `graph`, `llm`, or `vision` tools. Note
+ that you can only use one built-in tool type in the array (only one of `graph`,
+ `llm`, or `vision`).
top_p: Sets the threshold for "nucleus sampling," a technique to focus the model's
token generation on the most likely subset of tokens. Only tokens with
@@ -601,9 +684,11 @@ async def chat(
automatically choose the best tool, `none` disables tool calling. You can also
pass a specific previously defined function.
- tools: An array of tools described to the model using JSON schema that the model can
- use to generate responses. You can define your own functions or use the built-in
- `graph` or `llm` tools.
+ tools: An array containing tool definitions for tools that the model can use to
+ generate responses. The tool definitions use JSON schema. You can define your
+ own functions or use one of the built-in `graph`, `llm`, or `vision` tools. Note
+ that you can only use one built-in tool type in the array (only one of `graph`,
+ `llm`, or `vision`).
top_p: Sets the threshold for "nucleus sampling," a technique to focus the model's
token generation on the most likely subset of tokens. Only tokens with
@@ -670,6 +755,75 @@ async def chat(
stream_cls=AsyncStream[ChatCompletionChunk],
)
+ def stream(
+ self,
+ *,
+ messages: Iterable[chat_chat_params.Message],
+ model: str,
+ logprobs: bool | NotGiven = NOT_GIVEN,
+ max_tokens: int | NotGiven = NOT_GIVEN,
+ n: int | NotGiven = NOT_GIVEN,
+ stop: Union[List[str], str] | NotGiven = NOT_GIVEN,
+ stream_options: chat_chat_params.StreamOptions | NotGiven = NOT_GIVEN,
+ temperature: float | NotGiven = NOT_GIVEN,
+ tool_choice: chat_chat_params.ToolChoice | NotGiven = NOT_GIVEN,
+ tools: Iterable[ToolParam] | NotGiven = NOT_GIVEN,
+ top_p: float | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> AsyncChatCompletionStreamManager[ResponseFormatT]:
+ """Wrapper over the `client.chat.chat(stream=True)` method that provides a more granular event API
+ and automatic accumulation of each delta.
+
+ Unlike `.create(stream=True)`, the `.stream()` method requires usage within a context manager to prevent accidental leakage of the response:
+ ```py
+ async with client.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[...],
+ ) as stream:
+ async for event in stream:
+ if event.type == "content.delta":
+ print(event.delta, flush=True, end="")
+ ```
+
+ When the context manager is entered, a `AsyncChatCompletionStream` instance is returned which, like `.create(stream=True)` is an async iterator. The full list of events that are yielded by the iterator are outlined in [these docs](https://github.com/writer/writer-python/blob/main/helpers.md#chat-completions-events).
+
+ When the context manager exits, the response will be closed, however the `stream` instance is still available outside
+ the context manager.
+ """
+ extra_headers = {
+ "X-Stainless-Helper-Method": "chat.stream",
+ **(extra_headers or {}),
+ }
+
+ api_request = self._client.chat.chat(
+ messages=messages,
+ model=model,
+ stream=True,
+ logprobs=logprobs,
+ max_tokens=max_tokens,
+ n=n,
+ stop=stop,
+ stream_options=stream_options,
+ temperature=temperature,
+ tool_choice=tool_choice,
+ tools=tools,
+ top_p=top_p,
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ )
+ return AsyncChatCompletionStreamManager(
+ api_request,
+ response_format=NOT_GIVEN,
+ input_tools=tools,
+ )
+
class ChatResourceWithRawResponse:
def __init__(self, chat: ChatResource) -> None:
diff --git a/src/writerai/resources/graphs.py b/src/writerai/resources/graphs.py
index b4f3d87f..bd32124b 100644
--- a/src/writerai/resources/graphs.py
+++ b/src/writerai/resources/graphs.py
@@ -358,8 +358,8 @@ def question(
*,
graph_ids: List[str],
question: str,
- stream: Literal[False],
- subqueries: bool,
+ stream: Literal[False] | NotGiven = NOT_GIVEN,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -398,7 +398,7 @@ def question(
graph_ids: List[str],
question: str,
stream: Literal[True],
- subqueries: bool,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -437,7 +437,7 @@ def question(
graph_ids: List[str],
question: str,
stream: bool,
- subqueries: bool,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -469,14 +469,14 @@ def question(
"""
...
- @required_args(["graph_ids", "question", "stream", "subqueries"])
+ @required_args(["graph_ids", "question"], ["graph_ids", "question", "stream"])
def question(
self,
*,
graph_ids: List[str],
question: str,
- stream: Literal[False] | Literal[True],
- subqueries: bool,
+ stream: Literal[False] | Literal[True] | NotGiven = NOT_GIVEN,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -857,8 +857,8 @@ async def question(
*,
graph_ids: List[str],
question: str,
- stream: Literal[False],
- subqueries: bool,
+ stream: Literal[False] | NotGiven = NOT_GIVEN,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -897,7 +897,7 @@ async def question(
graph_ids: List[str],
question: str,
stream: Literal[True],
- subqueries: bool,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -936,7 +936,7 @@ async def question(
graph_ids: List[str],
question: str,
stream: bool,
- subqueries: bool,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -968,14 +968,14 @@ async def question(
"""
...
- @required_args(["graph_ids", "question", "stream", "subqueries"])
+ @required_args(["graph_ids", "question"], ["graph_ids", "question", "stream"])
async def question(
self,
*,
graph_ids: List[str],
question: str,
- stream: Literal[False] | Literal[True],
- subqueries: bool,
+ stream: Literal[False] | Literal[True] | NotGiven = NOT_GIVEN,
+ subqueries: bool | NotGiven = NOT_GIVEN,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
diff --git a/src/writerai/resources/vision.py b/src/writerai/resources/vision.py
new file mode 100644
index 00000000..26975788
--- /dev/null
+++ b/src/writerai/resources/vision.py
@@ -0,0 +1,200 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+
+import httpx
+
+from ..types import vision_analyze_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import (
+ maybe_transform,
+ async_maybe_transform,
+)
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.vision_response import VisionResponse
+
+__all__ = ["VisionResource", "AsyncVisionResource"]
+
+
+class VisionResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> VisionResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/writer/writer-python#accessing-raw-response-data-eg-headers
+ """
+ return VisionResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> VisionResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/writer/writer-python#with_streaming_response
+ """
+ return VisionResourceWithStreamingResponse(self)
+
+ def analyze(
+ self,
+ *,
+ model: str,
+ prompt: str,
+ variables: Iterable[vision_analyze_params.Variable],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> VisionResponse:
+ """
+ Submit images and a prompt to generate an analysis of the images.
+
+ Args:
+ model: The model to be used for image analysis. Currently only supports
+ `palmyra-vision`.
+
+ prompt: The prompt to use for the image analysis. The prompt must include the name of
+ each image variable, surrounded by double curly braces (`{{}}`). For example,
+ `Describe the difference between the image {{image_1}} and the image {{image_2}}`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/vision",
+ body=maybe_transform(
+ {
+ "model": model,
+ "prompt": prompt,
+ "variables": variables,
+ },
+ vision_analyze_params.VisionAnalyzeParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VisionResponse,
+ )
+
+
+class AsyncVisionResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncVisionResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/writer/writer-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncVisionResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncVisionResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/writer/writer-python#with_streaming_response
+ """
+ return AsyncVisionResourceWithStreamingResponse(self)
+
+ async def analyze(
+ self,
+ *,
+ model: str,
+ prompt: str,
+ variables: Iterable[vision_analyze_params.Variable],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> VisionResponse:
+ """
+ Submit images and a prompt to generate an analysis of the images.
+
+ Args:
+ model: The model to be used for image analysis. Currently only supports
+ `palmyra-vision`.
+
+ prompt: The prompt to use for the image analysis. The prompt must include the name of
+ each image variable, surrounded by double curly braces (`{{}}`). For example,
+ `Describe the difference between the image {{image_1}} and the image {{image_2}}`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/vision",
+ body=await async_maybe_transform(
+ {
+ "model": model,
+ "prompt": prompt,
+ "variables": variables,
+ },
+ vision_analyze_params.VisionAnalyzeParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VisionResponse,
+ )
+
+
+class VisionResourceWithRawResponse:
+ def __init__(self, vision: VisionResource) -> None:
+ self._vision = vision
+
+ self.analyze = to_raw_response_wrapper(
+ vision.analyze,
+ )
+
+
+class AsyncVisionResourceWithRawResponse:
+ def __init__(self, vision: AsyncVisionResource) -> None:
+ self._vision = vision
+
+ self.analyze = async_to_raw_response_wrapper(
+ vision.analyze,
+ )
+
+
+class VisionResourceWithStreamingResponse:
+ def __init__(self, vision: VisionResource) -> None:
+ self._vision = vision
+
+ self.analyze = to_streamed_response_wrapper(
+ vision.analyze,
+ )
+
+
+class AsyncVisionResourceWithStreamingResponse:
+ def __init__(self, vision: AsyncVisionResource) -> None:
+ self._vision = vision
+
+ self.analyze = async_to_streamed_response_wrapper(
+ vision.analyze,
+ )
diff --git a/src/writerai/types/__init__.py b/src/writerai/types/__init__.py
index e76f9b35..03f63434 100644
--- a/src/writerai/types/__init__.py
+++ b/src/writerai/types/__init__.py
@@ -22,6 +22,7 @@
from .question import Question as Question
from .completion import Completion as Completion
from .chat_completion import ChatCompletion as ChatCompletion
+from .vision_response import VisionResponse as VisionResponse
from .chat_chat_params import ChatChatParams as ChatChatParams
from .completion_chunk import CompletionChunk as CompletionChunk
from .file_list_params import FileListParams as FileListParams
@@ -40,6 +41,7 @@
from .graph_question_params import GraphQuestionParams as GraphQuestionParams
from .graph_update_response import GraphUpdateResponse as GraphUpdateResponse
from .tool_parse_pdf_params import ToolParsePdfParams as ToolParsePdfParams
+from .vision_analyze_params import VisionAnalyzeParams as VisionAnalyzeParams
from .chat_completion_choice import ChatCompletionChoice as ChatCompletionChoice
from .application_list_params import ApplicationListParams as ApplicationListParams
from .chat_completion_message import ChatCompletionMessage as ChatCompletionMessage
diff --git a/src/writerai/types/application_generate_content_params.py b/src/writerai/types/application_generate_content_params.py
index 885a2beb..9541512b 100644
--- a/src/writerai/types/application_generate_content_params.py
+++ b/src/writerai/types/application_generate_content_params.py
@@ -29,9 +29,12 @@ class Input(TypedDict, total=False):
value: Required[List[str]]
"""The value for the input field.
- If file is required you will need to pass a `file_id`. See
- [here](https://dev.writer.com/api-guides/api-reference/file-api/upload-files)
- for the Files API.
+ If the input type is "File upload", you must pass the `file_id` of an uploaded
+ file. You cannot pass a file object directly. See the
+ [file upload endpoint](/api-guides/api-reference/file-api/upload-files) for
+ instructions on uploading files or the
+ [list files endpoint](/api-guides/api-reference/file-api/get-all-files) for how
+ to see a list of uploaded files and their IDs.
"""
diff --git a/src/writerai/types/applications/job_create_params.py b/src/writerai/types/applications/job_create_params.py
index 86c6b048..467d133a 100644
--- a/src/writerai/types/applications/job_create_params.py
+++ b/src/writerai/types/applications/job_create_params.py
@@ -25,7 +25,10 @@ class Input(TypedDict, total=False):
value: Required[List[str]]
"""The value for the input field.
- If file is required you will need to pass a `file_id`. See
- [here](https://dev.writer.com/api-guides/api-reference/file-api/upload-files)
- for the Files API.
+ If the input type is "File upload", you must pass the `file_id` of an uploaded
+ file. You cannot pass a file object directly. See the
+ [file upload endpoint](/api-guides/api-reference/file-api/upload-files) for
+ instructions on uploading files or the
+ [list files endpoint](/api-guides/api-reference/file-api/get-all-files) for how
+ to see a list of uploaded files and their IDs.
"""
diff --git a/src/writerai/types/chat_chat_params.py b/src/writerai/types/chat_chat_params.py
index 1b14328b..08dce8f2 100644
--- a/src/writerai/types/chat_chat_params.py
+++ b/src/writerai/types/chat_chat_params.py
@@ -77,9 +77,11 @@ class ChatChatParamsBase(TypedDict, total=False):
tools: Iterable[ToolParam]
"""
- An array of tools described to the model using JSON schema that the model can
- use to generate responses. You can define your own functions or use the built-in
- `graph` or `llm` tools.
+ An array containing tool definitions for tools that the model can use to
+ generate responses. The tool definitions use JSON schema. You can define your
+ own functions or use one of the built-in `graph`, `llm`, or `vision` tools. Note
+ that you can only use one built-in tool type in the array (only one of `graph`,
+ `llm`, or `vision`).
"""
top_p: float
@@ -93,6 +95,12 @@ class ChatChatParamsBase(TypedDict, total=False):
class Message(TypedDict, total=False):
role: Required[Literal["user", "assistant", "system", "tool"]]
+ """The role of the chat message.
+
+ You can provide a system prompt by setting the role to `system`, or specify that
+ a message is the result of a [tool call](/api-guides/tool-calling) by setting
+ the role to `tool`.
+ """
content: Optional[str]
diff --git a/src/writerai/types/completion_choice.py b/src/writerai/types/completion_choice.py
new file mode 100644
index 00000000..74a1e2f9
--- /dev/null
+++ b/src/writerai/types/completion_choice.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .shared.logprobs import Logprobs
+
+__all__ = ["CompletionChoice"]
+
+
+class CompletionChoice(BaseModel):
+ text: str
+ """
+ The generated text output from the model, which forms the main content of the
+ response.
+ """
+
+ log_probs: Optional[Logprobs] = None
diff --git a/src/writerai/types/graph_question_params.py b/src/writerai/types/graph_question_params.py
index 508d10ae..570cbbd6 100644
--- a/src/writerai/types/graph_question_params.py
+++ b/src/writerai/types/graph_question_params.py
@@ -15,12 +15,12 @@ class GraphQuestionParamsBase(TypedDict, total=False):
question: Required[str]
"""The question to be answered using the Knowledge Graph."""
- subqueries: Required[bool]
+ subqueries: bool
"""Specify whether to include subqueries."""
class GraphQuestionParamsNonStreaming(GraphQuestionParamsBase, total=False):
- stream: Required[Literal[False]]
+ stream: Literal[False]
"""Determines whether the model's output should be streamed.
If true, the output is generated and sent incrementally, which can be useful for
diff --git a/src/writerai/types/parsed_chat.py b/src/writerai/types/parsed_chat.py
new file mode 100644
index 00000000..303aa5dd
--- /dev/null
+++ b/src/writerai/types/parsed_chat.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Generic, TypeVar, Optional
+
+from .._models import GenericModel
+from .chat_completion import ChatCompletion
+from .chat_completion_choice import ChatCompletionChoice
+from .chat_completion_message import ChatCompletionMessage
+from .parsed_function_tool_call import ParsedFunctionToolCall
+
+__all__ = ["ParsedChatCompletion", "ParsedChatCompletionChoice", "ParsedChatCompletionMessage"]
+
+
+ContentType = TypeVar("ContentType")
+
+
+# we need to disable this check because we're overriding properties
+# with subclasses of their types which is technically unsound as
+# properties can be mutated.
+# pyright: reportIncompatibleVariableOverride=false
+
+
+class ParsedChatCompletionMessage(ChatCompletionMessage, GenericModel, Generic[ContentType]):
+ parsed: Optional[ContentType] = None
+ """The auto-parsed message contents"""
+
+ tool_calls: Optional[List[ParsedFunctionToolCall]] = None # type: ignore[assignment]
+ """The tool calls generated by the model, such as function calls."""
+
+
+class ParsedChatCompletionChoice(ChatCompletionChoice, GenericModel, Generic[ContentType]):
+ message: ParsedChatCompletionMessage[ContentType]
+ """A chat completion message generated by the model."""
+
+
+class ParsedChatCompletion(ChatCompletion, GenericModel, Generic[ContentType]):
+ choices: List[ParsedChatCompletionChoice[ContentType]] # type: ignore[assignment]
+ """A list of chat completion choices.
+
+ Can be more than one if `n` is greater than 1.
+ """
diff --git a/src/writerai/types/parsed_function_tool_call.py b/src/writerai/types/parsed_function_tool_call.py
new file mode 100644
index 00000000..86b59b71
--- /dev/null
+++ b/src/writerai/types/parsed_function_tool_call.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .shared.tool_call import Function, ToolCall
+
+__all__ = ["ParsedFunctionToolCall", "ParsedFunction"]
+
+# we need to disable this check because we're overriding properties
+# with subclasses of their types which is technically unsound as
+# properties can be mutated.
+# pyright: reportIncompatibleVariableOverride=false
+
+
+class ParsedFunction(Function):
+ parsed_arguments: Optional[object] = None
+ """
+ The arguments to call the function with.
+
+ If you used `writerai.pydantic_function_tool()` then this will be an
+ instance of the given `BaseModel`.
+
+ Otherwise, this will be the parsed JSON arguments.
+ """
+
+
+class ParsedFunctionToolCall(ToolCall):
+ function: ParsedFunction
+ """The function that the model called."""
diff --git a/src/writerai/types/shared/tool_call.py b/src/writerai/types/shared/tool_call.py
index d21d0cc1..33d5d929 100644
--- a/src/writerai/types/shared/tool_call.py
+++ b/src/writerai/types/shared/tool_call.py
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import Optional
+from typing_extensions import Literal
from ..._models import BaseModel
@@ -18,6 +19,6 @@ class ToolCall(BaseModel):
function: Function
- type: str
+ type: Literal["function"]
index: Optional[int] = None
diff --git a/src/writerai/types/shared/tool_call_streaming.py b/src/writerai/types/shared/tool_call_streaming.py
index 350001f2..ad4619e6 100644
--- a/src/writerai/types/shared/tool_call_streaming.py
+++ b/src/writerai/types/shared/tool_call_streaming.py
@@ -1,6 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import Optional
+from typing_extensions import Literal
from ..._models import BaseModel
@@ -20,4 +21,4 @@ class ToolCallStreaming(BaseModel):
function: Optional[Function] = None
- type: Optional[str] = None
+ type: Optional[Literal["function"]] = None
diff --git a/src/writerai/types/shared/tool_param.py b/src/writerai/types/shared/tool_param.py
index c8761bab..ccc6cd45 100644
--- a/src/writerai/types/shared/tool_param.py
+++ b/src/writerai/types/shared/tool_param.py
@@ -7,7 +7,17 @@
from ..._models import BaseModel
from .function_definition import FunctionDefinition
-__all__ = ["ToolParam", "FunctionTool", "GraphTool", "GraphToolFunction", "LlmTool", "LlmToolFunction"]
+__all__ = [
+ "ToolParam",
+ "FunctionTool",
+ "GraphTool",
+ "GraphToolFunction",
+ "LlmTool",
+ "LlmToolFunction",
+ "VisionTool",
+ "VisionToolFunction",
+ "VisionToolFunctionVariable",
+]
class FunctionTool(BaseModel):
@@ -49,8 +59,43 @@ class LlmTool(BaseModel):
function: LlmToolFunction
"""A tool that uses another Writer model to generate a response."""
- type: Optional[Literal["llm"]] = None
+ type: Literal["llm"]
"""The type of tool."""
-ToolParam: TypeAlias = Annotated[Union[FunctionTool, GraphTool, LlmTool], PropertyInfo(discriminator="type")]
+class VisionToolFunctionVariable(BaseModel):
+ file_id: str
+ """The File ID of the image to be analyzed.
+
+ The file must be uploaded to the Writer platform before you use it with the
+ Vision tool.
+ """
+
+ name: str
+ """The name of the file variable.
+
+ You must reference this name in the `message.content` field of the request to
+ the chat completions endpoint. Use double curly braces (`{{}}`) to reference the
+ file. For example,
+ `Describe the difference between the image {{image_1}} and the image {{image_2}}`.
+ """
+
+
+class VisionToolFunction(BaseModel):
+ model: str
+ """The model to be used for image analysis. Must be `palmyra-vision`."""
+
+ variables: List[VisionToolFunctionVariable]
+
+
+class VisionTool(BaseModel):
+ function: VisionToolFunction
+ """A tool that uses Palmyra Vision to analyze images."""
+
+ type: Literal["vision"]
+ """The type of tool."""
+
+
+ToolParam: TypeAlias = Annotated[
+ Union[FunctionTool, GraphTool, LlmTool, VisionTool], PropertyInfo(discriminator="type")
+]
diff --git a/src/writerai/types/shared_params/__init__.py b/src/writerai/types/shared_params/__init__.py
index 1bd920cf..1067d9ae 100644
--- a/src/writerai/types/shared_params/__init__.py
+++ b/src/writerai/types/shared_params/__init__.py
@@ -5,6 +5,10 @@
from .graph_data import GraphData as GraphData
from .tool_param import ToolParam as ToolParam
from .function_params import FunctionParams as FunctionParams
+from .response_format import ResponseFormat as ResponseFormat
from .tool_choice_string import ToolChoiceString as ToolChoiceString
from .function_definition import FunctionDefinition as FunctionDefinition
+from .response_format_text import ResponseFormatText as ResponseFormatText
from .tool_choice_json_object import ToolChoiceJsonObject as ToolChoiceJsonObject
+from .response_format_json_object import ResponseFormatJSONObject as ResponseFormatJSONObject
+from .response_format_json_schema import ResponseFormatJSONSchema as ResponseFormatJSONSchema
diff --git a/src/writerai/types/shared_params/function_parameters.py b/src/writerai/types/shared_params/function_parameters.py
new file mode 100644
index 00000000..45fc742d
--- /dev/null
+++ b/src/writerai/types/shared_params/function_parameters.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["FunctionParameters"]
+
+FunctionParameters: TypeAlias = Dict[str, object]
diff --git a/src/writerai/types/shared_params/response_format.py b/src/writerai/types/shared_params/response_format.py
new file mode 100644
index 00000000..c9b058f8
--- /dev/null
+++ b/src/writerai/types/shared_params/response_format.py
@@ -0,0 +1,10 @@
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .response_format_text import ResponseFormatText
+from .response_format_json_object import ResponseFormatJSONObject
+from .response_format_json_schema import ResponseFormatJSONSchema
+
+__all__ = ["ResponseFormat"]
+
+ResponseFormat: TypeAlias = Union[ResponseFormatText, ResponseFormatJSONObject, ResponseFormatJSONSchema]
diff --git a/src/writerai/types/shared_params/response_format_json_object.py b/src/writerai/types/shared_params/response_format_json_object.py
new file mode 100644
index 00000000..8419c6cb
--- /dev/null
+++ b/src/writerai/types/shared_params/response_format_json_object.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ResponseFormatJSONObject"]
+
+
+class ResponseFormatJSONObject(TypedDict, total=False):
+ type: Required[Literal["json_object"]]
+ """The type of response format being defined: `json_object`"""
diff --git a/src/writerai/types/shared_params/response_format_json_schema.py b/src/writerai/types/shared_params/response_format_json_schema.py
new file mode 100644
index 00000000..b05b5822
--- /dev/null
+++ b/src/writerai/types/shared_params/response_format_json_schema.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ResponseFormatJSONSchema", "JSONSchema"]
+
+
+class JSONSchema(TypedDict, total=False):
+ name: Required[str]
+ """The name of the response format.
+
+ Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length
+ of 64.
+ """
+
+ description: str
+ """
+ A description of what the response format is for, used by the model to determine
+ how to respond in the format.
+ """
+
+ schema: Dict[str, object]
+ """The schema for the response format, described as a JSON Schema object."""
+
+ strict: Optional[bool]
+ """Whether to enable strict schema adherence when generating the output.
+
+ If set to true, the model will always follow the exact schema defined in the
+ `schema` field. Only a subset of JSON Schema is supported when `strict` is
+ `true`.
+ """
+
+
+class ResponseFormatJSONSchema(TypedDict, total=False):
+ json_schema: Required[JSONSchema]
+
+ type: Required[Literal["json_schema"]]
+ """The type of response format being defined: `json_schema`"""
diff --git a/src/writerai/types/shared_params/response_format_text.py b/src/writerai/types/shared_params/response_format_text.py
new file mode 100644
index 00000000..5bec7fc5
--- /dev/null
+++ b/src/writerai/types/shared_params/response_format_text.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ResponseFormatText"]
+
+
+class ResponseFormatText(TypedDict, total=False):
+ type: Required[Literal["text"]]
+ """The type of response format being defined: `text`"""
diff --git a/src/writerai/types/shared_params/tool_call.py b/src/writerai/types/shared_params/tool_call.py
index 5a81f596..cb736487 100644
--- a/src/writerai/types/shared_params/tool_call.py
+++ b/src/writerai/types/shared_params/tool_call.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing_extensions import Required, TypedDict
+from typing_extensions import Literal, Required, TypedDict
__all__ = ["ToolCall", "Function"]
@@ -18,6 +18,6 @@ class ToolCall(TypedDict, total=False):
function: Required[Function]
- type: Required[str]
+ type: Required[Literal["function"]]
index: int
diff --git a/src/writerai/types/shared_params/tool_param.py b/src/writerai/types/shared_params/tool_param.py
index 827e0820..7723c2f3 100644
--- a/src/writerai/types/shared_params/tool_param.py
+++ b/src/writerai/types/shared_params/tool_param.py
@@ -2,12 +2,22 @@
from __future__ import annotations
-from typing import List, Union
+from typing import List, Union, Iterable
from typing_extensions import Literal, Required, TypeAlias, TypedDict
from .function_definition import FunctionDefinition
-__all__ = ["ToolParam", "FunctionTool", "GraphTool", "GraphToolFunction", "LlmTool", "LlmToolFunction"]
+__all__ = [
+ "ToolParam",
+ "FunctionTool",
+ "GraphTool",
+ "GraphToolFunction",
+ "LlmTool",
+ "LlmToolFunction",
+ "VisionTool",
+ "VisionToolFunction",
+ "VisionToolFunctionVariable",
+]
class FunctionTool(TypedDict, total=False):
@@ -49,8 +59,41 @@ class LlmTool(TypedDict, total=False):
function: Required[LlmToolFunction]
"""A tool that uses another Writer model to generate a response."""
- type: Literal["llm"]
+ type: Required[Literal["llm"]]
"""The type of tool."""
-ToolParam: TypeAlias = Union[FunctionTool, GraphTool, LlmTool]
+class VisionToolFunctionVariable(TypedDict, total=False):
+ file_id: Required[str]
+ """The File ID of the image to be analyzed.
+
+ The file must be uploaded to the Writer platform before you use it with the
+ Vision tool.
+ """
+
+ name: Required[str]
+ """The name of the file variable.
+
+ You must reference this name in the `message.content` field of the request to
+ the chat completions endpoint. Use double curly braces (`{{}}`) to reference the
+ file. For example,
+ `Describe the difference between the image {{image_1}} and the image {{image_2}}`.
+ """
+
+
+class VisionToolFunction(TypedDict, total=False):
+ model: Required[str]
+ """The model to be used for image analysis. Must be `palmyra-vision`."""
+
+ variables: Required[Iterable[VisionToolFunctionVariable]]
+
+
+class VisionTool(TypedDict, total=False):
+ function: Required[VisionToolFunction]
+ """A tool that uses Palmyra Vision to analyze images."""
+
+ type: Required[Literal["vision"]]
+ """The type of tool."""
+
+
+ToolParam: TypeAlias = Union[FunctionTool, GraphTool, LlmTool, VisionTool]
diff --git a/src/writerai/types/vision_analyze_params.py b/src/writerai/types/vision_analyze_params.py
new file mode 100644
index 00000000..96b50b99
--- /dev/null
+++ b/src/writerai/types/vision_analyze_params.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, TypedDict
+
+__all__ = ["VisionAnalyzeParams", "Variable"]
+
+
+class VisionAnalyzeParams(TypedDict, total=False):
+ model: Required[str]
+ """The model to be used for image analysis.
+
+ Currently only supports `palmyra-vision`.
+ """
+
+ prompt: Required[str]
+ """The prompt to use for the image analysis.
+
+ The prompt must include the name of each image variable, surrounded by double
+ curly braces (`{{}}`). For example,
+ `Describe the difference between the image {{image_1}} and the image {{image_2}}`.
+ """
+
+ variables: Required[Iterable[Variable]]
+
+
+class Variable(TypedDict, total=False):
+ file_id: Required[str]
+ """The File ID of the image to be analyzed.
+
+ The file must be uploaded to the Writer platform before it can be used in a
+ vision request.
+ """
+
+ name: Required[str]
+ """The name of the file variable.
+
+ You must reference this name in the prompt with double curly braces (`{{}}`).
+ For example,
+ `Describe the difference between the image {{image_1}} and the image {{image_2}}`.
+ """
diff --git a/src/writerai/types/vision_response.py b/src/writerai/types/vision_response.py
new file mode 100644
index 00000000..0fceb4b0
--- /dev/null
+++ b/src/writerai/types/vision_response.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+from .._models import BaseModel
+
+__all__ = ["VisionResponse"]
+
+
+class VisionResponse(BaseModel):
+ data: str
+ """The result of the image analysis."""
diff --git a/tests/api_resources/test_chat.py b/tests/api_resources/test_chat.py
index a1c804be..c035240e 100644
--- a/tests/api_resources/test_chat.py
+++ b/tests/api_resources/test_chat.py
@@ -63,7 +63,7 @@ def test_method_chat_with_all_params_overload_1(self, client: Writer) -> None:
"arguments": "arguments",
"name": "name",
},
- "type": "type",
+ "type": "function",
"index": 0,
}
],
@@ -165,7 +165,7 @@ def test_method_chat_with_all_params_overload_2(self, client: Writer) -> None:
"arguments": "arguments",
"name": "name",
},
- "type": "type",
+ "type": "function",
"index": 0,
}
],
@@ -271,7 +271,7 @@ async def test_method_chat_with_all_params_overload_1(self, async_client: AsyncW
"arguments": "arguments",
"name": "name",
},
- "type": "type",
+ "type": "function",
"index": 0,
}
],
@@ -373,7 +373,7 @@ async def test_method_chat_with_all_params_overload_2(self, async_client: AsyncW
"arguments": "arguments",
"name": "name",
},
- "type": "type",
+ "type": "function",
"index": 0,
}
],
diff --git a/tests/api_resources/test_graphs.py b/tests/api_resources/test_graphs.py
index 510af9e6..22c7b21b 100644
--- a/tests/api_resources/test_graphs.py
+++ b/tests/api_resources/test_graphs.py
@@ -261,6 +261,14 @@ def test_path_params_add_file_to_graph(self, client: Writer) -> None:
@parametrize
def test_method_question_overload_1(self, client: Writer) -> None:
+ graph = client.graphs.question(
+ graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+ question="question",
+ )
+ assert_matches_type(Question, graph, path=["response"])
+
+ @parametrize
+ def test_method_question_with_all_params_overload_1(self, client: Writer) -> None:
graph = client.graphs.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
@@ -274,8 +282,6 @@ def test_raw_response_question_overload_1(self, client: Writer) -> None:
response = client.graphs.with_raw_response.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
- stream=False,
- subqueries=True,
)
assert response.is_closed is True
@@ -288,8 +294,6 @@ def test_streaming_response_question_overload_1(self, client: Writer) -> None:
with client.graphs.with_streaming_response.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
- stream=False,
- subqueries=True,
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -301,6 +305,15 @@ def test_streaming_response_question_overload_1(self, client: Writer) -> None:
@parametrize
def test_method_question_overload_2(self, client: Writer) -> None:
+ graph_stream = client.graphs.question(
+ graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+ question="question",
+ stream=True,
+ )
+ graph_stream.response.close()
+
+ @parametrize
+ def test_method_question_with_all_params_overload_2(self, client: Writer) -> None:
graph_stream = client.graphs.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
@@ -315,7 +328,6 @@ def test_raw_response_question_overload_2(self, client: Writer) -> None:
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
stream=True,
- subqueries=True,
)
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -328,7 +340,6 @@ def test_streaming_response_question_overload_2(self, client: Writer) -> None:
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
stream=True,
- subqueries=True,
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -625,6 +636,14 @@ async def test_path_params_add_file_to_graph(self, async_client: AsyncWriter) ->
@parametrize
async def test_method_question_overload_1(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.question(
+ graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+ question="question",
+ )
+ assert_matches_type(Question, graph, path=["response"])
+
+ @parametrize
+ async def test_method_question_with_all_params_overload_1(self, async_client: AsyncWriter) -> None:
graph = await async_client.graphs.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
@@ -638,8 +657,6 @@ async def test_raw_response_question_overload_1(self, async_client: AsyncWriter)
response = await async_client.graphs.with_raw_response.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
- stream=False,
- subqueries=True,
)
assert response.is_closed is True
@@ -652,8 +669,6 @@ async def test_streaming_response_question_overload_1(self, async_client: AsyncW
async with async_client.graphs.with_streaming_response.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
- stream=False,
- subqueries=True,
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -665,6 +680,15 @@ async def test_streaming_response_question_overload_1(self, async_client: AsyncW
@parametrize
async def test_method_question_overload_2(self, async_client: AsyncWriter) -> None:
+ graph_stream = await async_client.graphs.question(
+ graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
+ question="question",
+ stream=True,
+ )
+ await graph_stream.response.aclose()
+
+ @parametrize
+ async def test_method_question_with_all_params_overload_2(self, async_client: AsyncWriter) -> None:
graph_stream = await async_client.graphs.question(
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
@@ -679,7 +703,6 @@ async def test_raw_response_question_overload_2(self, async_client: AsyncWriter)
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
stream=True,
- subqueries=True,
)
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -692,7 +715,6 @@ async def test_streaming_response_question_overload_2(self, async_client: AsyncW
graph_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"],
question="question",
stream=True,
- subqueries=True,
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_vision.py b/tests/api_resources/test_vision.py
new file mode 100644
index 00000000..20f5d72f
--- /dev/null
+++ b/tests/api_resources/test_vision.py
@@ -0,0 +1,150 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from writerai import Writer, AsyncWriter
+from tests.utils import assert_matches_type
+from writerai.types import VisionResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVision:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_analyze(self, client: Writer) -> None:
+ vision = client.vision.analyze(
+ model="palmyra-vision",
+ prompt="Describe the difference between the image {{image_1}} and the image {{image_2}}.",
+ variables=[
+ {
+ "file_id": "f1234",
+ "name": "image_1",
+ },
+ {
+ "file_id": "f9876",
+ "name": "image_2",
+ },
+ ],
+ )
+ assert_matches_type(VisionResponse, vision, path=["response"])
+
+ @parametrize
+ def test_raw_response_analyze(self, client: Writer) -> None:
+ response = client.vision.with_raw_response.analyze(
+ model="palmyra-vision",
+ prompt="Describe the difference between the image {{image_1}} and the image {{image_2}}.",
+ variables=[
+ {
+ "file_id": "f1234",
+ "name": "image_1",
+ },
+ {
+ "file_id": "f9876",
+ "name": "image_2",
+ },
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vision = response.parse()
+ assert_matches_type(VisionResponse, vision, path=["response"])
+
+ @parametrize
+ def test_streaming_response_analyze(self, client: Writer) -> None:
+ with client.vision.with_streaming_response.analyze(
+ model="palmyra-vision",
+ prompt="Describe the difference between the image {{image_1}} and the image {{image_2}}.",
+ variables=[
+ {
+ "file_id": "f1234",
+ "name": "image_1",
+ },
+ {
+ "file_id": "f9876",
+ "name": "image_2",
+ },
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vision = response.parse()
+ assert_matches_type(VisionResponse, vision, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncVision:
+ parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ async def test_method_analyze(self, async_client: AsyncWriter) -> None:
+ vision = await async_client.vision.analyze(
+ model="palmyra-vision",
+ prompt="Describe the difference between the image {{image_1}} and the image {{image_2}}.",
+ variables=[
+ {
+ "file_id": "f1234",
+ "name": "image_1",
+ },
+ {
+ "file_id": "f9876",
+ "name": "image_2",
+ },
+ ],
+ )
+ assert_matches_type(VisionResponse, vision, path=["response"])
+
+ @parametrize
+ async def test_raw_response_analyze(self, async_client: AsyncWriter) -> None:
+ response = await async_client.vision.with_raw_response.analyze(
+ model="palmyra-vision",
+ prompt="Describe the difference between the image {{image_1}} and the image {{image_2}}.",
+ variables=[
+ {
+ "file_id": "f1234",
+ "name": "image_1",
+ },
+ {
+ "file_id": "f9876",
+ "name": "image_2",
+ },
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vision = await response.parse()
+ assert_matches_type(VisionResponse, vision, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_analyze(self, async_client: AsyncWriter) -> None:
+ async with async_client.vision.with_streaming_response.analyze(
+ model="palmyra-vision",
+ prompt="Describe the difference between the image {{image_1}} and the image {{image_2}}.",
+ variables=[
+ {
+ "file_id": "f1234",
+ "name": "image_1",
+ },
+ {
+ "file_id": "f9876",
+ "name": "image_2",
+ },
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vision = await response.parse()
+ assert_matches_type(VisionResponse, vision, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/lib/streaming/__init__.py b/tests/lib/streaming/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/lib/streaming/_utils.py b/tests/lib/streaming/_utils.py
new file mode 100644
index 00000000..af08db41
--- /dev/null
+++ b/tests/lib/streaming/_utils.py
@@ -0,0 +1,54 @@
+from __future__ import annotations
+
+import inspect
+from typing import Any, Iterable
+from typing_extensions import TypeAlias
+
+import pytest
+import pydantic
+
+from ...utils import rich_print_str
+
+ReprArgs: TypeAlias = "Iterable[tuple[str | None, Any]]"
+
+
+def print_obj(obj: object, monkeypatch: pytest.MonkeyPatch) -> str:
+ """Pretty print an object to a string"""
+
+ # monkeypatch pydantic model printing so that model fields
+ # are always printed in the same order so we can reliably
+ # use this for snapshot tests
+ original_repr = pydantic.BaseModel.__repr_args__
+
+ def __repr_args__(self: pydantic.BaseModel) -> ReprArgs:
+ return sorted(original_repr(self), key=lambda arg: arg[0] or arg)
+
+ with monkeypatch.context() as m:
+ m.setattr(pydantic.BaseModel, "__repr_args__", __repr_args__)
+
+ string = rich_print_str(obj)
+
+ # we remove all `fn_name..` occurences
+ # so that we can share the same snapshots between
+ # pydantic v1 and pydantic v2 as their output for
+ # generic models differs, e.g.
+ #
+ # v2: `ParsedChatCompletion[test_parse_pydantic_model..Location]`
+ # v1: `ParsedChatCompletion[Location]`
+ return clear_locals(string, stacklevel=2)
+
+
+def get_caller_name(*, stacklevel: int = 1) -> str:
+ frame = inspect.currentframe()
+ assert frame is not None
+
+ for i in range(stacklevel):
+ frame = frame.f_back
+ assert frame is not None, f"no {i}th frame"
+
+ return frame.f_code.co_name
+
+
+def clear_locals(string: str, *, stacklevel: int) -> str:
+ caller = get_caller_name(stacklevel=stacklevel + 1)
+ return string.replace(f"{caller}..", "")
diff --git a/tests/lib/streaming/test_chat_completions_streaming.py b/tests/lib/streaming/test_chat_completions_streaming.py
new file mode 100644
index 00000000..e3735c57
--- /dev/null
+++ b/tests/lib/streaming/test_chat_completions_streaming.py
@@ -0,0 +1,1062 @@
+from __future__ import annotations
+
+import os
+from typing import Any, Generic, Callable, Iterator, overload
+from typing_extensions import Literal, TypeVar
+
+# import rich
+import httpx
+import pytest
+from respx import MockRouter
+from pydantic import BaseModel
+from inline_snapshot import external, snapshot, outsource
+
+import writerai
+from writerai import Writer, AsyncWriter
+from writerai._utils import assert_signatures_in_sync
+
+# from writerai._compat import model_copy
+from writerai.lib.streaming.chat import (
+ ContentDoneEvent,
+ ChatCompletionStream,
+ ChatCompletionStreamEvent,
+ ChatCompletionStreamManager,
+ # ParsedChatCompletionSnapshot,
+)
+from writerai.lib._parsing._completions import ResponseFormatT
+
+from ._utils import print_obj
+from ...conftest import base_url
+
+_T = TypeVar("_T")
+
+# all the snapshots in this file are auto-generated from the live API
+#
+# you can update them with
+#
+# `WRITER_LIVE=1 pytest --inline-snapshot=fix`
+
+
+@pytest.mark.respx(base_url=base_url)
+def test_parse_nothing(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+ listener = _make_stream_snapshot_request(
+ lambda c: c.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather like in SF?",
+ },
+ ],
+ ),
+ content_snapshot=snapshot(external("e2aad469b71d*.writer")),
+ openai_content_snapshot=snapshot(external("e2aad469b71d*.openai")),
+ mock_client=client,
+ respx_mock=respx_mock,
+ )
+
+ assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot(
+ """\
+[
+ ParsedChatCompletionChoice[NoneType](
+ finish_reason='stop',
+ index=0,
+ logprobs=None,
+ message=ParsedChatCompletionMessage[NoneType](
+ content="I'm unable to provide real-time weather updates. To get the current weather in San Francisco, I
+recommend checking a reliable weather website or a weather app.",
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal=None,
+ role='assistant',
+ tool_calls=[]
+ )
+ )
+]
+"""
+ )
+ assert print_obj(listener.get_event_by_type("content.done"), monkeypatch) == snapshot(
+ """\
+ContentDoneEvent[NoneType](
+ content="I'm unable to provide real-time weather updates. To get the current weather in San Francisco, I recommend
+checking a reliable weather website or a weather app.",
+ parsed=None,
+ type='content.done'
+)
+"""
+ )
+
+
+# @pytest.mark.respx(base_url=base_url)
+# def test_parse_pydantic_model(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+# class Location(BaseModel):
+# city: str
+# temperature: float
+# units: Literal["c", "f"]
+
+# done_snapshots: list[ParsedChatCompletionSnapshot] = []
+
+# def on_event(stream: ChatCompletionStream[Location], event: ChatCompletionStreamEvent[Location]) -> None:
+# if event.type == "content.done":
+# done_snapshots.append(model_copy(stream.current_completion_snapshot, deep=True))
+
+# listener = _make_stream_snapshot_request(
+# lambda c: c.chat.stream(
+# model="palmyra-x-003-instruct",
+# messages=[
+# {
+# "role": "user",
+# "content": "What's the weather like in SF?",
+# },
+# ],
+# # response_format=Location,
+# ),
+# content_snapshot=snapshot(external("7e5ea4d12e7c*_bin")),
+# mock_client=client,
+# respx_mock=respx_mock,
+# on_event=on_event,
+# )
+
+# assert len(done_snapshots) == 1
+# assert isinstance(done_snapshots[0].choices[0].message.parsed, Location)
+
+# for event in reversed(listener.events):
+# if event.type == "content.delta":
+# data = cast(Any, event.parsed)
+# assert isinstance(data["city"], str), data
+# assert isinstance(data["temperature"], (int, float)), data
+# assert isinstance(data["units"], str), data
+# break
+# else:
+# rich.print(listener.events)
+# raise AssertionError("Did not find a `content.delta` event")
+
+# assert print_obj(listener.stream.get_final_completion(), monkeypatch) == snapshot(
+# """\
+# ParsedChatCompletion[Location](
+# choices=[
+# ParsedChatCompletionChoice[Location](
+# finish_reason='stop',
+# index=0,
+# logprobs=None,
+# message=ParsedChatCompletionMessage[Location](
+# content='{"city":"San Francisco","temperature":61,"units":"f"}',
+# graph_data=None,
+# parsed=Location(city='San Francisco', temperature=61.0, units='f'),
+# refusal=None,
+# role='assistant',
+# tool_calls=[]
+# )
+# )
+# ],
+# created=1727346169,
+# id='chatcmpl-ABfw1e5abtU8OwGr15vOreYVb2MiF',
+# model='palmyra-x-003-instruct',
+# object='chat.completion',
+# service_tier=None,
+# system_fingerprint='fp_5050236cbd',
+# usage=CompletionUsage(
+# completion_tokens=14,
+# completion_tokens_details=CompletionTokensDetails(reasoning_tokens=0),
+# prompt_tokens=79,
+# total_tokens=93
+# )
+# )
+# """
+# )
+# assert print_obj(listener.get_event_by_type("content.done"), monkeypatch) == snapshot(
+# """\
+# ContentDoneEvent[Location](
+# content='{"city":"San Francisco","temperature":61,"units":"f"}',
+# parsed=Location(city='San Francisco', temperature=61.0, units='f'),
+# type='content.done'
+# )
+# """
+# )
+
+
+# @pytest.mark.respx(base_url=base_url)
+# def test_parse_pydantic_model_multiple_choices(
+# client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
+# ) -> None:
+# class Location(BaseModel):
+# city: str
+# temperature: float
+# units: Literal["c", "f"]
+
+# listener = _make_stream_snapshot_request(
+# lambda c: c.chat.stream(
+# model="palmyra-x-003-instruct",
+# messages=[
+# {
+# "role": "user",
+# "content": "What's the weather like in SF?",
+# },
+# ],
+# n=3,
+# response_format=Location,
+# ),
+# content_snapshot=snapshot(external("a491adda08c3*_bin")),
+# mock_client=client,
+# respx_mock=respx_mock,
+# )
+
+# assert [e.type for e in listener.events] == snapshot(
+# [
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.delta",
+# "chunk",
+# "content.done",
+# "chunk",
+# "content.done",
+# "chunk",
+# "content.done",
+# "chunk",
+# ]
+# )
+# assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot(
+# """\
+# [
+# ParsedChatCompletionChoice[Location](
+# finish_reason='stop',
+# index=0,
+# logprobs=None,
+# message=ParsedChatCompletionMessage[Location](
+# content='{"city":"San Francisco","temperature":65,"units":"f"}',
+# graph_data=None,
+# parsed=Location(city='San Francisco', temperature=65.0, units='f'),
+# refusal=None,
+# role='assistant',
+# tool_calls=[]
+# )
+# ),
+# ParsedChatCompletionChoice[Location](
+# finish_reason='stop',
+# index=1,
+# logprobs=None,
+# message=ParsedChatCompletionMessage[Location](
+# content='{"city":"San Francisco","temperature":61,"units":"f"}',
+# graph_data=None,
+# parsed=Location(city='San Francisco', temperature=61.0, units='f'),
+# refusal=None,
+# role='assistant',
+# tool_calls=[]
+# )
+# ),
+# ParsedChatCompletionChoice[Location](
+# finish_reason='stop',
+# index=2,
+# logprobs=None,
+# message=ParsedChatCompletionMessage[Location](
+# content='{"city":"San Francisco","temperature":59,"units":"f"}',
+# graph_data=None,
+# parsed=Location(city='San Francisco', temperature=59.0, units='f'),
+# refusal=None,
+# role='assistant',
+# tool_calls=[]
+# )
+# )
+# ]
+# """
+# )
+
+
+# @pytest.mark.respx(base_url=base_url)
+# def test_parse_max_tokens_reached(client: Writer, respx_mock: MockRouter) -> None:
+# class Location(BaseModel):
+# city: str
+# temperature: float
+# units: Literal["c", "f"]
+#
+# with pytest.raises(writerai.LengthFinishReasonError):
+# _make_stream_snapshot_request(
+# lambda c: c.chat.stream(
+# model="palmyra-x-003-instruct",
+# messages=[
+# {
+# "role": "user",
+# "content": "What's the weather like in SF?",
+# },
+# ],
+# max_tokens=1,
+# response_format=Location,
+# ),
+# content_snapshot=snapshot(external("4cc50a6135d2*_bin")),
+# mock_client=client,
+# respx_mock=respx_mock,
+# )
+
+
+# @pytest.mark.respx(base_url=base_url)
+# def test_parse_pydantic_model_refusal(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+# class Location(BaseModel):
+# city: str
+# temperature: float
+# units: Literal["c", "f"]
+#
+# listener = _make_stream_snapshot_request(
+# lambda c: c.chat.stream(
+# model="palmyra-x-003-instruct",
+# messages=[
+# {
+# "role": "user",
+# "content": "How do I make anthrax?",
+# },
+# ],
+# response_format=Location,
+# ),
+# content_snapshot=snapshot(external("173417d55340*_bin")),
+# mock_client=client,
+# respx_mock=respx_mock,
+# )
+#
+# assert print_obj(listener.get_event_by_type("refusal.done"), monkeypatch) == snapshot("""\
+# RefusalDoneEvent(refusal="I'm sorry, I can't assist with that request.", type='refusal.done')
+# """)
+#
+# assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot(
+# """\
+# [
+# ParsedChatCompletionChoice[Location](
+# finish_reason='stop',
+# index=0,
+# logprobs=None,
+# message=ParsedChatCompletionMessage[Location](
+# content=None,
+# graph_data=None,
+# parsed=None,
+# refusal="I'm sorry, I can't assist with that request.",
+# role='assistant',
+# tool_calls=[]
+# )
+# )
+# ]
+# """
+# )
+
+
+@pytest.mark.respx(base_url=base_url)
+def test_content_logprobs_events(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+ listener = _make_stream_snapshot_request(
+ lambda c: c.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[
+ {
+ "role": "user",
+ "content": "Say foo",
+ },
+ ],
+ logprobs=True,
+ ),
+ content_snapshot=snapshot(external("83b060bae42e*.writer")),
+ openai_content_snapshot=snapshot(external("83b060bae42e*.openai")),
+ mock_client=client,
+ respx_mock=respx_mock,
+ )
+
+ assert print_obj([e for e in listener.events if e.type.startswith("logprobs")], monkeypatch) == snapshot("""\
+[
+ LogprobsContentDeltaEvent(
+ content=[LogprobsToken(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[])],
+ snapshot=[LogprobsToken(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[])],
+ type='logprobs.content.delta'
+ ),
+ LogprobsContentDeltaEvent(
+ content=[LogprobsToken(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[])],
+ snapshot=[
+ LogprobsToken(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]),
+ LogprobsToken(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[])
+ ],
+ type='logprobs.content.delta'
+ ),
+ LogprobsContentDoneEvent(
+ content=[
+ LogprobsToken(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]),
+ LogprobsToken(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[])
+ ],
+ type='logprobs.content.done'
+ )
+]
+""")
+
+ assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot("""\
+[
+ ParsedChatCompletionChoice[NoneType](
+ finish_reason='stop',
+ index=0,
+ logprobs=Logprobs(
+ content=[
+ LogprobsToken(bytes=[70, 111, 111], logprob=-0.0025094282, token='Foo', top_logprobs=[]),
+ LogprobsToken(bytes=[33], logprob=-0.26638845, token='!', top_logprobs=[])
+ ],
+ refusal=None
+ ),
+ message=ParsedChatCompletionMessage[NoneType](
+ content='Foo!',
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal=None,
+ role='assistant',
+ tool_calls=[]
+ )
+ )
+]
+""")
+
+
+@pytest.mark.respx(base_url=base_url)
+def test_refusal_logprobs_events(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+ # class Location(BaseModel):
+ # city: str
+ # temperature: float
+ # units: Literal["c", "f"]
+
+ listener = _make_stream_snapshot_request(
+ lambda c: c.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[
+ {
+ "role": "user",
+ "content": "How do I make anthrax?",
+ },
+ ],
+ logprobs=True,
+ # response_format=Location,
+ ),
+ content_snapshot=snapshot(external("569c877e6942*.writer")),
+ openai_content_snapshot=snapshot(external("569c877e6942*.openai")),
+ mock_client=client,
+ respx_mock=respx_mock,
+ )
+
+ assert print_obj([e.type for e in listener.events if e.type.startswith("logprobs")], monkeypatch) == snapshot("""\
+[
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.delta',
+ 'logprobs.refusal.done'
+]
+""")
+
+ assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot("""\
+[
+ ParsedChatCompletionChoice[NoneType](
+ finish_reason='stop',
+ index=0,
+ logprobs=Logprobs(
+ content=None,
+ refusal=[
+ LogprobsToken(bytes=[73, 39, 109], logprob=-0.0012038043, token="I'm", top_logprobs=[]),
+ LogprobsToken(bytes=[32, 118, 101, 114, 121], logprob=-0.8438816, token=' very', top_logprobs=[]),
+ LogprobsToken(
+ bytes=[32, 115, 111, 114, 114, 121],
+ logprob=-3.4121115e-06,
+ token=' sorry',
+ top_logprobs=[]
+ ),
+ LogprobsToken(bytes=[44], logprob=-3.3809047e-05, token=',', top_logprobs=[]),
+ LogprobsToken(bytes=[32, 98, 117, 116], logprob=-0.038048144, token=' but', top_logprobs=[]),
+ LogprobsToken(bytes=[32, 73], logprob=-0.0016109125, token=' I', top_logprobs=[]),
+ LogprobsToken(
+ bytes=[32, 99, 97, 110, 39, 116],
+ logprob=-0.0073532974,
+ token=" can't",
+ top_logprobs=[]
+ ),
+ LogprobsToken(
+ bytes=[32, 97, 115, 115, 105, 115, 116],
+ logprob=-0.0020837625,
+ token=' assist',
+ top_logprobs=[]
+ ),
+ LogprobsToken(bytes=[32, 119, 105, 116, 104], logprob=-0.00318354, token=' with', top_logprobs=[]),
+ LogprobsToken(bytes=[32, 116, 104, 97, 116], logprob=-0.0017186158, token=' that', top_logprobs=[]),
+ LogprobsToken(bytes=[46], logprob=-0.57687104, token='.', top_logprobs=[])
+ ]
+ ),
+ message=ParsedChatCompletionMessage[NoneType](
+ content=None,
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal="I'm very sorry, but I can't assist with that.",
+ role='assistant',
+ tool_calls=[]
+ )
+ )
+]
+""")
+
+
+@pytest.mark.respx(base_url=base_url)
+def test_parse_pydantic_tool(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+ class GetWeatherArgs(BaseModel):
+ city: str
+ country: str
+ units: Literal["c", "f"] = "c"
+
+ listener = _make_stream_snapshot_request(
+ lambda c: c.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather like in Edinburgh?",
+ },
+ ],
+ tools=[
+ writerai.pydantic_function_tool(GetWeatherArgs),
+ ],
+ ),
+ content_snapshot=snapshot(external("c6aa7e397b71*.writer")),
+ openai_content_snapshot=snapshot(external("c6aa7e397b71*.openai")),
+ mock_client=client,
+ respx_mock=respx_mock,
+ )
+
+ assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot(
+ """\
+[
+ ParsedChatCompletionChoice[object](
+ finish_reason='tool_calls',
+ index=0,
+ logprobs=None,
+ message=ParsedChatCompletionMessage[object](
+ content=None,
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal=None,
+ role='assistant',
+ tool_calls=[
+ ParsedFunctionToolCall(
+ function=ParsedFunction(
+ arguments='{"city":"Edinburgh","country":"UK","units":"c"}',
+ name='GetWeatherArgs',
+ parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c')
+ ),
+ id='call_c91SqDXlYFuETYv8mUHzz6pp',
+ index=0,
+ type='function'
+ )
+ ]
+ )
+ )
+]
+"""
+ )
+
+ assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot(
+ """\
+[
+ ParsedChatCompletionChoice[NoneType](
+ finish_reason='tool_calls',
+ index=0,
+ logprobs=None,
+ message=ParsedChatCompletionMessage[NoneType](
+ content=None,
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal=None,
+ role='assistant',
+ tool_calls=[
+ ParsedFunctionToolCall(
+ function=ParsedFunction(
+ arguments='{"city":"Edinburgh","country":"UK","units":"c"}',
+ name='GetWeatherArgs',
+ parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c')
+ ),
+ id='call_c91SqDXlYFuETYv8mUHzz6pp',
+ index=0,
+ type='function'
+ )
+ ]
+ )
+ )
+]
+"""
+ )
+
+
+@pytest.mark.respx(base_url=base_url)
+def test_parse_multiple_pydantic_tools(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+ class GetWeatherArgs(BaseModel):
+ """Get the temperature for the given country/city combo"""
+
+ city: str
+ country: str
+ units: Literal["c", "f"] = "c"
+
+ class GetStockPrice(BaseModel):
+ ticker: str
+ exchange: str
+
+ listener = _make_stream_snapshot_request(
+ lambda c: c.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather like in Edinburgh?",
+ },
+ {
+ "role": "user",
+ "content": "What's the price of AAPL?",
+ },
+ ],
+ tools=[
+ writerai.pydantic_function_tool(GetWeatherArgs),
+ writerai.pydantic_function_tool(
+ GetStockPrice, name="get_stock_price", description="Fetch the latest price for a given ticker"
+ ),
+ ],
+ ),
+ content_snapshot=snapshot(external("f82268f2fefd*.writer")),
+ openai_content_snapshot=snapshot(external("f82268f2fefd*.openai")),
+ mock_client=client,
+ respx_mock=respx_mock,
+ )
+
+ assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot(
+ """\
+[
+ ParsedChatCompletionChoice[object](
+ finish_reason='tool_calls',
+ index=0,
+ logprobs=None,
+ message=ParsedChatCompletionMessage[object](
+ content=None,
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal=None,
+ role='assistant',
+ tool_calls=[
+ ParsedFunctionToolCall(
+ function=ParsedFunction(
+ arguments='{"city": "Edinburgh", "country": "GB", "units": "c"}',
+ name='GetWeatherArgs',
+ parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c')
+ ),
+ id='call_JMW1whyEaYG438VE1OIflxA2',
+ index=0,
+ type='function'
+ ),
+ ParsedFunctionToolCall(
+ function=ParsedFunction(
+ arguments='{"ticker": "AAPL", "exchange": "NASDAQ"}',
+ name='get_stock_price',
+ parsed_arguments=GetStockPrice(exchange='NASDAQ', ticker='AAPL')
+ ),
+ id='call_DNYTawLBoN8fj3KN6qU9N1Ou',
+ index=1,
+ type='function'
+ )
+ ]
+ )
+ )
+]
+"""
+ )
+ completion = listener.stream.get_final_completion()
+ assert print_obj(completion.choices[0].message.tool_calls, monkeypatch) == snapshot(
+ """\
+[
+ ParsedFunctionToolCall(
+ function=ParsedFunction(
+ arguments='{"city": "Edinburgh", "country": "GB", "units": "c"}',
+ name='GetWeatherArgs',
+ parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c')
+ ),
+ id='call_JMW1whyEaYG438VE1OIflxA2',
+ index=0,
+ type='function'
+ ),
+ ParsedFunctionToolCall(
+ function=ParsedFunction(
+ arguments='{"ticker": "AAPL", "exchange": "NASDAQ"}',
+ name='get_stock_price',
+ parsed_arguments=GetStockPrice(exchange='NASDAQ', ticker='AAPL')
+ ),
+ id='call_DNYTawLBoN8fj3KN6qU9N1Ou',
+ index=1,
+ type='function'
+ )
+]
+"""
+ )
+
+
+# @pytest.mark.respx(base_url=base_url)
+# def test_parse_strict_tools(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+# listener = _make_stream_snapshot_request(
+# lambda c: c.chat.stream(
+# model="palmyra-x-003-instruct",
+# messages=[
+# {
+# "role": "user",
+# "content": "What's the weather like in SF?",
+# },
+# ],
+# tools=[
+# {
+# "type": "function",
+# "function": {
+# "name": "get_weather",
+# "parameters": {
+# "type": "object",
+# "properties": {
+# "city": {"type": "string"},
+# "state": {"type": "string"},
+# },
+# "required": [
+# "city",
+# "state",
+# ],
+# "additionalProperties": False,
+# },
+# "strict": True,
+# },
+# }
+# ],
+# ),
+# content_snapshot=snapshot(external("a247c49c5fcd*_bin")),
+# mock_client=client,
+# respx_mock=respx_mock,
+# )
+
+# assert print_obj(listener.stream.current_completion_snapshot.choices, monkeypatch) == snapshot(
+# """\
+# [
+# ParsedChatCompletionChoice[object](
+# finish_reason='tool_calls',
+# index=0,
+# logprobs=None,
+# message=ParsedChatCompletionMessage[object](
+# content=None,
+# graph_data=None,
+# parsed=None,
+# refusal=None,
+# role='assistant',
+# tool_calls=[
+# ParsedFunctionToolCall(
+# function=ParsedFunction(
+# arguments='{"city":"San Francisco","state":"CA"}',
+# name='get_weather',
+# parsed_arguments={'city': 'San Francisco', 'state': 'CA'}
+# ),
+# id='call_CTf1nWJLqSeRgDqaCG27xZ74',
+# index=0,
+# type='function'
+# )
+# ]
+# )
+# )
+# ]
+# """
+# )
+
+
+@pytest.mark.respx(base_url=base_url)
+def test_non_pydantic_response_format(client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+ listener = _make_stream_snapshot_request(
+ lambda c: c.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[
+ {
+ "role": "user",
+ "content": "What's the weather like in SF? Give me any JSON back",
+ },
+ ],
+ # response_format={"type": "json_object"},
+ ),
+ content_snapshot=snapshot(external("d61558011839*.writer")),
+ openai_content_snapshot=snapshot(external("d61558011839*.openai")),
+ mock_client=client,
+ respx_mock=respx_mock,
+ )
+
+ assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot(
+ """\
+[
+ ParsedChatCompletionChoice[NoneType](
+ finish_reason='stop',
+ index=0,
+ logprobs=None,
+ message=ParsedChatCompletionMessage[NoneType](
+ content='\\n {\\n "location": "San Francisco, CA",\\n "weather": {\\n "temperature": "18°C",\\n
+"condition": "Partly Cloudy",\\n "humidity": "72%",\\n "windSpeed": "15 km/h",\\n "windDirection": "NW"\\n
+},\\n "forecast": [\\n {\\n "day": "Monday",\\n "high": "20°C",\\n "low": "14°C",\\n
+"condition": "Sunny"\\n },\\n {\\n "day": "Tuesday",\\n "high": "19°C",\\n "low": "15°C",\\n
+"condition": "Mostly Cloudy"\\n },\\n {\\n "day": "Wednesday",\\n "high": "18°C",\\n "low":
+"14°C",\\n "condition": "Cloudy"\\n }\\n ]\\n }\\n',
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal=None,
+ role='assistant',
+ tool_calls=[]
+ )
+ )
+]
+"""
+ )
+
+
+@pytest.mark.respx(base_url=base_url)
+def test_allows_non_strict_tools_but_no_parsing(
+ client: Writer, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ listener = _make_stream_snapshot_request(
+ lambda c: c.chat.stream(
+ model="palmyra-x-003-instruct",
+ messages=[{"role": "user", "content": "what's the weather in NYC?"}],
+ tools=[
+ {
+ "type": "function",
+ "function": {
+ "name": "get_weather",
+ "parameters": {"type": "object", "properties": {"city": {"type": "string"}}},
+ },
+ }
+ ],
+ ),
+ content_snapshot=snapshot(external("2018feb66ae1*.writer")),
+ openai_content_snapshot=snapshot(external("2018feb66ae1*.openai")),
+ mock_client=client,
+ respx_mock=respx_mock,
+ )
+
+ assert print_obj(listener.get_event_by_type("tool_calls.function.arguments.done"), monkeypatch) == snapshot("""\
+FunctionToolCallArgumentsDoneEvent(
+ arguments='{"city":"New York City"}',
+ index=0,
+ name='get_weather',
+ parsed_arguments=None,
+ tool_call_snapshot_id='call_4XzlGBLtUe9dy3GVNV4jhq7h',
+ type='tool_calls.function.arguments.done'
+)
+""")
+
+ assert print_obj(listener.stream.get_final_completion().choices, monkeypatch) == snapshot(
+ """\
+[
+ ParsedChatCompletionChoice[NoneType](
+ finish_reason='tool_calls',
+ index=0,
+ logprobs=None,
+ message=ParsedChatCompletionMessage[NoneType](
+ content=None,
+ graph_data=None,
+ llm_data=None,
+ parsed=None,
+ refusal=None,
+ role='assistant',
+ tool_calls=[
+ ParsedFunctionToolCall(
+ function=ParsedFunction(
+ arguments='{"city":"New York City"}',
+ name='get_weather',
+ parsed_arguments=None
+ ),
+ id='call_4XzlGBLtUe9dy3GVNV4jhq7h',
+ index=0,
+ type='function'
+ )
+ ]
+ )
+ )
+]
+"""
+ )
+
+
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+def test_stream_method_in_sync(sync: bool, client: Writer, async_client: AsyncWriter) -> None:
+ checking_client: Writer | AsyncWriter = client if sync else async_client
+
+ assert_signatures_in_sync(
+ checking_client.chat.chat,
+ checking_client.chat.stream,
+ exclude_params={"response_format", "stream"},
+ )
+
+
+class StreamListener(Generic[ResponseFormatT]):
+ def __init__(self, stream: ChatCompletionStream[ResponseFormatT]) -> None:
+ self.stream = stream
+ self.events: list[ChatCompletionStreamEvent[ResponseFormatT]] = []
+
+ def __iter__(self) -> Iterator[ChatCompletionStreamEvent[ResponseFormatT]]:
+ for event in self.stream:
+ self.events.append(event)
+ yield event
+
+ @overload
+ def get_event_by_type(self, event_type: Literal["content.done"]) -> ContentDoneEvent[ResponseFormatT] | None: ...
+
+ @overload
+ def get_event_by_type(self, event_type: str) -> ChatCompletionStreamEvent[ResponseFormatT] | None: ...
+
+ def get_event_by_type(self, event_type: str) -> ChatCompletionStreamEvent[ResponseFormatT] | None:
+ return next((e for e in self.events if e.type == event_type), None)
+
+
+def _make_stream_snapshot_request(
+ func: Callable[[Writer], ChatCompletionStreamManager[ResponseFormatT]],
+ *,
+ content_snapshot: Any,
+ openai_content_snapshot: Any,
+ respx_mock: MockRouter,
+ mock_client: Writer,
+ on_event: Callable[[ChatCompletionStream[ResponseFormatT], ChatCompletionStreamEvent[ResponseFormatT]], Any]
+ | None = None,
+) -> StreamListener[ResponseFormatT]:
+ def mock_response(snapshot: Any) -> Writer:
+ respx_mock.post("/v1/chat").mock(
+ return_value=httpx.Response(
+ 200,
+ content=snapshot._old_value._load_value(),
+ headers={"content-type": "text/event-stream"},
+ )
+ )
+ return mock_client
+
+ def consume_stream(client: Writer) -> StreamListener[ResponseFormatT]:
+ with func(client) as stream:
+ listener = StreamListener(stream)
+
+ for event in listener:
+ if on_event:
+ on_event(stream, event)
+ return listener
+
+ # Test against Writer's content snapshot
+ live = os.environ.get("WRITER_LIVE") == "1"
+ if live:
+
+ def _on_response(response: httpx.Response) -> None:
+ # update Writer's content snapshot
+ assert outsource(response.read(), suffix=".writer") == content_snapshot
+
+ respx_mock.stop()
+
+ client = Writer(
+ http_client=httpx.Client(
+ event_hooks={
+ "response": [_on_response],
+ }
+ )
+ )
+ else:
+ client = mock_response(content_snapshot)
+
+ writer_listener = consume_stream(client)
+
+ if live:
+ client.close()
+
+ # Ensure compatibility with OpenAI's content snapshot
+ client = mock_response(openai_content_snapshot)
+ consume_stream(client)
+
+ return writer_listener
diff --git a/tests/test_client.py b/tests/test_client.py
index 7d9acb8e..5a2f6c16 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -735,7 +735,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No
dict(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -764,7 +764,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non
dict(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -1555,7 +1555,7 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter)
dict(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
@@ -1584,7 +1584,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter)
dict(
messages=[
{
- "content": "Write a poem about Python",
+ "content": "Write a haiku about programming",
"role": "user",
}
],
diff --git a/tests/test_models.py b/tests/test_models.py
index d0a41b28..91faed5a 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -854,3 +854,35 @@ class Model(BaseModel):
m = construct_type(value={"cls": "foo"}, type_=Model)
assert isinstance(m, Model)
assert isinstance(m.cls, str)
+
+
+def test_discriminated_union_case() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: bool
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: List[Union[A, object]]
+
+ class ModelA(BaseModel):
+ type: Literal["modelA"]
+
+ data: int
+
+ class ModelB(BaseModel):
+ type: Literal["modelB"]
+
+ required: str
+
+ data: Union[A, B]
+
+ # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required`
+ m = construct_type(
+ value={"type": "modelB", "data": {"type": "a", "data": True}},
+ type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]),
+ )
+
+ assert isinstance(m, ModelB)
diff --git a/tests/utils.py b/tests/utils.py
index 64664db4..8d8f1908 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import io
import os
import inspect
import traceback
@@ -8,6 +9,8 @@
from datetime import date, datetime
from typing_extensions import Literal, get_args, get_origin, assert_type
+import rich
+
from writerai._types import Omit, NoneType
from writerai._utils import (
is_dict,
@@ -142,6 +145,16 @@ def _assert_list_type(type_: type[object], value: object) -> None:
assert_type(inner_type, entry) # type: ignore
+def rich_print_str(obj: object) -> str:
+ """Like `rich.print()` but returns the string instead"""
+ buf = io.StringIO()
+
+ console = rich.console.Console(file=buf, width=120)
+ console.print(obj)
+
+ return buf.getvalue()
+
+
@contextlib.contextmanager
def update_env(**new_env: str | Omit) -> Iterator[None]:
old = os.environ.copy()