feat(rag): add Ollama embeddings + Gemini 2.5 agent support - #59
Conversation
* feat(rag): configure Ollama embeddings + Gemini 2.5 agents - Add os.environ export for API keys in validate_api_key_for_model - Wrap model_settings for PydanticAI Agent constructor - Fix result.output (PydanticAI v1.48.0 API change) - Add datetime serialization in _serialize_messages - Apply RAG_SIMILARITY_THRESHOLD from settings (configurable default) - Make similarity_threshold nullable in schemas - Add bilingual (EN/HU) setup documentation Tested with: - Ollama qwen3-embedding:4b (10.0.0.226:11434) - Google Gemini 2.5 Pro via google-gla provider Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(agents): correct test mocks and type annotations - Fix tests to use .output instead of .data (PydanticAI v1.48) - Replace Any with object in json_safe function (ANN401) - Handle None threshold in RAG service retrieve method Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: address code review feedback - Only set API key env vars if not already present (avoids repeated mutations) - Use local variable for effective threshold in RAG retrieve (clearer intent) - Add fallback str() conversion in json_safe for unknown types Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(rag): update test for None default threshold Schema defaults similarity_threshold to None so service can use settings fallback. Updated test to match. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Gabe@w7dev <gabor@w7-7.net> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughConfiguration-driven similarity thresholds replace hardcoded defaults in RAG services. Agent model configuration wraps settings under a "model_settings" key for PydanticAI compatibility. API keys are exported to environment variables. Result attribute changed from data to output for PydanticAI v1.48.0. Datetime serialization support added for message handling. Comprehensive setup guide added. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Sorry @w7-mgfcode, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@app/features/agents/agents/base.py`:
- Around line 71-92: The env-var presence checks in the provider branch
(provider == "anthropic", "openai", "google-gla"/"google-vertex") only test for
key existence in os.environ and therefore treat an existing empty string as
valid; change the logic to treat empty strings as missing by checking both
presence and non-empty value (e.g., if os.environ.get("ANTHROPIC_API_KEY") not
truthy) before skipping setting, and when settings.anthropic_api_key /
settings.openai_api_key / settings.google_api_key are provided, set
os.environ[...] to that value if the current env var is missing or empty so
downstream clients see the real key (apply the same pattern for
ANTHROPIC_API_KEY, OPENAI_API_KEY, and GOOGLE_API_KEY).
In `@app/features/agents/service.py`:
- Around line 266-267: In stream_chat(), the code awaits result.get_data() but
PydanticAI v1.48.0's StreamedRunResult exposes get_output() instead; update the
call in the stream_chat function to await result.get_output() (matching the
non-streaming chat() use of get_output()), ensuring you reference the
StreamedRunResult instance named result and replace get_data() with get_output()
so the final validated output is retrieved correctly.
In `@docs/rag-ollama-setup.md`:
- Around line 508-530: The documentation snippet for validate_api_key_for_model
does not show the environment-guard present in the real implementation; update
the docs to wrap each os.environ assignment with a check like if
"ANTHROPIC_API_KEY" not in os.environ (and similarly for OPENAI_API_KEY and
GOOGLE_API_KEY) so the function validate_api_key_for_model only sets
os.environ["ANTHROPIC_API_KEY"], os.environ["OPENAI_API_KEY"], or
os.environ["GOOGLE_API_KEY"] when the respective setting
(settings.anthropic_api_key, settings.openai_api_key, settings.google_api_key)
is present and the environment variable is not already set, matching the actual
code behavior.
🧹 Nitpick comments (2)
docs/rag-ollama-setup.md (2)
1-50: Well-structured bilingual documentation.The overview and architecture sections effectively document the RAG system with Ollama embeddings. The ASCII architecture diagrams clearly illustrate the component relationships.
Consider adding a language specifier to the architecture diagram code block for consistency:
📝 Minor: Add language specifier to architecture diagram
-``` +```text ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ ForecastLabAI │────▶│ Ollama Server │────▶│ qwen3-embedding │
346-370: Optional: Add language specifiers to error message code blocks.Static analysis flags missing language specifiers on error message blocks. While minor, adding specifiers like
textorplaintextimproves consistency.📝 Example fix
-``` +```text Failed to connect to Ollama at http://10.0.0.226:11434</details> </blockquote></details> </blockquote></details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| if provider == "anthropic": | ||
| if not settings.anthropic_api_key: | ||
| raise ValueError( | ||
| "Anthropic API key not configured. Set ANTHROPIC_API_KEY environment variable." | ||
| ) | ||
| # Only set env var if not already present to avoid repeated mutations | ||
| if "ANTHROPIC_API_KEY" not in os.environ: | ||
| os.environ["ANTHROPIC_API_KEY"] = settings.anthropic_api_key | ||
| elif provider == "openai": | ||
| if not settings.openai_api_key: | ||
| raise ValueError( | ||
| "OpenAI API key not configured. Set OPENAI_API_KEY environment variable." | ||
| ) | ||
| if "OPENAI_API_KEY" not in os.environ: | ||
| os.environ["OPENAI_API_KEY"] = settings.openai_api_key | ||
| elif provider in ["google-gla", "google-vertex"]: | ||
| if not settings.google_api_key: | ||
| raise ValueError( | ||
| "Google API key not configured. Set GOOGLE_API_KEY environment variable." | ||
| ) | ||
| if "GOOGLE_API_KEY" not in os.environ: | ||
| os.environ["GOOGLE_API_KEY"] = settings.google_api_key |
There was a problem hiding this comment.
Handle empty env-var values when exporting API keys.
If the env var exists but is an empty string, the code will skip setting the real key and downstream clients will still see a blank value. Consider treating empty as “missing.”
🛠️ Proposed fix
- if "ANTHROPIC_API_KEY" not in os.environ:
+ if not os.environ.get("ANTHROPIC_API_KEY"):
os.environ["ANTHROPIC_API_KEY"] = settings.anthropic_api_key
@@
- if "OPENAI_API_KEY" not in os.environ:
+ if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = settings.openai_api_key
@@
- if "GOOGLE_API_KEY" not in os.environ:
+ if not os.environ.get("GOOGLE_API_KEY"):
os.environ["GOOGLE_API_KEY"] = settings.google_api_key🤖 Prompt for AI Agents
In `@app/features/agents/agents/base.py` around lines 71 - 92, The env-var
presence checks in the provider branch (provider == "anthropic", "openai",
"google-gla"/"google-vertex") only test for key existence in os.environ and
therefore treat an existing empty string as valid; change the logic to treat
empty strings as missing by checking both presence and non-empty value (e.g., if
os.environ.get("ANTHROPIC_API_KEY") not truthy) before skipping setting, and
when settings.anthropic_api_key / settings.openai_api_key /
settings.google_api_key are provided, set os.environ[...] to that value if the
current env var is missing or empty so downstream clients see the real key
(apply the same pattern for ANTHROPIC_API_KEY, OPENAI_API_KEY, and
GOOGLE_API_KEY).
| # NOTE: PydanticAI v1.48.0 uses result.output (not result.data) | ||
| result_data: Any = result.output |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the stream_chat implementation in service.py
grep -n "stream_chat\|get_data\|result\.output" app/features/agents/service.py | head -20Repository: w7-mgfcode/ForecastLabAI
Length of output: 408
🏁 Script executed:
# Check the dependencies to confirm PydanticAI version
fd -e "requirements.txt" -e "pyproject.toml" -e "poetry.lock" -e "setup.py" | head -5 | xargs grep -l "pydantic" 2>/dev/nullRepository: w7-mgfcode/ForecastLabAI
Length of output: 52
🌐 Web query:
PydanticAI v1.48.0 run_stream API result object output get_data method documentation
💡 Result:
In PydanticAI (pydantic-ai) v1.48.0, the object you get from Agent.run_stream(...) is a StreamedRunResult, and the method to “get the data” is get_output() (not get_data). (ai.pydantic.dev)
What to use in v1.48.0
-
Async streaming run:
async with agent.run_stream(...) as result:- Stream partial output via
result.stream_text(...)/result.stream_output(...) - Then get the final validated output via
await result.get_output()(ai.pydantic.dev)
-
Sync streaming run:
agent.run_stream_sync(...)returns a sync wrapper (StreamedRunResultSync)- Then call
result.get_output()(sync) (ai.pydantic.dev)
Where it’s documented
- API reference for
StreamedRunResult.get_output()(“Stream the whole response, validate and return it.”) (ai.pydantic.dev) - Core concept docs explaining that
run_stream()returns a streamed result wrapper around the final output (ai.pydantic.dev)
About get_data()
If you’re seeing get_data() in older/community mirrors (or older docs snapshots), that name doesn’t match the current official v1.48.0 API; in v1.48.0 the corresponding method is get_output(). (ai.pydantic.dev)
Citations:
- 1: https://ai.pydantic.dev/api/result/?utm_source=openai
- 2: https://ai.pydantic.dev/api/result/?utm_source=openai
- 3: https://ai.pydantic.dev/api/agent/?utm_source=openai
- 4: https://ai.pydantic.dev/api/result/?utm_source=openai
- 5: https://ai.pydantic.dev/output/
- 6: https://ai.pydantic.dev/api/result/?utm_source=openai
Replace result.get_data() with result.get_output() in stream_chat().
In PydanticAI v1.48.0, StreamedRunResult uses get_output() (not get_data()) to retrieve the final validated output. Line 417 currently calls await result.get_data(), which will fail at runtime. Update to await result.get_output() to match the corrected API and maintain consistency with the non-streaming chat() method.
🤖 Prompt for AI Agents
In `@app/features/agents/service.py` around lines 266 - 267, In stream_chat(), the
code awaits result.get_data() but PydanticAI v1.48.0's StreamedRunResult exposes
get_output() instead; update the call in the stream_chat function to await
result.get_output() (matching the non-streaming chat() use of get_output()),
ensuring you reference the StreamedRunResult instance named result and replace
get_data() with get_output() so the final validated output is retrieved
correctly.
| **File: `app/features/agents/agents/base.py`** | ||
| ```python | ||
| import os | ||
| from app.core.config import get_settings | ||
|
|
||
| def validate_api_key_for_model(model: str) -> None: | ||
| """Validate and export API key to environment.""" | ||
| settings = get_settings() | ||
| provider = model.split(":")[0] | ||
|
|
||
| if provider == "anthropic": | ||
| if not settings.anthropic_api_key: | ||
| raise ValueError("ANTHROPIC_API_KEY not configured") | ||
| os.environ["ANTHROPIC_API_KEY"] = settings.anthropic_api_key | ||
| elif provider == "openai": | ||
| if not settings.openai_api_key: | ||
| raise ValueError("OPENAI_API_KEY not configured") | ||
| os.environ["OPENAI_API_KEY"] = settings.openai_api_key | ||
| elif provider in ["google-gla", "google-vertex"]: | ||
| if not settings.google_api_key: | ||
| raise ValueError("GOOGLE_API_KEY not configured") | ||
| os.environ["GOOGLE_API_KEY"] = settings.google_api_key | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "base.py" -path "*/agents/agents/*" | head -5Repository: w7-mgfcode/ForecastLabAI
Length of output: 104
🏁 Script executed:
cat -n app/features/agents/agents/base.pyRepository: w7-mgfcode/ForecastLabAI
Length of output: 5545
Update documentation snippet to include the environment variable guard.
The actual implementation correctly includes a guard (if "KEY_NAME" not in os.environ:) before setting API keys, as shown at lines 77-78, 84-85, and 91-92 of app/features/agents/agents/base.py. The documentation snippet at lines 508-530 of docs/rag-ollama-setup.md should be updated to reflect this optimization and match the actual implementation. Each API key assignment should be wrapped with a conditional check to avoid unnecessary mutations on repeated calls.
🤖 Prompt for AI Agents
In `@docs/rag-ollama-setup.md` around lines 508 - 530, The documentation snippet
for validate_api_key_for_model does not show the environment-guard present in
the real implementation; update the docs to wrap each os.environ assignment with
a check like if "ANTHROPIC_API_KEY" not in os.environ (and similarly for
OPENAI_API_KEY and GOOGLE_API_KEY) so the function validate_api_key_for_model
only sets os.environ["ANTHROPIC_API_KEY"], os.environ["OPENAI_API_KEY"], or
os.environ["GOOGLE_API_KEY"] when the respective setting
(settings.anthropic_api_key, settings.openai_api_key, settings.google_api_key)
is present and the environment variable is not already set, matching the actual
code behavior.
Summary
nomic-embed-textstr()for unknown types in message serializationChanges
app/features/agents/agents/base.py- API key validation improvementsapp/features/agents/agents/rag_assistant.py- Better error handlingapp/features/agents/service.py- JSON serialization robustnessapp/features/rag/service.py- Threshold handling refactorapp/features/rag/schemas.py- Schema refinementsdocs/rag-ollama-setup.md- Comprehensive Ollama setup guide (742 lines)Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.