From 3f8c1acc9d746eb09e9c1941e655691e81186833 Mon Sep 17 00:00:00 2001 From: lobstersyrup Date: Sun, 3 May 2026 23:46:32 +0800 Subject: [PATCH 1/2] fix(llm): format tools in continue-gen path to include type field _call_llm_for_continue_gen was passing raw Tool dicts directly to _call_llm without calling format_tools first. The raw Tool TypedDict lacks the 'type': 'function' field required by OpenAI-compatible APIs. This caused 400 errors ('tools[0]: missing field type') from providers that strictly validate tool definitions (e.g., DeepSeek), particularly when long responses triggered finish_reason=length and entered the continue-generation path. Fixed in both OpenAI._call_llm_for_continue_gen and DeepSeek._call_llm_for_continue_gen to call self.format_tools(tools), matching the existing pattern in generate(). --- ms_agent/llm/deepseek_llm.py | 2 +- ms_agent/llm/openai_llm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ms_agent/llm/deepseek_llm.py b/ms_agent/llm/deepseek_llm.py index e565308bc..9cdd36aff 100644 --- a/ms_agent/llm/deepseek_llm.py +++ b/ms_agent/llm/deepseek_llm.py @@ -37,7 +37,7 @@ def _call_llm_for_continue_gen(self, messages = self.format_input_message(messages) stop = kwargs.pop('stop', []).append('```') return self._call_llm( - messages=messages, tools=tools, stop=stop, **kwargs) + messages=messages, tools=self.format_tools(tools), stop=stop, **kwargs) if __name__ == '__main__': diff --git a/ms_agent/llm/openai_llm.py b/ms_agent/llm/openai_llm.py index dadc1bf1c..dca780c53 100644 --- a/ms_agent/llm/openai_llm.py +++ b/ms_agent/llm/openai_llm.py @@ -499,7 +499,7 @@ def _call_llm_for_continue_gen(self, messages[-1].partial = True messages[-1].api_calls += 1 - return self._call_llm(messages, tools, **kwargs) + return self._call_llm(messages, self.format_tools(tools), **kwargs) def _continue_generate(self, messages: List[Message], From d7537a59a8471f61caa9266c3d0f29a469663ad9 Mon Sep 17 00:00:00 2001 From: lobstersyrup Date: Mon, 4 May 2026 03:22:30 +0800 Subject: [PATCH 2/2] fix(llm): prevent orphaned tool_calls in continue-gen path When finish_reason=length with tool_calls present, _continue_generate and _stream_continue_generate append the partial message (with tool_calls) to history then immediately call the LLM for continuation without executing the tools. The resulting messages list has assistant tool_calls with no corresponding tool responses, causing: openai.BadRequestError: insufficient tool messages following tool_calls message Fix: return early when tool_calls are present, letting the normal step() tool-execution loop handle the message. - _continue_generate: return new_message immediately if tool_calls - _stream_continue_generate: skip continuation if tool_calls present Fixes #909 --- ms_agent/llm/openai_llm.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ms_agent/llm/openai_llm.py b/ms_agent/llm/openai_llm.py index dca780c53..b4c2974fd 100644 --- a/ms_agent/llm/openai_llm.py +++ b/ms_agent/llm/openai_llm.py @@ -343,9 +343,10 @@ def _stream_continue_generate(self, # The stream may end without a final usage chunk, which is acceptable. pass first_run = not messages[-1].to_dict().get('partial', False) - if chunk.choices[0].finish_reason in [ + if (not message.tool_calls + and chunk.choices[0].finish_reason in [ 'length', 'null' - ] and (max_runs is None or max_runs != 0): + ] and (max_runs is None or max_runs != 0)): logger.info( f'finish_reason: {chunk.choices[0].finish_reason}, continue generate.' ) @@ -522,6 +523,8 @@ def _continue_generate(self, Message: A fully formed Message object containing the complete response. """ new_message = self._format_output_message(completion) + if new_message.tool_calls: + return new_message if completion.choices[0].finish_reason in [ 'length', 'null' ] and (max_runs is None or max_runs != 0):