Skip to content

(feat): add support for llm based inital turn#844

Open
narsimhaReddyJuspay wants to merge 1 commit into
juspay:releasefrom
narsimhaReddyJuspay:llm-based-intial-turn
Open

(feat): add support for llm based inital turn#844
narsimhaReddyJuspay wants to merge 1 commit into
juspay:releasefrom
narsimhaReddyJuspay:llm-based-intial-turn

Conversation

@narsimhaReddyJuspay

@narsimhaReddyJuspay narsimhaReddyJuspay commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features
    • Added streaming endpoint for dynamic initial greetings in chat sessions
    • Templates without static greetings can now fetch AI-generated initial messages via the new initial-turn endpoint
    • Sessions include proper access controls, rate limiting, and session state management to support flexible conversation flows

Copilot AI review requested due to automatic review settings June 18, 2026 09:42
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38e4cf54-2dc5-4a7a-9a1a-c952a9d4354a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a /initial-turn SSE streaming endpoint to the Breeze Buddy widget chat flow. ChatAgent.run_initial_turn is introduced as an async generator for the bot's first turn without a user message. A shared send_chat_initial_turn_handler with Redis locking drives this via StreamingResponse, and a new widget route with preflight and auth guards delegates to it.

Changes

Initial-Turn SSE Streaming

Layer / File(s) Summary
ChatAgent.run_initial_turn async generator
app/ai/voice/agents/breeze_buddy/chat/agent.py
Adds run_initial_turn that allocates per-turn aiohttp_session/mcp_pool, prepares tools, seeds resume-style context for empty history, streams events via _cycle_loop, and closes resources in finally.
Shared initial-turn handler with Redis lock
app/api/routers/breeze_buddy/chat/handlers.py
Adds send_chat_initial_turn_handler which acquires a per-session Redis lock (409 on contention), loads session and template with optional access_check, rejects ended sessions, loads capped history, merges template_vars, constructs ChatAgent, and returns a StreamingResponse streaming agent.run_initial_turn. Updates the greeting comment in create_chat_session_handler. Lock is released in finally when handoff does not occur.
Widget route wiring and guard handler
app/api/routers/breeze_buddy/widget/__init__.py, app/api/routers/breeze_buddy/widget/handlers.py
Registers OPTIONS CORS preflight and authenticated POST /widget/session/{session_id}/initial-turn routes. Adds send_widget_initial_turn_handler which enforces widget-active state, IP rate limit, session ownership/ended/CHAT-channel guards, then delegates to send_chat_initial_turn_handler.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant WidgetRouter
  participant send_widget_initial_turn_handler
  participant send_chat_initial_turn_handler
  participant ChatAgent

  Browser->>WidgetRouter: POST /widget/session/{id}/initial-turn
  WidgetRouter->>send_widget_initial_turn_handler: session_id, request, WidgetSessionContext
  send_widget_initial_turn_handler->>send_widget_initial_turn_handler: widget active, IP limit, ownership, ended, CHAT channel checks
  send_widget_initial_turn_handler->>send_chat_initial_turn_handler: session_id, access_check=None
  send_chat_initial_turn_handler->>send_chat_initial_turn_handler: Redis lock acquire (409 on contention)
  send_chat_initial_turn_handler->>send_chat_initial_turn_handler: load session, template, history, agent state, template_vars
  send_chat_initial_turn_handler->>ChatAgent: run_initial_turn(current_node)
  ChatAgent->>ChatAgent: allocate aiohttp_session, mcp_pool, prepare tools
  ChatAgent->>ChatAgent: seed_resume_context, _cycle_loop
  ChatAgent-->>Browser: SSE events streamed via StreamingResponse
  ChatAgent->>ChatAgent: finally — close aiohttp_session, mcp_pool
  send_chat_initial_turn_handler->>send_chat_initial_turn_handler: finally — release Redis lock (if not handed off)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 No message needed, I'll start with cheer,
A greeting stream appears—first turn is here!
Lock the Redis gate, load the session tight,
Yield those SSE events into the night.
The MCP pool closes when the stream is done—
Initial turn complete, the chat has begun! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title references 'llm based initial turn' which matches the PR's main objective of adding LLM-based initial turn support, as evidenced by the new run_initial_turn method and streaming endpoints added across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/api/routers/breeze_buddy/widget/handlers.py (1)

301-305: ⚡ Quick win

Add a return type hint to the new handler signature.

send_widget_initial_turn_handler is missing an explicit return annotation.

As per coding guidelines, “Add type hints on all function signatures.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/routers/breeze_buddy/widget/handlers.py` around lines 301 - 305, The
function `send_widget_initial_turn_handler` is missing an explicit return type
annotation in its signature. Add a return type hint after the closing
parenthesis of the parameter list using the arrow notation (->). Determine the
appropriate return type based on what the function actually returns and add it
to comply with the coding guidelines requiring type hints on all function
signatures.

Source: Coding guidelines

app/api/routers/breeze_buddy/widget/__init__.py (1)

154-159: ⚡ Quick win

Add a return type hint to the route handler.

send_widget_initial_turn should declare its return type (e.g., Response/StreamingResponse) for consistency with the typed router surface.

As per coding guidelines, “Add type hints on all function signatures.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/routers/breeze_buddy/widget/__init__.py` around lines 154 - 159, The
async function `send_widget_initial_turn` is missing a return type hint in its
function signature. Add an appropriate return type annotation (such as Response
or StreamingResponse) between the closing parenthesis of the parameter list and
the colon for consistency with the typed router surface and to comply with the
coding guidelines requiring type hints on all function signatures.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/routers/breeze_buddy/chat/handlers.py`:
- Around line 703-717: The issue is that run_initial_turn is being called
unconditionally even when the session already has message history, which can
cause duplicate writes and side effects on session retries. After fetching
history_rows using list_chat_messages_for_session around line 703, add a
conditional check before the run_initial_turn call to only execute it when
history_rows is empty. Wrap the run_initial_turn invocation in an if statement
that verifies the session is fresh by checking if the history list is empty,
ensuring initial-turn logic only runs on new sessions.

In `@app/api/routers/breeze_buddy/widget/handlers.py`:
- Around line 327-351: The call to send_chat_initial_turn_handler with
access_check=None skips re-validation of widget ownership and channel status
under the chat lock, creating a race condition where a concurrent channel switch
to VOICE can bypass the pre-check. Instead of passing access_check=None, provide
a validation function that re-checks the widget session ownership and
current_channel status under the chat lock to ensure the session is still on the
CHAT channel before starting the SSE stream.

---

Nitpick comments:
In `@app/api/routers/breeze_buddy/widget/__init__.py`:
- Around line 154-159: The async function `send_widget_initial_turn` is missing
a return type hint in its function signature. Add an appropriate return type
annotation (such as Response or StreamingResponse) between the closing
parenthesis of the parameter list and the colon for consistency with the typed
router surface and to comply with the coding guidelines requiring type hints on
all function signatures.

In `@app/api/routers/breeze_buddy/widget/handlers.py`:
- Around line 301-305: The function `send_widget_initial_turn_handler` is
missing an explicit return type annotation in its signature. Add a return type
hint after the closing parenthesis of the parameter list using the arrow
notation (->). Determine the appropriate return type based on what the function
actually returns and add it to comply with the coding guidelines requiring type
hints on all function signatures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98dfd63e-8bb1-4253-92c0-c4dc6590391a

📥 Commits

Reviewing files that changed from the base of the PR and between cd803b3 and bc32c87.

📒 Files selected for processing (4)
  • app/ai/voice/agents/breeze_buddy/chat/agent.py
  • app/api/routers/breeze_buddy/chat/handlers.py
  • app/api/routers/breeze_buddy/widget/__init__.py
  • app/api/routers/breeze_buddy/widget/handlers.py

Comment on lines +703 to +717
history_limit = await CHAT_HISTORY_REPLAY_LIMIT()
history_rows = await list_chat_messages_for_session(
session_id, limit=history_limit
)
history: list = blocks_to_llm_context_messages(
[
{
"role": row.role.value,
"content": row.content,
"content_blocks": row.content_blocks,
}
for row in history_rows
if row.role in (ChatMessageRole.USER, ChatMessageRole.ASSISTANT)
]
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block /initial-turn once the session already has messages.

Line 703 already fetches message history, but Line 755 still runs run_initial_turn(...) unconditionally. Since initial-turn runs a normal LLM/tool cycle, retries on non-fresh sessions can duplicate writes/side effects.

💡 Suggested fix
         history_rows = await list_chat_messages_for_session(
             session_id, limit=history_limit
         )
-        history: list = blocks_to_llm_context_messages(
-            [
-                {
-                    "role": row.role.value,
-                    "content": row.content,
-                    "content_blocks": row.content_blocks,
-                }
-                for row in history_rows
-                if row.role in (ChatMessageRole.USER, ChatMessageRole.ASSISTANT)
-            ]
-        )
+        if any(
+            row.role in (ChatMessageRole.USER, ChatMessageRole.ASSISTANT)
+            for row in history_rows
+        ):
+            raise HTTPException(
+                status_code=status.HTTP_409_CONFLICT,
+                detail=(
+                    "Initial turn already started for this session. "
+                    "Use /message for subsequent turns."
+                ),
+            )

Also applies to: 750-757

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/routers/breeze_buddy/chat/handlers.py` around lines 703 - 717, The
issue is that run_initial_turn is being called unconditionally even when the
session already has message history, which can cause duplicate writes and side
effects on session retries. After fetching history_rows using
list_chat_messages_for_session around line 703, add a conditional check before
the run_initial_turn call to only execute it when history_rows is empty. Wrap
the run_initial_turn invocation in an if statement that verifies the session is
fresh by checking if the history list is empty, ensuring initial-turn logic only
runs on new sessions.

Comment on lines +327 to +351
# Cheap pre-check before delegating — the chat handler will reload
# the session under its own lock, but we want a clean 409 for
# "voice is live" before the SSE stream opens.
session = await get_chat_session_by_id(session_id)
if session is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Widget session '{session_id}' not found",
)
assert_widget_session_ownership(session, ctx)
if session.status == ChatSessionStatus.ENDED:
raise HTTPException(
status_code=status.HTTP_410_GONE,
detail=f"Widget session '{session_id}' has ended",
)
if session.current_channel != WidgetChannel.CHAT:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=(
f"Widget session is on channel {session.current_channel.value}; "
"end the voice attachment first."
),
)

return await send_chat_initial_turn_handler(session_id, access_check=None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-check widget ownership/channel under the chat lock, not only before delegation.

The pre-check is done before lock acquisition, but Line 351 passes access_check=None, so locked revalidation is skipped. A concurrent channel switch to VOICE can slip through and still start SSE.

💡 Suggested fix
+    def _locked_access_check(fresh: ChatSession) -> None:
+        assert_widget_session_ownership(fresh, ctx)
+        if fresh.current_channel != WidgetChannel.CHAT:
+            raise HTTPException(
+                status_code=status.HTTP_409_CONFLICT,
+                detail=(
+                    f"Widget session is on channel {fresh.current_channel.value}; "
+                    "end the voice attachment first."
+                ),
+            )
+
-    return await send_chat_initial_turn_handler(session_id, access_check=None)
+    return await send_chat_initial_turn_handler(
+        session_id, access_check=_locked_access_check
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/routers/breeze_buddy/widget/handlers.py` around lines 327 - 351, The
call to send_chat_initial_turn_handler with access_check=None skips
re-validation of widget ownership and channel status under the chat lock,
creating a race condition where a concurrent channel switch to VOICE can bypass
the pre-check. Instead of passing access_check=None, provide a validation
function that re-checks the widget session ownership and current_channel status
under the chat lock to ensure the session is still on the CHAT channel before
starting the SSE stream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants