@tiens.nguyen/gonext-local-worker 1.0.141 → 1.0.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1805,6 +1805,85 @@ def run_agent_chat(cfg):
1805
1805
  # and any step memory it accumulates. completion_kwargs["messages"] here is the
1806
1806
  # literal messages array sent to /v1/chat/completions.
1807
1807
  class _LoggingModel(OpenAIServerModel):
1808
+ def __init__(self, *a, stream_agent=False, **kw):
1809
+ # stream_agent: request the completion with stream=True and reassemble the
1810
+ # deltas into one message. Enabled for Ollama, where a long single-shot
1811
+ # (non-stream) generation sends nothing until the very end — a reverse proxy
1812
+ # in front of the box (ollama1.gomarsic.cc) then hits its idle read-timeout
1813
+ # and kills the request mid-generation. Streaming keeps tokens flowing so the
1814
+ # idle timer never fires. smolagents still receives the COMPLETE text, so its
1815
+ # <code>/final_answer parsing is unaffected.
1816
+ self._stream_agent = bool(stream_agent)
1817
+ super().__init__(*a, **kw)
1818
+
1819
+ def _generate_once(self, *args, **kwargs):
1820
+ """One model call. Streams + reassembles when _stream_agent, else the normal
1821
+ non-streaming path. Streaming is a pure transport optimization: on ANY failure
1822
+ it falls back to the non-streaming call, and it's skipped for tool-calling
1823
+ mode (tools_to_call_from), whose tool_call deltas we don't reassemble."""
1824
+ tools_to_call_from = kwargs.get("tools_to_call_from")
1825
+ if not self._stream_agent or tools_to_call_from:
1826
+ return super().generate(*args, **kwargs)
1827
+ try:
1828
+ return self._streamed_generate(*args, **kwargs)
1829
+ except Exception as e: # noqa: BLE001
1830
+ _log(f"streamed generate failed ({e}) → falling back to non-streaming")
1831
+ return super().generate(*args, **kwargs)
1832
+
1833
+ def _streamed_generate(
1834
+ self, messages, stop_sequences=None, response_format=None,
1835
+ tools_to_call_from=None, **kwargs,
1836
+ ):
1837
+ from smolagents.models import ChatMessage, TokenUsage # local: version-safe
1838
+ completion_kwargs = self._prepare_completion_kwargs(
1839
+ messages=messages,
1840
+ stop_sequences=stop_sequences,
1841
+ response_format=response_format,
1842
+ tools_to_call_from=tools_to_call_from,
1843
+ model=self.model_id,
1844
+ custom_role_conversions=self.custom_role_conversions,
1845
+ convert_images_to_image_urls=True,
1846
+ **kwargs,
1847
+ )
1848
+ completion_kwargs["stream"] = True
1849
+ # Ask for a usage summary in the final chunk (OpenAI streaming convention;
1850
+ # Ollama honors it). Harmless if the server ignores it — usage stays 0.
1851
+ completion_kwargs["stream_options"] = {"include_usage": True}
1852
+ self._apply_rate_limit()
1853
+ stream = self.client.chat.completions.create(**completion_kwargs)
1854
+ parts = []
1855
+ role = "assistant"
1856
+ in_tok = out_tok = 0
1857
+ for chunk in stream:
1858
+ usage = getattr(chunk, "usage", None)
1859
+ if usage is not None:
1860
+ in_tok = getattr(usage, "prompt_tokens", 0) or in_tok
1861
+ out_tok = getattr(usage, "completion_tokens", 0) or out_tok
1862
+ choices = getattr(chunk, "choices", None)
1863
+ if not choices:
1864
+ continue
1865
+ delta = getattr(choices[0], "delta", None)
1866
+ if delta is None:
1867
+ continue
1868
+ if getattr(delta, "role", None):
1869
+ role = delta.role
1870
+ piece = getattr(delta, "content", None)
1871
+ if piece:
1872
+ parts.append(piece)
1873
+ content = "".join(parts)
1874
+ if stop_sequences and not self.supports_stop_parameter:
1875
+ from smolagents.models import remove_content_after_stop_sequences
1876
+ content = remove_content_after_stop_sequences(content, stop_sequences)
1877
+ _log(f"streamed generate assembled {len(content)} chars "
1878
+ f"(in={in_tok} out={out_tok} tokens)")
1879
+ return ChatMessage(
1880
+ role=role,
1881
+ content=content,
1882
+ tool_calls=None,
1883
+ raw=None,
1884
+ token_usage=TokenUsage(input_tokens=in_tok, output_tokens=out_tok),
1885
+ )
1886
+
1808
1887
  def generate(self, *args, **kwargs):
1809
1888
  # Two safeguards on each step's reply BEFORE smolagents parses it for the
1810
1889
  # code block / final_answer:
@@ -1843,7 +1922,7 @@ def run_agent_chat(cfg):
1843
1922
  try:
1844
1923
  for attempt in range(3):
1845
1924
  try:
1846
- msg = super().generate(*args, **kwargs)
1925
+ msg = self._generate_once(*args, **kwargs)
1847
1926
  break
1848
1927
  except Exception as e: # noqa: BLE001
1849
1928
  emsg = str(e)
@@ -2014,13 +2093,17 @@ def run_agent_chat(cfg):
2014
2093
  # the live test — minutes per step on a slow GPU — so disable it explicitly.
2015
2094
  # Sniffed per-server because MLX might reject the unknown param.
2016
2095
  extra_model_kwargs = {}
2017
- if _is_ollama_server(coding_base_url):
2018
- _log("coding server is Ollama → sending reasoning_effort='none' to disable thinking")
2096
+ coding_is_ollama = _is_ollama_server(coding_base_url)
2097
+ if coding_is_ollama:
2098
+ _log("coding server is Ollama → reasoning_effort='none' (disable thinking) + streaming")
2019
2099
  extra_model_kwargs["reasoning_effort"] = "none"
2020
2100
  model = _LoggingModel(
2021
2101
  model_id=coding_model_id,
2022
2102
  api_base=coding_base_url,
2023
2103
  api_key=agent_api_key,
2104
+ # Stream for Ollama so a long generation keeps the connection warm and can't
2105
+ # trip a reverse-proxy idle read-timeout. smolagents still gets the full text.
2106
+ stream_agent=coding_is_ollama,
2024
2107
  # Cap each HTTP attempt at 600s so a genuinely slow single call (a
2025
2108
  # remote M6000/Vulkan box doing a cold ~4-5min prompt-eval) can finish
2026
2109
  # on the first try, while a truly hung request still fails into OUR
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gonext-local-worker",
3
- "version": "1.0.141",
3
+ "version": "1.0.142",
4
4
  "description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
5
5
  "type": "module",
6
6
  "license": "MIT",