-
Notifications
You must be signed in to change notification settings - Fork 415
feat(agent): bootstrap FastAPI agent service #765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
39f6f8a
feat(agent): bootstrap FastAPI agent service
Zawiszowski 2cd3ae1
refactor(agent): align data model with spec
FBegiello cfa20e8
feat(agent): add initial alembic migration
FBegiello a68926b
feat(agent): add pygentic-ai with multi-provider LLM support
FBegiello 63764bb
feat(backend): implement recovery endpoint and extend user profile
FBegiello 6d00e6d
feat(agent): implement OW backend REST client
FBegiello d7121eb
feat(agent): implement agent toolsets, prompts, and workflow engine
FBegiello 797fd75
feat(agent): wire workflow engine into process_message Celery task
FBegiello 4a1c06e
feat(agent): implement conversation lifecycle Celery worker
FBegiello 24c9a68
chore: add .gitattributes for LF line endings and update scripts
FBegiello 12da7ad
test(agent): add pytest test suite covering all new features
FBegiello e61f69e
chore(agent): Language coding in line with ISO 629 codes and minor cl…
FBegiello 68a62a7
fix(agent): Prompt rework and general housekeeping
FBegiello df3ab90
chore(agent): Add language handling for default messages
FBegiello 0ebb243
chore(agent): Change llm handling to cover defaults for each provider…
FBegiello 9ece9ed
chore(agent): Add healthcheck for LLM providers
FBegiello 1dfecad
chore(agent): Unified naming convention across the project with top t…
FBegiello 34796c0
fix(agent): Fix migrations and dependencies for pygentic-ai
FBegiello c12a572
fix(agent): Fix default llm falbacks
FBegiello e973355
chore(agent): Fix all ruff and ty violations, remove dead ChatSession…
FBegiello 5195c13
test(agent): refactor agent test suite for pydantic-ai workflow
Zawiszowski b530383
fix(agent): convert fixtures and tests to async and fix validation er…
Zawiszowski 76d9505
chore(agent): remove unused celery tasks: dummy_task and expire_sessions
Zawiszowski 7ec22a8
fix(agent): rename auth header to X-Open-Wearables-API-Key in OWClient
Zawiszowski 61759d9
chore(agent): delete unused filter and error schemas
Zawiszowski 38445ca
refactor(agent): align exception handling with backend pattern
Zawiszowski 716fc44
fix(agent): include assistant messages in chat history seed
FBegiello 7522328
fix(agent): replace manual ReAct prompt with pydantic-ai-compatible g…
FBegiello 55eede3
fix(agent): cap tool calls per turn via UsageLimits from config
FBegiello ff45a5e
fix(agent): inject user_id via RunContext deps instead of message text
FBegiello 5033074
fix(router): pass chat history to router for contextual follow-up cla…
FBegiello e077e5b
fix(router): emit short refusals in the configured conversation language
FBegiello b0a84fe
fix(agent): address CodeRabbit review issues from PR #765
FBegiello ff50825
fix(agent): address second batch of CodeRabbit review issues from PR …
FBegiello File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # Default: normalize line endings to LF on commit | ||
| * text=auto eol=lf | ||
|
|
||
| # Shell scripts must always be LF | ||
| *.sh text eol=lf | ||
|
|
||
| # Windows-only files can keep CRLF | ||
| *.bat text eol=crlf | ||
| *.cmd text eol=crlf | ||
| *.ps1 text eol=crlf |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,7 @@ | ||
| { | ||
| "python.defaultInterpreterPath": "${workspaceFolder}/backend/.venv/bin/python", | ||
| "python.defaultInterpreterPath": "${workspaceFolder}/agent/.venv/bin/python", | ||
| "python.analysis.extraPaths": [ | ||
| "${workspaceFolder}/backend" | ||
| "${workspaceFolder}/agent" | ||
| ], | ||
| "makefile.configureOnOpen": false | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.13 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| FROM python:3.13-slim AS builder | ||
|
|
||
| RUN apt-get update && \ | ||
| apt-get install -y --no-install-recommends build-essential libpq-dev git && \ | ||
| apt-get clean && \ | ||
| rm -rf /var/lib/apt/lists/* | ||
| RUN pip install --no-cache-dir --upgrade pyopenssl | ||
|
|
||
| COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /uvx /bin/ | ||
|
|
||
| WORKDIR /root_project | ||
|
|
||
| COPY uv.lock pyproject.toml ./ | ||
| COPY . . | ||
|
|
||
| ENV VIRTUAL_ENV=/opt/venv | ||
| ENV UV_PROJECT_ENVIRONMENT=/opt/venv | ||
| RUN uv venv $VIRTUAL_ENV | ||
| ENV PATH="$VIRTUAL_ENV/bin:$PATH" | ||
| RUN uv sync --locked --no-dev | ||
|
|
||
| FROM python:3.13-slim | ||
|
|
||
| RUN apt-get update && \ | ||
| apt-get install -y --no-install-recommends libpq5 git && \ | ||
| apt-get clean && \ | ||
| rm -rf /var/lib/apt/lists/* | ||
|
|
||
| WORKDIR /root_project | ||
|
|
||
| COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /uvx /bin/ | ||
|
|
||
| COPY --from=builder /opt/venv /opt/venv | ||
| COPY --from=builder /root_project /root_project | ||
| ENV VIRTUAL_ENV=/opt/venv | ||
| ENV PATH="/opt/venv/bin:$PATH" | ||
| ENV UV_PROJECT_ENVIRONMENT=/opt/venv | ||
|
|
||
| RUN groupadd -r appuser && useradd -r -g appuser appuser && \ | ||
| chown -R appuser:appuser /root_project /opt/venv | ||
| USER appuser | ||
|
|
||
| EXPOSE 8000 | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # API | ||
|
|
||
| A FastAPI application with PostgreSQL database support, containerized with Docker. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Docker and Docker Compose | ||
| - Python 3.12+ (for local development) | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. **Create environment file** | ||
| ```bash | ||
| cp ./config/.env.example ./config/.env | ||
| # Edit .env file with your configuration | ||
| ``` | ||
|
|
||
| ## Running the Application | ||
|
|
||
| ### Docker (Recommended) | ||
|
|
||
| ```bash | ||
| # Start services | ||
| docker compose up -d | ||
|
|
||
| # Create migration | ||
| docker compose exec app uv run alembic revision --autogenerate -m "Description" | ||
|
|
||
| # Run migrations | ||
| docker compose exec app uv run alembic upgrade head | ||
| ``` | ||
|
|
||
| ### Local Development | ||
|
|
||
| ```bash | ||
| # Install dependencies | ||
| uv sync | ||
|
|
||
| # Start PostgreSQL locally | ||
|
|
||
| # Create migration | ||
| uv run alembic revision --autogenerate -m "Description" | ||
|
|
||
| # Run migrations | ||
| uv run alembic upgrade head | ||
|
|
||
| # Start development server | ||
| uv run fastapi run app/main.py --reload | ||
| ``` | ||
|
|
||
| ### Access the application | ||
| - API: http://localhost:8000 | ||
| - Swagger: http://localhost:8000/docs | ||
|
|
||
| --- | ||
|
|
||
| This project was generated from the [Python AI Kit](https://github.com/the-momentum/python-ai-kit). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| [alembic] | ||
| script_location = migrations | ||
| prepend_sys_path = . | ||
| version_path_separator = os | ||
| sqlalchemy.url = | ||
|
|
||
| [loggers] | ||
| keys = root,sqlalchemy,alembic | ||
|
|
||
| [handlers] | ||
| keys = console | ||
|
|
||
| [formatters] | ||
| keys = generic | ||
|
|
||
| [logger_root] | ||
| level = WARN | ||
| handlers = console | ||
| qualname = | ||
|
|
||
| [logger_sqlalchemy] | ||
| level = WARN | ||
| handlers = | ||
| qualname = sqlalchemy.engine | ||
|
|
||
| [logger_alembic] | ||
| level = INFO | ||
| handlers = | ||
| qualname = alembic | ||
|
|
||
| [handler_console] | ||
| class = StreamHandler | ||
| args = (sys.stderr,) | ||
| level = NOTSET | ||
| formatter = generic | ||
|
|
||
| [formatter_generic] | ||
| format = %(levelname)-5.5s [%(name)s] %(message)s | ||
| datefmt = %H:%M:%S |
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| """Agent dependency types for pydantic-ai RunContext injection.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from uuid import UUID | ||
|
|
||
| from pygentic_ai.engines.base import BaseAgentDeps | ||
|
|
||
|
|
||
| @dataclass | ||
| class HealthAgentDeps(BaseAgentDeps): | ||
| """Dependencies injected into every tool call via RunContext. | ||
|
|
||
| Extends BaseAgentDeps (which carries language) with the resolved | ||
| user_id so tools never need to receive it as a model-supplied argument. | ||
| """ | ||
|
|
||
| user_id: UUID | None = None |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| """Health guardrails agent — wraps pygentic-ai GuardrailsAgent.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from pygentic_ai.engines.guardrails import GuardrailsAgent | ||
|
|
||
| from app.agent.prompts.worker_prompts import WorkerType, build_worker_prompt | ||
| from app.agent.utils.model_utils import get_llm | ||
|
|
||
| _DEFAULT_SOFT_WORD_LIMIT = 150 | ||
|
|
||
|
|
||
| class HealthGuardrailsAgent(GuardrailsAgent): | ||
| """Output formatter and validator for health assistant responses. | ||
|
|
||
| Wraps pygentic-ai GuardrailsAgent with health-domain formatting | ||
| rules and the configured worker LLM from app settings. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| language: str = "English", | ||
| soft_word_limit: int | None = _DEFAULT_SOFT_WORD_LIMIT, | ||
| ) -> None: | ||
| vendor, model, api_key = get_llm(is_worker=True) | ||
| prompt = build_worker_prompt( | ||
| WorkerType.GUARDRAILS, | ||
| language=language, | ||
| soft_word_limit=soft_word_limit, | ||
| ) | ||
|
|
||
| super().__init__( | ||
| llm_vendor=vendor, | ||
| llm_model=model, | ||
| api_key=api_key, | ||
| system_prompt=prompt, | ||
| language=language, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| """Health-domain reasoning agent — wraps pygentic-ai BaseAgent.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
| from uuid import UUID | ||
|
|
||
| from pydantic_ai import UsageLimits | ||
| from pydantic_ai.messages import ModelMessage | ||
| from pydantic_ai.run import AgentRunResult | ||
| from pygentic_ai import BaseAgent | ||
|
|
||
| from app.agent.deps import HealthAgentDeps | ||
| from app.agent.prompts.agent_prompts import build_system_prompt | ||
| from app.agent.utils.model_utils import get_llm | ||
| from app.config import settings | ||
| from app.schemas.agent import AgentMode | ||
| from app.schemas.language import LANGUAGE_NAMES, Language | ||
|
|
||
|
|
||
| class HealthReasoningAgent(BaseAgent): | ||
| """ReAct-style reasoning agent for the Open Wearables health domain. | ||
|
|
||
| Wraps pygentic-ai BaseAgent with health-specific instructions and | ||
| the configured LLM provider from app settings. | ||
|
|
||
| user_id is stored on the instance and injected into every tool call | ||
| via HealthAgentDeps / RunContext — the model never needs to supply it. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| user_id: UUID, | ||
| mode: AgentMode = AgentMode.GENERAL, | ||
| tools: list | None = None, | ||
| language: Language | None = None, | ||
| ) -> None: | ||
| self.user_id = user_id | ||
| vendor, model, api_key = get_llm() | ||
| lang_name = LANGUAGE_NAMES[language] if language else LANGUAGE_NAMES[Language.english] | ||
| instructions = build_system_prompt(mode, language) | ||
|
|
||
| super().__init__( | ||
| llm_vendor=vendor, | ||
| llm_model=model, | ||
| api_key=api_key, | ||
| tool_list=tools or [], | ||
| system_prompt=instructions, | ||
| language=lang_name, | ||
| deps_type=HealthAgentDeps, | ||
| usage_limits=UsageLimits(request_limit=settings.max_tool_calls), | ||
| ) | ||
|
|
||
| async def generate_response( | ||
| self, | ||
| query: str, | ||
| chat_history: list[ModelMessage] | None = None, | ||
| ) -> AgentRunResult: | ||
| """Generate response, injecting user_id into deps for tool access.""" | ||
| deps = HealthAgentDeps(language=self.language, user_id=self.user_id) | ||
| run_kwargs: dict[str, Any] = { | ||
| "user_prompt": query, | ||
| "message_history": chat_history or [], | ||
| "deps": deps, | ||
| } | ||
| if self.usage_limits: | ||
| run_kwargs["usage_limits"] = self.usage_limits | ||
| return await self.agent.run(**run_kwargs) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: the-momentum/open-wearables
Length of output: 53
🏁 Script executed:
Repository: the-momentum/open-wearables
Length of output: 1383
🏁 Script executed:
Repository: the-momentum/open-wearables
Length of output: 201
🏁 Script executed:
Repository: the-momentum/open-wearables
Length of output: 1769
Remove
gitfrom the runtime stage to reduce image size and attack surface.The
pygentic-aidependency is installed from a git repository, but git is only needed during the build stage (in the builder). Since all dependencies are pre-installed in the builder's venv and copied to runtime as-is, git is not required at runtime.🗑️ Proposed fix to remove git from runtime
📝 Committable suggestion
🤖 Prompt for AI Agents