Azure OpenAI Integration#
RAGFlow integrates Azure OpenAI as a first-class provider across two runtimes:
- Python layer — handles embeddings, chat/LLM, and vision models via
rag/llm/ - Go layer — handles chat and embeddings for the agent model runner via
internal/entity/models/
Within the Python layer, two different SDK strategies are used:
| Model Type | Python Implementation | Go Implementation |
|---|---|---|
| Embeddings | OpenAI SDK AzureOpenAI client directly | Native HTTP client |
| Chat / LLM | LiteLLM with azure/ prefix routing | Native HTTP client |
| Vision (CV) | OpenAI SDK AzureOpenAI client directly | Not implemented |
The Go driver was added in PR #15022 and implements a hand-rolled HTTP client targeting Azure's deployment-scoped REST API directly, with no dependency on any OpenAI SDK or LiteLLM.
Credential Handling#
Python Layer#
Azure credentials in the Python layer are stored as a JSON object:
{"api_key": "your-azure-api-key", "api_version": "2024-02-01"}
The helper _resolve_azure_credentials(key) in embedding_model.py (and its duplicate in cv_model.py) parses this JSON and handles three cases:
- Valid JSON object → extracts
api_keyandapi_versionfields - Raw string (e.g., plain API key from UI) → uses as
api_key, defaultsapi_versionto"2024-02-01" - Malformed / non-JSON → falls back to raw string as
api_key, logs a warning
This fix was introduced in PR #15877 to address a crash (JSONDecodeError) when users supplied a plain API key string. Before the fix, both AzureEmbed and AzureGptV4 called json.loads(key) unconditionally.
⚠️ Known inconsistency: The LiteLLM-based chat model in
chat_model.pylines 1620–1622 still uses rawjson.loads(key)without the same fallback. A plain API key string passed to the chat model will still raiseJSONDecodeError.
Go Layer#
The Go driver receives credentials from the model configuration's ApiKey field directly — no JSON wrapping. Authentication uses the api-key HTTP header (not Authorization: Bearer). See azure_openai.go for header usage.
Deployment-Scoped Endpoints#
Azure OpenAI's REST API routes requests through deployment-specific URL paths rather than using a shared model catalog:
{baseURL}/deployments/{deployment_name}/{operation}?api-version={version}
Go Driver#
The Go driver makes this explicit. The deploymentURL() method constructs the full URL with the deployment name embedded in the path, and request bodies for both chat and embeddings omit the model field entirely — the deployment is already encoded in the URL.
Python Embedding (AzureEmbed)#
AzureEmbed passes model_name to the OpenAI SDK's AzureOpenAI client, which internally maps it to the deployment name in the URL path. The embeddings.create(model=self.model_name) call includes the model field in the request, but the SDK uses it to construct the deployment URL.
Python Chat (LiteLLM)#
The LiteLLM integration passes the model name prefixed with azure/ (e.g., azure/gpt-4o) and supplies api_key, api_base, and api_version as explicit kwargs. LiteLLM handles Azure URL construction internally.
Key Difference#
| Layer | Deployment Name Handling |
|---|---|
| Go | Explicitly embedded in URL path; model field absent from request body |
| Python Embedding (SDK) | Passed as model_name; SDK handles URL routing |
| Python Chat (LiteLLM) | Prefixed as azure/{model_name}; LiteLLM handles routing |
The model_name value passed into all Python and Go layers must be the Azure deployment name, not the underlying base model name. Passing the model name (e.g., gpt-4o) instead of the deployment name will cause a 404 from Azure.
API Versioning and Inconsistencies#
Version Defaults#
| Layer | Default api_version | Source |
|---|---|---|
Python Embedding (AzureEmbed) | "2024-02-01" | |
Python Vision (AzureGptV4) | "2024-02-01" | |
| Python Chat (LiteLLM) | "2024-02-01" | |
| Go driver | "2024-10-21" |
The Go driver pins to "2024-10-21" (the latest GA non-preview version at time of PR #15022), while all Python layers default to "2024-02-01". Users configuring credentials via the JSON format can override the version for Python layers; the Go driver's version is hardcoded.
Summary of Cross-Layer Inconsistencies#
| Concern | Python (Embed/Vision) | Python (Chat/LiteLLM) | Go |
|---|---|---|---|
| api_version default | 2024-02-01 | 2024-02-01 | 2024-10-21 (hardcoded) |
| Credential input | JSON object or raw string | JSON object only (no fallback) | Raw string via config |
| Deployment routing | SDK-abstracted | LiteLLM-abstracted | Explicit URL path |
| Auth header | SDK-managed | LiteLLM-managed | api-key header |
model in request body | Included (SDK sends it) | LiteLLM-managed | Omitted |
Go Driver Architecture#
File: internal/entity/models/azure_openai.go — added in PR #15022
Provider config: conf/models/azure-openai.json — no default base URL (each Azure resource has a unique endpoint like https://<resource>.openai.azure.com/openai)
Implemented Methods#
| Method | Description |
|---|---|
ChatWithMessages | Non-streaming chat; returns single JSON response |
ChatStreamlyWithSender | SSE streaming chat with 1 MB scanner buffer and sawTerminal flag to detect premature stream termination |
Embed | Embeddings with input-order preservation via response Index field |
ListModels | Lists Azure deployments at {baseURL}/deployments?api-version=... |
CheckConnection | Lightweight connectivity check via ListModels |
Methods Balance, Rerank, TranscribeAudio, AudioSpeech, OCRFile, ParseFile, ListTasks, and ShowTask all return "no such method".
HTTP Transport#
The driver clones http.DefaultTransport (preserving proxy, TLS, and HTTP/2 settings) and sets conservative connection pool limits. No client-wide timeout is applied to avoid cutting off SSE streams; non-streaming calls use context.WithTimeout individually.