186 lines
5.6 KiB
Python
186 lines
5.6 KiB
Python
"""xAI-specific utilities.
|
|
|
|
This module contains utilities specific to the xAI provider,
|
|
including reask functions, response handlers, and message formatting.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, TYPE_CHECKING
|
|
|
|
from ...mode import Mode
|
|
|
|
if TYPE_CHECKING:
|
|
from xai_sdk import chat as xchat
|
|
else:
|
|
try:
|
|
from xai_sdk import chat as xchat
|
|
except ImportError:
|
|
xchat = None
|
|
|
|
|
|
def _convert_messages(messages: list[dict[str, Any]]):
|
|
"""Convert OpenAI-style messages to xAI format."""
|
|
if xchat is None:
|
|
from ...core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The xAI provider needs the optional dependency `xai-sdk`. "
|
|
'Install it with `uv pip install "instructor[xai]"` (or `pip install "instructor[xai]"`). '
|
|
"Note: xai-sdk requires Python 3.10+."
|
|
) from None
|
|
|
|
converted = []
|
|
for m in messages:
|
|
role = m["role"]
|
|
content = m.get("content", "")
|
|
if isinstance(content, str):
|
|
c = xchat.text(content)
|
|
else:
|
|
raise ValueError("Only string content supported for xAI provider")
|
|
if role == "user":
|
|
converted.append(xchat.user(c))
|
|
elif role == "assistant":
|
|
converted.append(xchat.assistant(c))
|
|
elif role == "system":
|
|
converted.append(xchat.system(c))
|
|
elif role == "tool":
|
|
converted.append(xchat.tool_result(content))
|
|
else:
|
|
raise ValueError(f"Unsupported role: {role}")
|
|
return converted
|
|
|
|
|
|
def reask_xai_json(
|
|
kwargs: dict[str, Any],
|
|
response: Any,
|
|
exception: Exception,
|
|
):
|
|
"""
|
|
Handle reask for xAI JSON mode when validation fails.
|
|
|
|
Kwargs modifications:
|
|
- Modifies: "messages" (appends user message requesting correction)
|
|
"""
|
|
kwargs = kwargs.copy()
|
|
reask_msg = {
|
|
"role": "user",
|
|
"content": f"Validation Errors found:\n{exception}\nRecall the function correctly, fix the errors found in the following attempt:\n{response}",
|
|
}
|
|
kwargs["messages"].append(reask_msg)
|
|
return kwargs
|
|
|
|
|
|
def reask_xai_tools(
|
|
kwargs: dict[str, Any],
|
|
response: Any,
|
|
exception: Exception,
|
|
):
|
|
"""
|
|
Handle reask for xAI tools mode when validation fails.
|
|
|
|
Kwargs modifications:
|
|
- Modifies: "messages" (appends assistant and user messages for tool correction)
|
|
"""
|
|
kwargs = kwargs.copy()
|
|
|
|
# Add assistant response to conversation history
|
|
assistant_msg = {
|
|
"role": "assistant",
|
|
"content": str(response),
|
|
}
|
|
kwargs["messages"].append(assistant_msg)
|
|
|
|
# Add user correction request
|
|
reask_msg = {
|
|
"role": "user",
|
|
"content": f"Validation Error found:\n{exception}\nRecall the function correctly, fix the errors",
|
|
}
|
|
kwargs["messages"].append(reask_msg)
|
|
return kwargs
|
|
|
|
|
|
def handle_xai_json(
|
|
response_model: type[Any] | None, new_kwargs: dict[str, Any]
|
|
) -> tuple[type[Any] | None, dict[str, Any]]:
|
|
"""
|
|
Handle xAI JSON mode.
|
|
|
|
When response_model is None:
|
|
- Converts messages from OpenAI format to xAI format
|
|
- No schema is added to the request
|
|
|
|
When response_model is provided:
|
|
- Converts messages from OpenAI format to xAI format
|
|
- Sets up the model for JSON parsing mode
|
|
|
|
Kwargs modifications:
|
|
- Modifies: "messages" (converts from OpenAI to xAI format)
|
|
- Removes: instructor-specific kwargs (max_retries, validation_context, context, hooks)
|
|
"""
|
|
# Convert messages to xAI format
|
|
messages = new_kwargs.get("messages", [])
|
|
new_kwargs["x_messages"] = _convert_messages(messages)
|
|
|
|
# Remove instructor-specific kwargs that xAI doesn't support
|
|
new_kwargs.pop("max_retries", None)
|
|
new_kwargs.pop("validation_context", None)
|
|
new_kwargs.pop("context", None)
|
|
new_kwargs.pop("hooks", None)
|
|
|
|
return response_model, new_kwargs
|
|
|
|
|
|
def handle_xai_tools(
|
|
response_model: type[Any] | None, new_kwargs: dict[str, Any]
|
|
) -> tuple[type[Any] | None, dict[str, Any]]:
|
|
"""
|
|
Handle xAI tools mode.
|
|
|
|
When response_model is None:
|
|
- Converts messages from OpenAI format to xAI format
|
|
- No tools are configured
|
|
|
|
When response_model is provided:
|
|
- Converts messages from OpenAI format to xAI format
|
|
- Sets up tool schema from the response model
|
|
- Configures tool choice for automatic tool selection
|
|
|
|
Kwargs modifications:
|
|
- Modifies: "messages" (converts from OpenAI to xAI format)
|
|
- Adds: "tool" (xAI tool schema) - only when response_model provided
|
|
- Removes: instructor-specific kwargs (max_retries, validation_context, context, hooks)
|
|
"""
|
|
# Convert messages to xAI format
|
|
messages = new_kwargs.get("messages", [])
|
|
new_kwargs["x_messages"] = _convert_messages(messages)
|
|
|
|
# Remove instructor-specific kwargs that xAI doesn't support
|
|
new_kwargs.pop("max_retries", None)
|
|
new_kwargs.pop("validation_context", None)
|
|
new_kwargs.pop("context", None)
|
|
new_kwargs.pop("hooks", None)
|
|
|
|
if response_model is not None and xchat is not None:
|
|
# Set up tool schema for structured output
|
|
new_kwargs["tool"] = xchat.tool(
|
|
name=response_model.__name__,
|
|
description=response_model.__doc__ or "",
|
|
parameters=response_model.model_json_schema(),
|
|
)
|
|
|
|
return response_model, new_kwargs
|
|
|
|
|
|
# Handler registry for xAI
|
|
XAI_HANDLERS = {
|
|
Mode.XAI_JSON: {
|
|
"reask": reask_xai_json,
|
|
"response": handle_xai_json,
|
|
},
|
|
Mode.XAI_TOOLS: {
|
|
"reask": reask_xai_tools,
|
|
"response": handle_xai_tools,
|
|
},
|
|
}
|