mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 19:25:24 +03:00
fix(structured): harden structured output for local servers and thinking models
- Local servers (LM Studio, vLLM) reject the object-form tool_choice langchain sends for function calling. The generic openai_compatible provider now binds the schema as a tool without forcing tool_choice. - A structured call can return no parsed result (a thinking model answering in plain text); fall back to free text with a clear reason instead of an opaque render error.
This commit is contained in:
@@ -63,6 +63,11 @@ def invoke_structured_or_freetext(
|
||||
if structured_llm is not None:
|
||||
try:
|
||||
result = structured_llm.invoke(prompt)
|
||||
if result is None:
|
||||
# A thinking model can answer in plain text instead of calling
|
||||
# the tool, leaving the parser with nothing to return. Treat it
|
||||
# as a structured miss and fall back, with a clear reason.
|
||||
raise ValueError("structured output returned no parsed result")
|
||||
return render(result)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
|
||||
@@ -51,6 +51,23 @@ class NormalizedChatOpenAI(ChatOpenAI):
|
||||
return super().with_structured_output(schema, method=method, **kwargs)
|
||||
|
||||
|
||||
class LocalCompatibleChatOpenAI(NormalizedChatOpenAI):
|
||||
"""OpenAI-compatible client for arbitrary local servers (LM Studio, vLLM,
|
||||
llama.cpp via the generic ``openai_compatible`` provider).
|
||||
|
||||
Their tool-calling support varies, and many reject the object-form
|
||||
``tool_choice`` langchain sends for function-calling structured output. Bind
|
||||
the schema as a tool but don't force tool_choice, so structured output works
|
||||
across local servers regardless of the model ID's capabilities (#1057).
|
||||
"""
|
||||
|
||||
def with_structured_output(self, schema, *, method=None, **kwargs):
|
||||
resolved = method or get_capabilities(self.model_name).preferred_structured_method
|
||||
if resolved == "function_calling":
|
||||
kwargs.setdefault("tool_choice", None)
|
||||
return super().with_structured_output(schema, method=method, **kwargs)
|
||||
|
||||
|
||||
def _input_to_messages(input_: Any) -> list:
|
||||
"""Normalise a langchain LLM input to a list of message objects.
|
||||
|
||||
@@ -210,7 +227,9 @@ OPENAI_COMPATIBLE_PROVIDERS: dict[str, ProviderSpec] = {
|
||||
"ollama": ProviderSpec(base_url="http://localhost:11434/v1", base_url_env="OLLAMA_BASE_URL",
|
||||
key_optional=True, placeholder_key="ollama"),
|
||||
# Generic endpoint: user supplies base_url; key optional (keyless local).
|
||||
"openai_compatible": ProviderSpec(require_base_url=True, key_optional=True),
|
||||
"openai_compatible": ProviderSpec(
|
||||
require_base_url=True, key_optional=True, chat_class=LocalCompatibleChatOpenAI
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user