mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-14 06:17:23 +00:00
fix: address PR review — Responses input encoding + finish Azure removal
- Replay prior-turn assistant text as input_text, not output_text: a Responses easy-input message only accepts input_* content parts, so the old shape would 400 on the second turn of every conversation (default store=false resends prior turns inline). - Always request include=["reasoning.encrypted_content"] so in-turn reasoning carryover works whether or not the response is also stored server-side (OPENAI_RESPONSES_STORE). - Add detail="auto" to input_image parts. - Remove the now-dead azure_openai option from setup.sh / setup.ps1 and the docs, completing the AzureOpenAILLM removal. - Tests: correct the input_text / image-detail / include assertions and add parallel tool-call and stream-error coverage.
This commit is contained in:
1 parent
02d1458467
commit
27f9ddc8e0
6 files changed
+80
-67
No files matched your search
+21
-11
@@ -461,8 +461,15 @@ class OpenAILLM(BaseLLM):
|
||||
@staticmethod
|
||||
def _responses_content_parts(role, content):
|
||||
"""Translate a cleaned chat ``content`` value into Responses content
|
||||
parts (``input_text``/``output_text``/``input_image``/``input_file``)."""
|
||||
text_type = "output_text" if role == "assistant" else "input_text"
|
||||
parts (``input_text``/``input_image``/``input_file``).
|
||||
|
||||
Assistant text from prior turns is replayed as ``input_text`` too: a
|
||||
Responses easy-input message only accepts the input_* content types
|
||||
regardless of role (``output_text`` is reserved for full
|
||||
output-message items, which carry id/status/annotations).
|
||||
"""
|
||||
_ = role
|
||||
text_type = "input_text"
|
||||
parts = []
|
||||
if content is None:
|
||||
return parts
|
||||
@@ -480,7 +487,11 @@ class OpenAILLM(BaseLLM):
|
||||
elif itype == "image_url":
|
||||
url = (item.get("image_url") or {}).get("url")
|
||||
if url:
|
||||
parts.append({"type": "input_image", "image_url": url})
|
||||
parts.append({
|
||||
"type": "input_image",
|
||||
"image_url": url,
|
||||
"detail": "auto",
|
||||
})
|
||||
elif itype == "file":
|
||||
file_obj = item.get("file") or {}
|
||||
file_part = {"type": "input_file"}
|
||||
@@ -640,14 +651,13 @@ class OpenAILLM(BaseLLM):
|
||||
|
||||
store = bool(settings.OPENAI_RESPONSES_STORE)
|
||||
params["store"] = store
|
||||
if store:
|
||||
if previous_response_id:
|
||||
params["previous_response_id"] = previous_response_id
|
||||
else:
|
||||
# Stateless: ask the API to return encrypted reasoning so it can
|
||||
# be replayed across the in-turn tool loop without server-side
|
||||
# retention of the conversation.
|
||||
params["include"] = ["reasoning.encrypted_content"]
|
||||
if store and previous_response_id:
|
||||
params["previous_response_id"] = previous_response_id
|
||||
# Always request encrypted reasoning content so reasoning items can be
|
||||
# replayed by value across the in-turn tool loop — this keeps
|
||||
# carryover working whether or not the response is also retained
|
||||
# server-side (store=true).
|
||||
params["include"] = ["reasoning.encrypted_content"]
|
||||
return params
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -48,7 +48,6 @@ Here are some of the most fundamental settings you'll likely want to configure:
|
||||
- `anthropic`: Use Anthropic's Claude models.
|
||||
- `groq`: Use Groq's models.
|
||||
- `huggingface`: Use HuggingFace Inference API.
|
||||
- `azure_openai`: Use Azure OpenAI Service.
|
||||
- `openai` (when using local inference engines like Ollama, Llama.cpp, TGI, etc.): This signals DocsGPT to use an OpenAI-compatible API format, even if the actual LLM is running locally.
|
||||
|
||||
- **`LLM_NAME`**: Specifies the specific model to use from the chosen LLM provider. The available models depend on the `LLM_PROVIDER` you've selected.
|
||||
@@ -103,7 +102,7 @@ In this case, even though you are using Ollama locally, `LLM_PROVIDER` is set to
|
||||
|
||||
DocsGPT ships with a built-in catalog of models for the providers it
|
||||
supports out of the box (OpenAI, Anthropic, Google, Groq, OpenRouter,
|
||||
Novita, Azure OpenAI, Hugging Face, DocsGPT). To add **your own
|
||||
Novita, Hugging Face, DocsGPT). To add **your own
|
||||
models** without forking the repo — for example, a Mistral or Together
|
||||
account, a self-hosted vLLM endpoint, or any other OpenAI-compatible
|
||||
API — point `MODELS_CONFIG_DIR` at a directory of YAML files.
|
||||
|
||||
@@ -29,7 +29,6 @@ DocsGPT offers direct, streamlined support for the following cloud LLM providers
|
||||
| Anthropic (Claude) | `anthropic` | `claude-3-5-sonnet-20241022`|
|
||||
| Groq | `groq` | `llama-3.3-70b-versatile` |
|
||||
| HuggingFace Inference API | `huggingface` | `meta-llama/Llama-3.1-8B-Instruct` |
|
||||
| Azure OpenAI | `azure_openai` | `azure-gpt-4` |
|
||||
| Prem AI | `premai` | (See Prem AI docs) |
|
||||
| AWS SageMaker | `sagemaker` | (See SageMaker docs) |
|
||||
| Novita AI | `novita` | (See Novita docs) |
|
||||
|
||||
@@ -266,11 +266,10 @@ function Prompt-CloudAPIProviderOptions {
|
||||
Write-ColorText "3) Anthropic (Claude)" -ForegroundColor "Yellow"
|
||||
Write-ColorText "4) Groq" -ForegroundColor "Yellow"
|
||||
Write-ColorText "5) HuggingFace Inference API" -ForegroundColor "Yellow"
|
||||
Write-ColorText "6) Azure OpenAI" -ForegroundColor "Yellow"
|
||||
Write-ColorText "7) Novita" -ForegroundColor "Yellow"
|
||||
Write-ColorText "6) Novita" -ForegroundColor "Yellow"
|
||||
Write-ColorText "b) Back to Main Menu" -ForegroundColor "Yellow"
|
||||
Write-Host ""
|
||||
$script:provider_choice = Read-Host "Choose option (1-7, or b)"
|
||||
$script:provider_choice = Read-Host "Choose option (1-6, or b)"
|
||||
}
|
||||
|
||||
# Function to prompt for Ollama CPU/GPU options
|
||||
@@ -974,20 +973,7 @@ function Connect-CloudAPIProvider {
|
||||
Get-APIKey
|
||||
break
|
||||
}
|
||||
"6" { # Azure OpenAI
|
||||
$script:provider_name = "Azure OpenAI"
|
||||
$script:llm_name = "azure_openai"
|
||||
$script:model_name = "gpt-4o"
|
||||
Get-APIKey
|
||||
Write-Host ""
|
||||
Write-ColorText "Azure OpenAI requires additional configuration:" -ForegroundColor "White" -Bold
|
||||
$script:azure_api_base = Read-Host "Enter Azure OpenAI API base URL (e.g. https://your-resource.openai.azure.com/)"
|
||||
$script:azure_api_version = Read-Host "Enter Azure OpenAI API version (e.g. 2024-02-15-preview)"
|
||||
$script:azure_deployment = Read-Host "Enter Azure deployment name for chat"
|
||||
$script:azure_emb_deployment = Read-Host "Enter Azure deployment name for embeddings (leave empty to skip)"
|
||||
break
|
||||
}
|
||||
"7" { # Novita
|
||||
"6" { # Novita
|
||||
$script:provider_name = "Novita"
|
||||
$script:llm_provider = "novita"
|
||||
$script:model_name = "moonshotai/kimi-k2.5"
|
||||
@@ -998,7 +984,7 @@ function Connect-CloudAPIProvider {
|
||||
"B" { Clear-Host; return }
|
||||
default {
|
||||
Write-Host ""
|
||||
Write-ColorText "Invalid choice. Please choose 1-7, or b." -ForegroundColor "Red"
|
||||
Write-ColorText "Invalid choice. Please choose 1-6, or b." -ForegroundColor "Red"
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
}
|
||||
@@ -1017,14 +1003,6 @@ function Connect-CloudAPIProvider {
|
||||
"LLM_NAME=$model_name" | Add-Content -Path $ENV_FILE -Encoding utf8
|
||||
"VITE_API_STREAMING=true" | Add-Content -Path $ENV_FILE -Encoding utf8
|
||||
|
||||
# Azure OpenAI additional settings
|
||||
if ($llm_name -eq "azure_openai") {
|
||||
if ($azure_api_base) { "OPENAI_API_BASE=$azure_api_base" | Add-Content -Path $ENV_FILE -Encoding utf8 }
|
||||
if ($azure_api_version) { "OPENAI_API_VERSION=$azure_api_version" | Add-Content -Path $ENV_FILE -Encoding utf8 }
|
||||
if ($azure_deployment) { "AZURE_DEPLOYMENT_NAME=$azure_deployment" | Add-Content -Path $ENV_FILE -Encoding utf8 }
|
||||
if ($azure_emb_deployment) { "AZURE_EMBEDDINGS_DEPLOYMENT_NAME=$azure_emb_deployment" | Add-Content -Path $ENV_FILE -Encoding utf8 }
|
||||
}
|
||||
|
||||
Write-ColorText ".env file configured for $provider_name." -ForegroundColor "Green"
|
||||
|
||||
Prompt-AdvancedSettings
|
||||
|
||||
@@ -152,11 +152,10 @@ prompt_cloud_api_provider_options() {
|
||||
echo -e "${YELLOW}3) Anthropic (Claude)${NC}"
|
||||
echo -e "${YELLOW}4) Groq${NC}"
|
||||
echo -e "${YELLOW}5) HuggingFace Inference API${NC}"
|
||||
echo -e "${YELLOW}6) Azure OpenAI${NC}"
|
||||
echo -e "${YELLOW}7) Novita${NC}"
|
||||
echo -e "${YELLOW}6) Novita${NC}"
|
||||
echo -e "${YELLOW}b) Back to Main Menu${NC}"
|
||||
echo
|
||||
read -p "$(echo -e "${DEFAULT_FG}Choose option (1-7, or b): ${NC}")" provider_choice
|
||||
read -p "$(echo -e "${DEFAULT_FG}Choose option (1-6, or b): ${NC}")" provider_choice
|
||||
}
|
||||
|
||||
# Function to prompt for Ollama CPU/GPU options
|
||||
@@ -700,25 +699,14 @@ connect_cloud_api_provider() {
|
||||
model_name="meta-llama/Llama-3.1-8B-Instruct"
|
||||
get_api_key
|
||||
break ;;
|
||||
6) # Azure OpenAI
|
||||
provider_name="Azure OpenAI"
|
||||
llm_provider="azure_openai"
|
||||
model_name="gpt-4o"
|
||||
get_api_key
|
||||
echo -e "\n${DEFAULT_FG}${BOLD}Azure OpenAI requires additional configuration:${NC}"
|
||||
read -p "$(echo -e "${DEFAULT_FG}Enter Azure OpenAI API base URL (e.g. https://your-resource.openai.azure.com/): ${NC}")" azure_api_base
|
||||
read -p "$(echo -e "${DEFAULT_FG}Enter Azure OpenAI API version (e.g. 2024-02-15-preview): ${NC}")" azure_api_version
|
||||
read -p "$(echo -e "${DEFAULT_FG}Enter Azure deployment name for chat: ${NC}")" azure_deployment
|
||||
read -p "$(echo -e "${DEFAULT_FG}Enter Azure deployment name for embeddings (leave empty to skip): ${NC}")" azure_emb_deployment
|
||||
break ;;
|
||||
7) # Novita
|
||||
6) # Novita
|
||||
provider_name="Novita"
|
||||
llm_provider="novita"
|
||||
model_name="moonshotai/kimi-k2.5"
|
||||
get_api_key
|
||||
break ;;
|
||||
b|B) clear; return 1 ;; # Clear screen and Back to Main Menu
|
||||
*) echo -e "\n${RED}Invalid choice. Please choose 1-7, or b.${NC}" ; sleep 1 ;;
|
||||
*) echo -e "\n${RED}Invalid choice. Please choose 1-6, or b.${NC}" ; sleep 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -728,14 +716,6 @@ connect_cloud_api_provider() {
|
||||
echo "LLM_NAME=$model_name" >> "$ENV_FILE"
|
||||
echo "VITE_API_STREAMING=true" >> "$ENV_FILE"
|
||||
|
||||
# Azure OpenAI additional settings
|
||||
if [ "$llm_provider" = "azure_openai" ]; then
|
||||
[ -n "$azure_api_base" ] && echo "OPENAI_API_BASE=$azure_api_base" >> "$ENV_FILE"
|
||||
[ -n "$azure_api_version" ] && echo "OPENAI_API_VERSION=$azure_api_version" >> "$ENV_FILE"
|
||||
[ -n "$azure_deployment" ] && echo "AZURE_DEPLOYMENT_NAME=$azure_deployment" >> "$ENV_FILE"
|
||||
[ -n "$azure_emb_deployment" ] && echo "AZURE_EMBEDDINGS_DEPLOYMENT_NAME=$azure_emb_deployment" >> "$ENV_FILE"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}.env file configured for ${BOLD}${provider_name}${NC}${GREEN}.${NC}"
|
||||
|
||||
prompt_advanced_settings
|
||||
|
||||
@@ -102,7 +102,7 @@ def test_to_responses_input_tool_roundtrip(monkeypatch):
|
||||
"arguments": '{"q":"x"}',
|
||||
},
|
||||
{"type": "function_call_output", "call_id": "call_1", "output": "result"},
|
||||
{"role": "assistant", "content": [{"type": "output_text", "text": "final"}]},
|
||||
{"role": "assistant", "content": [{"type": "input_text", "text": "final"}]},
|
||||
]
|
||||
|
||||
|
||||
@@ -145,7 +145,11 @@ def test_to_responses_input_multimodal_image(monkeypatch):
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "input_text", "text": "look"},
|
||||
{"type": "input_image", "image_url": "data:image/png;base64,xx"},
|
||||
{
|
||||
"type": "input_image",
|
||||
"image_url": "data:image/png;base64,xx",
|
||||
"detail": "auto",
|
||||
},
|
||||
],
|
||||
}]
|
||||
|
||||
@@ -230,7 +234,9 @@ def test_build_responses_params_store_with_previous_id(monkeypatch):
|
||||
)
|
||||
assert params["store"] is True
|
||||
assert params["previous_response_id"] == "resp_abc"
|
||||
assert "include" not in params
|
||||
# Encrypted reasoning is always requested so in-turn carryover works
|
||||
# regardless of server-side retention.
|
||||
assert params["include"] == ["reasoning.encrypted_content"]
|
||||
|
||||
|
||||
# ── streaming normalization into the existing handler contract ───────────────
|
||||
@@ -294,6 +300,47 @@ def test_responses_gen_stream_text_only(monkeypatch):
|
||||
assert llm._last_response_id == "resp_2"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_responses_gen_stream_parallel_tool_calls(monkeypatch):
|
||||
from application.llm.handlers.openai import OpenAILLMHandler
|
||||
|
||||
llm = _make_llm(monkeypatch, _responses_caps())
|
||||
events = [
|
||||
_ns(
|
||||
type="response.output_item.added", output_index=0,
|
||||
item=_ns(type="function_call", call_id="call_a", name="t1", id="fc_a"),
|
||||
),
|
||||
_ns(
|
||||
type="response.output_item.added", output_index=1,
|
||||
item=_ns(type="function_call", call_id="call_b", name="t2", id="fc_b"),
|
||||
),
|
||||
_ns(type="response.function_call_arguments.delta", output_index=0, delta='{"a":'),
|
||||
_ns(type="response.function_call_arguments.done", output_index=0, arguments='{"a":1}'),
|
||||
_ns(type="response.function_call_arguments.done", output_index=1, arguments='{"b":2}'),
|
||||
_ns(type="response.completed", response=_ns(id="resp_p")),
|
||||
]
|
||||
llm.client.responses.create = MagicMock(return_value=events)
|
||||
out = list(llm._responses_gen_stream("gpt-5.5", [{"role": "user", "content": "hi"}], tools=[{"type": "function", "function": {"name": "t1", "parameters": {}}}]))
|
||||
parsed = OpenAILLMHandler().parse_response(out[-1])
|
||||
assert parsed.finish_reason == "tool_calls"
|
||||
assert [tc.id for tc in parsed.tool_calls] == ["call_a", "call_b"]
|
||||
assert [tc.index for tc in parsed.tool_calls] == [0, 1]
|
||||
assert parsed.tool_calls[0].arguments == '{"a":1}'
|
||||
assert parsed.tool_calls[1].arguments == '{"b":2}'
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_responses_gen_stream_error_event_raises(monkeypatch):
|
||||
llm = _make_llm(monkeypatch, _responses_caps())
|
||||
events = [
|
||||
_ns(type="response.output_text.delta", delta="partial"),
|
||||
_ns(type="response.failed", response=_ns(error="boom")),
|
||||
]
|
||||
llm.client.responses.create = MagicMock(return_value=events)
|
||||
with pytest.raises(RuntimeError):
|
||||
list(llm._responses_gen_stream("gpt-5.5", [{"role": "user", "content": "hi"}], tools=None))
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_responses_gen_nonstream_tools(monkeypatch):
|
||||
from application.llm.handlers.openai import OpenAILLMHandler
|
||||
|
||||
Reference in new issue
Block a user