Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ GOOGLE_GCS_BUCKET=bucket-name
# ============================================================
# == LLM settings ==
# ============================================================
MODEL_URI=google_genai:gemini-3-flash-preview
MODEL_TEMPERATURE=0.2
THINKING_LEVEL=low
OPENAI_API_KEY=openai-api-key
MODEL_URI=gpt-5.6-luna
REASONING_EFFORT=medium

# ============================================================
# == LangSmith settings ==
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-chatbot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ jobs:
GOOGLE_GCS_BUCKET: mock-bucket

# Mock LLM configuration
OPENAI_API_KEY: mock-openai-api-key
MODEL_URI: mock-model-uri
MODEL_TEMPERATURE: 0.0
THINKING_LEVEL: low
REASONING_EFFORT: medium

# Mock LangSmith configuration
LANGSMITH_TRACING: false
Expand Down
32 changes: 32 additions & 0 deletions alembic/versions/4b3d2fa4a75f_add_thread_language.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Add language column to threads table.

Revision ID: 4b3d2fa4a75f
Revises: f6ce7837e023
Create Date: 2026-08-04 19:50:02.000000
"""

from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "4b3d2fa4a75f"
down_revision: Union[str, Sequence[str], None] = "f6ce7837e023"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# server_default backfills existing threads with the Portuguese default;
# new rows get their value from the application (ThreadPayload.language).
op.add_column(
"thread",
sa.Column("language", sa.String(), nullable=False, server_default="pt"),
)


def downgrade() -> None:
"""Downgrade schema."""
op.drop_column("thread", "language")
20 changes: 20 additions & 0 deletions app/agent/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from dataclasses import dataclass

from app.i18n import LanguageCode


@dataclass
class AgentContext:
"""Per-run context for an agent run.

Read by:
- The system-prompt middleware (`language`)
- The tools (`language` for localized metadata; `thread_id` and `user_id` for BigQuery job labels).

`thread_id` is *also* kept in `config["configurable"]` because the langgraph
checkpointer keys persistence on it there; on this context it is app metadata.
"""

thread_id: str
user_id: str
language: LanguageCode
9 changes: 9 additions & 0 deletions app/agent/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from datetime import date

from langchain.agents.middleware import ModelRequest, dynamic_prompt


@dynamic_prompt
def system_prompt_middleware(request: ModelRequest) -> str:
"""Render the system prompt template, filling `{current_date}`."""
return request.system_message.content.format(current_date=date.today().isoformat())
164 changes: 54 additions & 110 deletions app/agent/prompts.py

Large diffs are not rendered by default.

50 changes: 4 additions & 46 deletions app/agent/schemas.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,6 @@
from enum import Enum

from pydantic import BaseModel, Field


class TemporalGranularity(str, Enum):
"""Granularity of the data's temporal coverage."""

DAY = "day"
MONTH = "month"
YEAR = "year"


class TemporalCoverage(BaseModel):
"""The interval the SQL query actually filtered on in the answer."""

period_start: str = Field(
description=(
"Start of the interval filtered by the SQL query (e.g. '2010' for "
"`ano = 2010` or `ano BETWEEN 2010 AND 2012`). Format it to match `granularity`: "
"YYYY (year), YYYY-MM (month) or YYYY-MM-DD (day) — e.g. '2010', '2010-01', '2010-01-01'. "
"May be narrower than the table's full coverage."
)
)
period_end: str = Field(
description=(
"End of the interval filtered by the SQL query (e.g. '2010' for "
"`ano = 2010`; '2012' for `ano BETWEEN 2010 AND 2012`). Format it to match `granularity`: "
"YYYY (year), YYYY-MM (month) or YYYY-MM-DD (day) — e.g. '2012', '2012-01', '2012-01-01'. "
"May be narrower than the table's full coverage."
)
)
granularity: TemporalGranularity = Field(
description=(
"Granularity of `period_start`/`period_end`, matching their format: "
"YYYY (year), YYYY-MM (month), YYYY-MM-DD (day)."
)
)


class DataSource(BaseModel):
"""A Base dos Dados table the answer draws on or points the user to."""

Expand Down Expand Up @@ -73,16 +36,11 @@ class StructuredResponse(BaseModel):
"on clarification turns. Leave empty (None) when no table is relevant."
),
)
temporal_coverage: TemporalCoverage | None = Field(
default=None,
description=(
"The interval your SQL query actually filtered. Leave empty (None) when no query "
"was run (e.g. a clarification turn) or the answer has no temporal dimension."
),
)
follow_up_questions: list[str] | None = Field(
follow_up_prompts: list[str] | None = Field(
default=None,
description=(
"3 suggested follow-up questions (in the user's language) to explore the data further."
"3 next prompts the user could send you to explore the data further, each written "
"in the user's own voice — a message the user types to you, never a question you ask "
"the user. In the user's language."
),
)
5 changes: 1 addition & 4 deletions app/agent/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from langchain_core.tools import BaseTool

from app.agent.tools.api import get_dataset_details, get_table_details, search_datasets
from app.agent.tools.bigquery import (
decode_table_values,
execute_bigquery_sql,
)
from app.agent.tools.bigquery import decode_table_values, execute_bigquery_sql


class BDToolkit:
Expand Down
Loading
Loading