From 42e779581b973988374856c5f64cc6fe86a12a9c Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sat, 10 May 2025 07:27:34 +0000 Subject: [PATCH 1/8] Port to OpenAI-agents SDK --- src/backend/fastapi_app/__init__.py | 2 +- src/backend/fastapi_app/api_models.py | 5 +- .../fastapi_app/prompts/query_fewshots.json | 90 +++------- src/backend/fastapi_app/rag_advanced.py | 157 ++++++++++-------- src/backend/fastapi_app/rag_base.py | 17 +- src/backend/pyproject.toml | 5 +- 6 files changed, 123 insertions(+), 153 deletions(-) diff --git a/src/backend/fastapi_app/__init__.py b/src/backend/fastapi_app/__init__.py index b760fdb2..4f2fd484 100644 --- a/src/backend/fastapi_app/__init__.py +++ b/src/backend/fastapi_app/__init__.py @@ -58,7 +58,7 @@ def create_app(testing: bool = False): else: if not testing: load_dotenv(override=True) - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.DEBUG) # Turn off particularly noisy INFO level logs from Azure Core SDK: logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) diff --git a/src/backend/fastapi_app/api_models.py b/src/backend/fastapi_app/api_models.py index 46574c4e..a5a03385 100644 --- a/src/backend/fastapi_app/api_models.py +++ b/src/backend/fastapi_app/api_models.py @@ -1,9 +1,8 @@ from enum import Enum -from typing import Any, Optional, Union +from typing import Any, Optional from openai.types.chat import ChatCompletionMessageParam from pydantic import BaseModel, Field -from pydantic_ai.messages import ModelRequest, ModelResponse class AIChatRoles(str, Enum): @@ -96,7 +95,7 @@ class ChatParams(ChatRequestOverrides): enable_text_search: bool enable_vector_search: bool original_user_query: str - past_messages: list[Union[ModelRequest, ModelResponse]] + past_messages: list[ChatCompletionMessageParam] class Filter(BaseModel): diff --git a/src/backend/fastapi_app/prompts/query_fewshots.json b/src/backend/fastapi_app/prompts/query_fewshots.json index d5ab7f2b..3efce3bf 100644 --- a/src/backend/fastapi_app/prompts/query_fewshots.json +++ b/src/backend/fastapi_app/prompts/query_fewshots.json @@ -1,76 +1,34 @@ [ - { - "parts": [ - { - "content": "good options for climbing gear that can be used outside?", - "timestamp": "2025-05-07T19:02:46.977501Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" - }, - { - "parts": [ - { - "tool_name": "search_database", - "args": "{\"search_query\":\"climbing gear outside\"}", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "part_kind": "tool-call" - } - ], - "model_name": "gpt-4o-mini-2024-07-18", - "timestamp": "2025-05-07T19:02:47Z", - "kind": "response" - }, - { - "parts": [ + {"role": "user", "content": "good options for climbing gear that can be used outside?"}, + {"role": "assistant", "tool_calls": [ { - "tool_name": "search_database", - "content": "Search results for climbing gear that can be used outside: ...", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "timestamp": "2025-05-07T19:02:48.242408Z", - "part_kind": "tool-return" + "id": "call_abc123", + "type": "function", + "function": { + "arguments": "{\"search_query\":\"climbing gear outside\"}", + "name": "search_database" + } } - ], - "instructions": null, - "kind": "request" - }, + ]}, { - "parts": [ - { - "content": "are there any shoes less than $50?", - "timestamp": "2025-05-07T19:02:46.977501Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" + "role": "tool", + "tool_call_id": "call_abc123", + "content": "Search results for climbing gear that can be used outside: ..." }, - { - "parts": [ + {"role": "user", "content": "are there any shoes less than $50?"}, + {"role": "assistant", "tool_calls": [ { - "tool_name": "search_database", - "args": "{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "part_kind": "tool-call" + "id": "call_abc456", + "type": "function", + "function": { + "arguments": "{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}", + "name": "search_database" + } } - ], - "model_name": "gpt-4o-mini-2024-07-18", - "timestamp": "2025-05-07T19:02:47Z", - "kind": "response" - }, + ]}, { - "parts": [ - { - "tool_name": "search_database", - "content": "Search results for shoes cheaper than 50: ...", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "timestamp": "2025-05-07T19:02:48.242408Z", - "part_kind": "tool-return" - } - ], - "instructions": null, - "kind": "request" + "role": "tool", + "tool_call_id": "call_abc456", + "content": "Search results for shoes cheaper than 50: ..." } ] diff --git a/src/backend/fastapi_app/rag_advanced.py b/src/backend/fastapi_app/rag_advanced.py index 3541d8c7..1061ae37 100644 --- a/src/backend/fastapi_app/rag_advanced.py +++ b/src/backend/fastapi_app/rag_advanced.py @@ -1,13 +1,18 @@ +import json from collections.abc import AsyncGenerator from typing import Optional, Union +from agents import Agent, ModelSettings, OpenAIChatCompletionsModel, Runner, function_tool, set_tracing_disabled from openai import AsyncAzureOpenAI, AsyncOpenAI -from openai.types.chat import ChatCompletionMessageParam -from pydantic_ai import Agent, RunContext -from pydantic_ai.messages import ModelMessagesTypeAdapter -from pydantic_ai.models.openai import OpenAIModel -from pydantic_ai.providers.openai import OpenAIProvider -from pydantic_ai.settings import ModelSettings +from openai.types.chat import ( + ChatCompletionMessageParam, +) +from openai.types.responses import ( + EasyInputMessageParam, + ResponseFunctionToolCallParam, + ResponseTextDeltaEvent, +) +from openai.types.responses.response_input_item_param import FunctionCallOutput from fastapi_app.api_models import ( AIChatRoles, @@ -24,7 +29,9 @@ ThoughtStep, ) from fastapi_app.postgres_searcher import PostgresSearcher -from fastapi_app.rag_base import ChatParams, RAGChatBase +from fastapi_app.rag_base import RAGChatBase + +set_tracing_disabled(disabled=True) class AdvancedRAGChat(RAGChatBase): @@ -46,34 +53,29 @@ def __init__( self.model_for_thoughts = ( {"model": chat_model, "deployment": chat_deployment} if chat_deployment else {"model": chat_model} ) - pydantic_chat_model = OpenAIModel( - chat_model if chat_deployment is None else chat_deployment, - provider=OpenAIProvider(openai_client=openai_chat_client), + openai_agents_model = OpenAIChatCompletionsModel( + model=chat_model if chat_deployment is None else chat_deployment, openai_client=openai_chat_client ) - self.search_agent = Agent[ChatParams, SearchResults]( - pydantic_chat_model, - model_settings=ModelSettings( - temperature=0.0, - max_tokens=500, - **({"seed": self.chat_params.seed} if self.chat_params.seed is not None else {}), - ), - system_prompt=self.query_prompt_template, - tools=[self.search_database], - output_type=SearchResults, + self.search_agent = Agent( + name="Searcher", + instructions=self.query_prompt_template, + tools=[function_tool(self.search_database)], + tool_use_behavior="stop_on_first_tool", + model=openai_agents_model, ) self.answer_agent = Agent( - pydantic_chat_model, - system_prompt=self.answer_prompt_template, + name="Answerer", + instructions=self.answer_prompt_template, + model=openai_agents_model, model_settings=ModelSettings( temperature=self.chat_params.temperature, max_tokens=self.chat_params.response_token_limit, - **({"seed": self.chat_params.seed} if self.chat_params.seed is not None else {}), + extra_body={"seed": self.chat_params.seed} if self.chat_params.seed is not None else {}, ), ) async def search_database( self, - ctx: RunContext[ChatParams], search_query: str, price_filter: Optional[PriceFilter] = None, brand_filter: Optional[BrandFilter] = None, @@ -97,9 +99,9 @@ async def search_database( filters.append(brand_filter) results = await self.searcher.search_and_embed( search_query, - top=ctx.deps.top, - enable_vector_search=ctx.deps.enable_vector_search, - enable_text_search=ctx.deps.enable_text_search, + top=self.chat_params.top, + enable_vector_search=self.chat_params.enable_vector_search, + enable_text_search=self.chat_params.enable_text_search, filters=filters, ) return SearchResults( @@ -107,56 +109,78 @@ async def search_database( ) async def prepare_context(self) -> tuple[list[ItemPublic], list[ThoughtStep]]: - few_shots = ModelMessagesTypeAdapter.validate_json(self.query_fewshots) + few_shots = json.loads(self.query_fewshots) + few_shot_inputs = [] + for few_shot in few_shots: + if few_shot["role"] == "user": + message = EasyInputMessageParam(role="user", content=few_shot["content"]) + elif few_shot["role"] == "assistant" and few_shot["tool_calls"] is not None: + message = ResponseFunctionToolCallParam( + id="madeup", + call_id=few_shot["tool_calls"][0]["id"], + name=few_shot["tool_calls"][0]["function"]["name"], + arguments=few_shot["tool_calls"][0]["function"]["arguments"], + type="function_call", + ) + elif few_shot["role"] == "tool" and few_shot["tool_call_id"] is not None: + message = FunctionCallOutput( + id="madeupoutput", + call_id=few_shot["tool_call_id"], + output=few_shot["content"], + type="function_call_output", + ) + few_shot_inputs.append(message) + user_query = f"Find search results for user query: {self.chat_params.original_user_query}" - results = await self.search_agent.run( - user_query, - message_history=few_shots + self.chat_params.past_messages, - deps=self.chat_params, - ) - items = results.output.items + new_user_message = EasyInputMessageParam(role="user", content=user_query) + all_messages = few_shot_inputs + self.chat_params.past_messages + [new_user_message] + + run_results = await Runner.run(self.search_agent, input=all_messages) + search_results = run_results.new_items[-1].output + thoughts = [ ThoughtStep( title="Prompt to generate search arguments", - description=results.all_messages(), + description=run_results.input, props=self.model_for_thoughts, ), ThoughtStep( title="Search using generated search arguments", - description=results.output.query, + description=search_results.query, props={ "top": self.chat_params.top, "vector_search": self.chat_params.enable_vector_search, "text_search": self.chat_params.enable_text_search, - "filters": results.output.filters, + "filters": search_results.filters, }, ), ThoughtStep( title="Search results", - description=items, + description=search_results.items, ), ] - return items, thoughts + return search_results.items, thoughts async def answer( self, items: list[ItemPublic], earlier_thoughts: list[ThoughtStep], ) -> RetrievalResponse: - response = await self.answer_agent.run( - user_prompt=self.prepare_rag_request(self.chat_params.original_user_query, items), - message_history=self.chat_params.past_messages, + run_results = await Runner.run( + self.answer_agent, + input=self.chat_params.past_messages + + [{"content": self.prepare_rag_request(self.chat_params.original_user_query, items), "role": "user"}], ) return RetrievalResponse( - message=Message(content=str(response.output), role=AIChatRoles.ASSISTANT), + message=Message(content=str(run_results.final_output), role=AIChatRoles.ASSISTANT), context=RAGContext( data_points={item.id: item for item in items}, thoughts=earlier_thoughts + [ ThoughtStep( title="Prompt to generate answer", - description=response.all_messages(), + description=run_results.input, props=self.model_for_thoughts, ), ], @@ -168,24 +192,27 @@ async def answer_stream( items: list[ItemPublic], earlier_thoughts: list[ThoughtStep], ) -> AsyncGenerator[RetrievalResponseDelta, None]: - async with self.answer_agent.run_stream( - self.prepare_rag_request(self.chat_params.original_user_query, items), - message_history=self.chat_params.past_messages, - ) as agent_stream_runner: - yield RetrievalResponseDelta( - context=RAGContext( - data_points={item.id: item for item in items}, - thoughts=earlier_thoughts - + [ - ThoughtStep( - title="Prompt to generate answer", - description=agent_stream_runner.all_messages(), - props=self.model_for_thoughts, - ), - ], - ), - ) - - async for message in agent_stream_runner.stream_text(delta=True, debounce_by=None): - yield RetrievalResponseDelta(delta=Message(content=str(message), role=AIChatRoles.ASSISTANT)) - return + run_results = Runner.run_streamed( + self.answer_agent, + input=self.chat_params.past_messages + + [{"content": self.prepare_rag_request(self.chat_params.original_user_query, items), "role": "user"}], + ) + + yield RetrievalResponseDelta( + context=RAGContext( + data_points={item.id: item for item in items}, + thoughts=earlier_thoughts + + [ + ThoughtStep( + title="Prompt to generate answer", + description=run_results.input, + props=self.model_for_thoughts, + ), + ], + ), + ) + + async for event in run_results.stream_events(): + if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent): + yield RetrievalResponseDelta(delta=Message(content=str(event.data.delta), role=AIChatRoles.ASSISTANT)) + return diff --git a/src/backend/fastapi_app/rag_base.py b/src/backend/fastapi_app/rag_base.py index 62bdc800..646b0205 100644 --- a/src/backend/fastapi_app/rag_base.py +++ b/src/backend/fastapi_app/rag_base.py @@ -1,10 +1,8 @@ import pathlib from abc import ABC, abstractmethod from collections.abc import AsyncGenerator -from typing import Union from openai.types.chat import ChatCompletionMessageParam -from pydantic_ai.messages import ModelRequest, ModelResponse, TextPart, UserPromptPart from fastapi_app.api_models import ( ChatParams, @@ -33,19 +31,6 @@ def get_chat_params( if not isinstance(original_user_query, str): raise ValueError("The most recent message content must be a string.") - # Convert to PydanticAI format: - past_messages: list[Union[ModelRequest, ModelResponse]] = [] - for message in messages[:-1]: - content = message["content"] - if not isinstance(content, str): - raise ValueError("All messages must have string content.") - if message["role"] == "user": - past_messages.append(ModelRequest(parts=[UserPromptPart(content=content)])) - elif message["role"] == "assistant": - past_messages.append(ModelResponse(parts=[TextPart(content=content)])) - else: - raise ValueError(f"Cannot convert message: {message}") - return ChatParams( top=overrides.top, temperature=overrides.temperature, @@ -57,7 +42,7 @@ def get_chat_params( enable_text_search=enable_text_search, enable_vector_search=enable_vector_search, original_user_query=original_user_query, - past_messages=past_messages, + past_messages=messages[:-1], ) @abstractmethod diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 7f5ac750..ff537a28 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -6,7 +6,7 @@ dependencies = [ "fastapi>=0.111.0,<1.0.0", "uvicorn>=0.30.1,<1.0.0", "python-dotenv>=1.0.1,<2.0.0", - "environs>=11.0.0,<12.0.0", + "environs>=11.0.0,<15.0.0", "azure-identity>=1.16.1,<2.0.0", "aiohttp>=3.9.5,<4.0.0", "asyncpg>=0.29.0,<1.0.0", @@ -19,7 +19,8 @@ dependencies = [ "opentelemetry-instrumentation-sqlalchemy", "opentelemetry-instrumentation-aiohttp-client", "opentelemetry-instrumentation-openai", - "pydantic-ai-slim[openai]" + "pydantic-ai-slim[openai]", + "openai-agents" ] [build-system] From c6b1801c092d6fb7b3de9d84cea5a7e84268bccd Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sat, 10 May 2025 07:30:07 +0000 Subject: [PATCH 2/8] Port to OpenAI-agents SDK --- src/backend/fastapi_app/rag_simple.py | 75 ++++++++++++++------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/backend/fastapi_app/rag_simple.py b/src/backend/fastapi_app/rag_simple.py index 2d41bb9d..9971eb3c 100644 --- a/src/backend/fastapi_app/rag_simple.py +++ b/src/backend/fastapi_app/rag_simple.py @@ -1,12 +1,10 @@ from collections.abc import AsyncGenerator from typing import Optional, Union +from agents import Agent, ModelSettings, OpenAIChatCompletionsModel, Runner, set_tracing_disabled from openai import AsyncAzureOpenAI, AsyncOpenAI from openai.types.chat import ChatCompletionMessageParam -from pydantic_ai import Agent -from pydantic_ai.models.openai import OpenAIModel -from pydantic_ai.providers.openai import OpenAIProvider -from pydantic_ai.settings import ModelSettings +from openai.types.responses import ResponseTextDeltaEvent from fastapi_app.api_models import ( AIChatRoles, @@ -21,6 +19,8 @@ from fastapi_app.postgres_searcher import PostgresSearcher from fastapi_app.rag_base import RAGChatBase +set_tracing_disabled(disabled=True) + class SimpleRAGChat(RAGChatBase): def __init__( @@ -38,17 +38,17 @@ def __init__( self.model_for_thoughts = ( {"model": chat_model, "deployment": chat_deployment} if chat_deployment else {"model": chat_model} ) - pydantic_chat_model = OpenAIModel( - chat_model if chat_deployment is None else chat_deployment, - provider=OpenAIProvider(openai_client=openai_chat_client), + openai_agents_model = OpenAIChatCompletionsModel( + model=chat_model if chat_deployment is None else chat_deployment, openai_client=openai_chat_client ) self.answer_agent = Agent( - pydantic_chat_model, - system_prompt=self.answer_prompt_template, + name="Answerer", + instructions=self.answer_prompt_template, + model=openai_agents_model, model_settings=ModelSettings( temperature=self.chat_params.temperature, max_tokens=self.chat_params.response_token_limit, - **({"seed": self.chat_params.seed} if self.chat_params.seed is not None else {}), + extra_body={"seed": self.chat_params.seed} if self.chat_params.seed is not None else {}, ), ) @@ -85,19 +85,21 @@ async def answer( items: list[ItemPublic], earlier_thoughts: list[ThoughtStep], ) -> RetrievalResponse: - response = await self.answer_agent.run( - user_prompt=self.prepare_rag_request(self.chat_params.original_user_query, items), - message_history=self.chat_params.past_messages, + run_results = await Runner.run( + self.answer_agent, + input=self.chat_params.past_messages + + [{"content": self.prepare_rag_request(self.chat_params.original_user_query, items), "role": "user"}], ) + return RetrievalResponse( - message=Message(content=str(response.output), role=AIChatRoles.ASSISTANT), + message=Message(content=str(run_results.final_output), role=AIChatRoles.ASSISTANT), context=RAGContext( data_points={item.id: item for item in items}, thoughts=earlier_thoughts + [ ThoughtStep( title="Prompt to generate answer", - description=response.all_messages(), + description=run_results.input, props=self.model_for_thoughts, ), ], @@ -109,24 +111,27 @@ async def answer_stream( items: list[ItemPublic], earlier_thoughts: list[ThoughtStep], ) -> AsyncGenerator[RetrievalResponseDelta, None]: - async with self.answer_agent.run_stream( - self.prepare_rag_request(self.chat_params.original_user_query, items), - message_history=self.chat_params.past_messages, - ) as agent_stream_runner: - yield RetrievalResponseDelta( - context=RAGContext( - data_points={item.id: item for item in items}, - thoughts=earlier_thoughts - + [ - ThoughtStep( - title="Prompt to generate answer", - description=agent_stream_runner.all_messages(), - props=self.model_for_thoughts, - ), - ], - ), - ) + run_results = Runner.run_streamed( + self.answer_agent, + input=self.chat_params.past_messages + + [{"content": self.prepare_rag_request(self.chat_params.original_user_query, items), "role": "user"}], + ) + + yield RetrievalResponseDelta( + context=RAGContext( + data_points={item.id: item for item in items}, + thoughts=earlier_thoughts + + [ + ThoughtStep( + title="Prompt to generate answer", + description=run_results.input, + props=self.model_for_thoughts, + ), + ], + ), + ) - async for message in agent_stream_runner.stream_text(delta=True, debounce_by=None): - yield RetrievalResponseDelta(delta=Message(content=str(message), role=AIChatRoles.ASSISTANT)) - return + async for event in run_results.stream_events(): + if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent): + yield RetrievalResponseDelta(delta=Message(content=str(event.data.delta), role=AIChatRoles.ASSISTANT)) + return From 7bb7453bc4b1636fb4eb1464215aaa5cf9550133 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 05:04:49 +0000 Subject: [PATCH 3/8] Fix tests, mypy --- src/backend/fastapi_app/api_models.py | 6 +- .../fastapi_app/prompts/query_fewshots.json | 58 +++--- src/backend/fastapi_app/rag_advanced.py | 53 ++--- src/backend/fastapi_app/rag_base.py | 8 +- src/backend/fastapi_app/rag_simple.py | 5 +- .../advanced_chat_flow_response.json | 190 +++--------------- ...ced_chat_streaming_flow_response.jsonlines | 2 +- .../simple_chat_flow_response.json | 28 +-- ...le_chat_flow_message_history_response.json | 44 +--- ...ple_chat_streaming_flow_response.jsonlines | 2 +- 10 files changed, 93 insertions(+), 303 deletions(-) diff --git a/src/backend/fastapi_app/api_models.py b/src/backend/fastapi_app/api_models.py index a5a03385..06d14a6b 100644 --- a/src/backend/fastapi_app/api_models.py +++ b/src/backend/fastapi_app/api_models.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Any, Optional -from openai.types.chat import ChatCompletionMessageParam +from openai.types.responses import ResponseInputItemParam from pydantic import BaseModel, Field @@ -36,7 +36,7 @@ class ChatRequestContext(BaseModel): class ChatRequest(BaseModel): - messages: list[ChatCompletionMessageParam] + messages: list[ResponseInputItemParam] context: ChatRequestContext sessionState: Optional[Any] = None @@ -95,7 +95,7 @@ class ChatParams(ChatRequestOverrides): enable_text_search: bool enable_vector_search: bool original_user_query: str - past_messages: list[ChatCompletionMessageParam] + past_messages: list[ResponseInputItemParam] class Filter(BaseModel): diff --git a/src/backend/fastapi_app/prompts/query_fewshots.json b/src/backend/fastapi_app/prompts/query_fewshots.json index 3efce3bf..0ef450fd 100644 --- a/src/backend/fastapi_app/prompts/query_fewshots.json +++ b/src/backend/fastapi_app/prompts/query_fewshots.json @@ -1,34 +1,36 @@ [ - {"role": "user", "content": "good options for climbing gear that can be used outside?"}, - {"role": "assistant", "tool_calls": [ - { - "id": "call_abc123", - "type": "function", - "function": { - "arguments": "{\"search_query\":\"climbing gear outside\"}", - "name": "search_database" - } - } - ]}, { - "role": "tool", - "tool_call_id": "call_abc123", - "content": "Search results for climbing gear that can be used outside: ..." + "role": "user", + "content": "good options for climbing gear that can be used outside?" }, - {"role": "user", "content": "are there any shoes less than $50?"}, - {"role": "assistant", "tool_calls": [ - { - "id": "call_abc456", - "type": "function", - "function": { - "arguments": "{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}", - "name": "search_database" - } - } - ]}, { - "role": "tool", - "tool_call_id": "call_abc456", - "content": "Search results for shoes cheaper than 50: ..." + "id": "madeup", + "call_id": "call_abc123", + "name": "search_database", + "arguments": "{\"search_query\":\"climbing gear outside\"}", + "type": "function_call" + }, + { + "id": "madeupoutput", + "call_id": "call_abc123", + "output": "Search results for climbing gear that can be used outside: ...", + "type": "function_call_output" + }, + { + "role": "user", + "content": "are there any shoes less than $50?" + }, + { + "id": "madeup", + "call_id": "call_abc456", + "name": "search_database", + "arguments": "{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}", + "type": "function_call" + }, + { + "id": "madeupoutput", + "call_id": "call_abc456", + "output": "Search results for shoes cheaper than 50: ...", + "type": "function_call_output" } ] diff --git a/src/backend/fastapi_app/rag_advanced.py b/src/backend/fastapi_app/rag_advanced.py index 1061ae37..158afc4c 100644 --- a/src/backend/fastapi_app/rag_advanced.py +++ b/src/backend/fastapi_app/rag_advanced.py @@ -2,17 +2,17 @@ from collections.abc import AsyncGenerator from typing import Optional, Union -from agents import Agent, ModelSettings, OpenAIChatCompletionsModel, Runner, function_tool, set_tracing_disabled -from openai import AsyncAzureOpenAI, AsyncOpenAI -from openai.types.chat import ( - ChatCompletionMessageParam, -) -from openai.types.responses import ( - EasyInputMessageParam, - ResponseFunctionToolCallParam, - ResponseTextDeltaEvent, +from agents import ( + Agent, + ModelSettings, + OpenAIChatCompletionsModel, + Runner, + ToolCallOutputItem, + function_tool, + set_tracing_disabled, ) -from openai.types.responses.response_input_item_param import FunctionCallOutput +from openai import AsyncAzureOpenAI, AsyncOpenAI +from openai.types.responses import EasyInputMessageParam, ResponseInputItemParam, ResponseTextDeltaEvent from fastapi_app.api_models import ( AIChatRoles, @@ -41,7 +41,7 @@ class AdvancedRAGChat(RAGChatBase): def __init__( self, *, - messages: list[ChatCompletionMessageParam], + messages: list[ResponseInputItemParam], overrides: ChatRequestOverrides, searcher: PostgresSearcher, openai_chat_client: Union[AsyncOpenAI, AsyncAzureOpenAI], @@ -109,34 +109,17 @@ async def search_database( ) async def prepare_context(self) -> tuple[list[ItemPublic], list[ThoughtStep]]: - few_shots = json.loads(self.query_fewshots) - few_shot_inputs = [] - for few_shot in few_shots: - if few_shot["role"] == "user": - message = EasyInputMessageParam(role="user", content=few_shot["content"]) - elif few_shot["role"] == "assistant" and few_shot["tool_calls"] is not None: - message = ResponseFunctionToolCallParam( - id="madeup", - call_id=few_shot["tool_calls"][0]["id"], - name=few_shot["tool_calls"][0]["function"]["name"], - arguments=few_shot["tool_calls"][0]["function"]["arguments"], - type="function_call", - ) - elif few_shot["role"] == "tool" and few_shot["tool_call_id"] is not None: - message = FunctionCallOutput( - id="madeupoutput", - call_id=few_shot["tool_call_id"], - output=few_shot["content"], - type="function_call_output", - ) - few_shot_inputs.append(message) - + few_shots: list[ResponseInputItemParam] = json.loads(self.query_fewshots) user_query = f"Find search results for user query: {self.chat_params.original_user_query}" new_user_message = EasyInputMessageParam(role="user", content=user_query) - all_messages = few_shot_inputs + self.chat_params.past_messages + [new_user_message] + all_messages = few_shots + self.chat_params.past_messages + [new_user_message] run_results = await Runner.run(self.search_agent, input=all_messages) - search_results = run_results.new_items[-1].output + most_recent_response = run_results.new_items[-1] + if isinstance(most_recent_response, ToolCallOutputItem): + search_results = most_recent_response.output + else: + raise ValueError("Error retrieving search results, model did not call tool properly") thoughts = [ ThoughtStep( diff --git a/src/backend/fastapi_app/rag_base.py b/src/backend/fastapi_app/rag_base.py index 646b0205..54e633c2 100644 --- a/src/backend/fastapi_app/rag_base.py +++ b/src/backend/fastapi_app/rag_base.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from collections.abc import AsyncGenerator -from openai.types.chat import ChatCompletionMessageParam +from openai.types.responses import ResponseInputItemParam from fastapi_app.api_models import ( ChatParams, @@ -18,16 +18,14 @@ class RAGChatBase(ABC): prompts_dir = pathlib.Path(__file__).parent / "prompts/" answer_prompt_template = open(prompts_dir / "answer.txt").read() - def get_chat_params( - self, messages: list[ChatCompletionMessageParam], overrides: ChatRequestOverrides - ) -> ChatParams: + def get_chat_params(self, messages: list[ResponseInputItemParam], overrides: ChatRequestOverrides) -> ChatParams: response_token_limit = 1024 prompt_template = overrides.prompt_template or self.answer_prompt_template enable_text_search = overrides.retrieval_mode in ["text", "hybrid", None] enable_vector_search = overrides.retrieval_mode in ["vectors", "hybrid", None] - original_user_query = messages[-1]["content"] + original_user_query = messages[-1].get("content") if not isinstance(original_user_query, str): raise ValueError("The most recent message content must be a string.") diff --git a/src/backend/fastapi_app/rag_simple.py b/src/backend/fastapi_app/rag_simple.py index 9971eb3c..a351cd9e 100644 --- a/src/backend/fastapi_app/rag_simple.py +++ b/src/backend/fastapi_app/rag_simple.py @@ -3,8 +3,7 @@ from agents import Agent, ModelSettings, OpenAIChatCompletionsModel, Runner, set_tracing_disabled from openai import AsyncAzureOpenAI, AsyncOpenAI -from openai.types.chat import ChatCompletionMessageParam -from openai.types.responses import ResponseTextDeltaEvent +from openai.types.responses import ResponseInputItemParam, ResponseTextDeltaEvent from fastapi_app.api_models import ( AIChatRoles, @@ -26,7 +25,7 @@ class SimpleRAGChat(RAGChatBase): def __init__( self, *, - messages: list[ChatCompletionMessageParam], + messages: list[ResponseInputItemParam], overrides: ChatRequestOverrides, searcher: PostgresSearcher, openai_chat_client: Union[AsyncOpenAI, AsyncAzureOpenAI], diff --git a/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json b/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json index 240a638a..6117ae62 100644 --- a/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json +++ b/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json @@ -19,154 +19,42 @@ "title": "Prompt to generate search arguments", "description": [ { - "parts": [ - { - "content": "good options for climbing gear that can be used outside?", - "timestamp": "2025-05-07T19:02:46.977501Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" + "role": "user", + "content": "good options for climbing gear that can be used outside?" }, { - "parts": [ - { - "tool_name": "search_database", - "args": "{\"search_query\":\"climbing gear outside\"}", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "part_kind": "tool-call" - } - ], - "model_name": "gpt-4o-mini-2024-07-18", - "timestamp": "2025-05-07T19:02:47Z", - "kind": "response" + "id": "madeup", + "call_id": "call_abc123", + "name": "search_database", + "arguments": "{\"search_query\":\"climbing gear outside\"}", + "type": "function_call" }, { - "parts": [ - { - "tool_name": "search_database", - "content": "Search results for climbing gear that can be used outside: ...", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "timestamp": "2025-05-07T19:02:48.242408Z", - "part_kind": "tool-return" - } - ], - "instructions": null, - "kind": "request" + "id": "madeupoutput", + "call_id": "call_abc123", + "output": "Search results for climbing gear that can be used outside: ...", + "type": "function_call_output" }, { - "parts": [ - { - "content": "are there any shoes less than $50?", - "timestamp": "2025-05-07T19:02:46.977501Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" + "role": "user", + "content": "are there any shoes less than $50?" }, { - "parts": [ - { - "tool_name": "search_database", - "args": "{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "part_kind": "tool-call" - } - ], - "model_name": "gpt-4o-mini-2024-07-18", - "timestamp": "2025-05-07T19:02:47Z", - "kind": "response" + "id": "madeup", + "call_id": "call_abc456", + "name": "search_database", + "arguments": "{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}", + "type": "function_call" }, { - "parts": [ - { - "tool_name": "search_database", - "content": "Search results for shoes cheaper than 50: ...", - "tool_call_id": "call_4HeBCmo2uioV6CyoePEGyZPc", - "timestamp": "2025-05-07T19:02:48.242408Z", - "part_kind": "tool-return" - } - ], - "instructions": null, - "kind": "request" + "id": "madeupoutput", + "call_id": "call_abc456", + "output": "Search results for shoes cheaper than 50: ...", + "type": "function_call_output" }, { - "parts": [ - { - "content": "Find search results for user query: What is the capital of France?", - "timestamp": "2024-01-01T12:00:00Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" - }, - { - "parts": [ - { - "tool_name": "search_database", - "args": "{\"search_query\":\"climbing gear outside\"}", - "tool_call_id": "call_abc123", - "part_kind": "tool-call" - } - ], - "model_name": "test-model", - "timestamp": "1970-01-01T00:00:00Z", - "kind": "response" - }, - { - "parts": [ - { - "tool_name": "search_database", - "content": { - "query": "climbing gear outside", - "items": [ - { - "id": 1, - "type": "Footwear", - "brand": "Daybird", - "name": "Wanderer Black Hiking Boots", - "description": "Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.", - "price": 109.99 - } - ], - "filters": [] - }, - "tool_call_id": "call_abc123", - "timestamp": "2024-01-01T12:00:00Z", - "part_kind": "tool-return" - } - ], - "instructions": null, - "kind": "request" - }, - { - "parts": [ - { - "tool_name": "final_result", - "args": "{\"query\": \"capital of France\", \"items\": [{\"id\": 1, \"type\": \"Footwear\", \"brand\": \"Daybird\", \"name\": \"Wanderer Black Hiking Boots\", \"description\": \"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.\", \"price\": 109.99}], \"filters\": []}", - "tool_call_id": "call_abc123final", - "part_kind": "tool-call" - } - ], - "model_name": "test-model", - "timestamp": "1970-01-01T00:00:00Z", - "kind": "response" - }, - { - "parts": [ - { - "tool_name": "final_result", - "content": "Final result processed.", - "tool_call_id": "call_abc123final", - "timestamp": "2024-01-01T12:00:00Z", - "part_kind": "tool-return" - } - ], - "instructions": null, - "kind": "request" + "role": "user", + "content": "Find search results for user query: What is the capital of France?" } ], "props": { @@ -176,7 +64,7 @@ }, { "title": "Search using generated search arguments", - "description": "capital of France", + "description": "climbing gear outside", "props": { "top": 1, "vector_search": true, @@ -202,32 +90,8 @@ "title": "Prompt to generate answer", "description": [ { - "parts": [ - { - "content": "Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51].", - "timestamp": "2024-01-01T12:00:00Z", - "dynamic_ref": null, - "part_kind": "system-prompt" - }, - { - "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", - "timestamp": "2024-01-01T12:00:00Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" - }, - { - "parts": [ - { - "content": "The capital of France is Paris. [Benefit_Options-2.pdf].", - "part_kind": "text" - } - ], - "model_name": "test-model", - "timestamp": "1970-01-01T00:00:00Z", - "kind": "response" + "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", + "role": "user" } ], "props": { diff --git a/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines b/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines index e241106d..4f9f5646 100644 --- a/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines +++ b/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines @@ -1,2 +1,2 @@ -{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Prompt to generate search arguments","description":[{"parts":[{"content":"good options for climbing gear that can be used outside?","timestamp":"2025-05-07T19:02:46.977501Z","part_kind":"user-prompt"}],"instructions":null,"kind":"request"},{"parts":[{"tool_name":"search_database","args":"{\"search_query\":\"climbing gear outside\"}","tool_call_id":"call_4HeBCmo2uioV6CyoePEGyZPc","part_kind":"tool-call"}],"model_name":"gpt-4o-mini-2024-07-18","timestamp":"2025-05-07T19:02:47Z","kind":"response"},{"parts":[{"tool_name":"search_database","content":"Search results for climbing gear that can be used outside: ...","tool_call_id":"call_4HeBCmo2uioV6CyoePEGyZPc","timestamp":"2025-05-07T19:02:48.242408Z","part_kind":"tool-return"}],"instructions":null,"kind":"request"},{"parts":[{"content":"are there any shoes less than $50?","timestamp":"2025-05-07T19:02:46.977501Z","part_kind":"user-prompt"}],"instructions":null,"kind":"request"},{"parts":[{"tool_name":"search_database","args":"{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}","tool_call_id":"call_4HeBCmo2uioV6CyoePEGyZPc","part_kind":"tool-call"}],"model_name":"gpt-4o-mini-2024-07-18","timestamp":"2025-05-07T19:02:47Z","kind":"response"},{"parts":[{"tool_name":"search_database","content":"Search results for shoes cheaper than 50: ...","tool_call_id":"call_4HeBCmo2uioV6CyoePEGyZPc","timestamp":"2025-05-07T19:02:48.242408Z","part_kind":"tool-return"}],"instructions":null,"kind":"request"},{"parts":[{"content":"Find search results for user query: What is the capital of France?","timestamp":"2024-01-01T12:00:00Z","part_kind":"user-prompt"}],"instructions":null,"kind":"request"},{"parts":[{"tool_name":"search_database","args":"{\"search_query\":\"climbing gear outside\"}","tool_call_id":"call_abc123","part_kind":"tool-call"}],"model_name":"test-model","timestamp":"1970-01-01T00:00:00Z","kind":"response"},{"parts":[{"tool_name":"search_database","content":{"query":"climbing gear outside","items":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"filters":[]},"tool_call_id":"call_abc123","timestamp":"2024-01-01T12:00:00Z","part_kind":"tool-return"}],"instructions":null,"kind":"request"},{"parts":[{"tool_name":"final_result","args":"{\"query\": \"capital of France\", \"items\": [{\"id\": 1, \"type\": \"Footwear\", \"brand\": \"Daybird\", \"name\": \"Wanderer Black Hiking Boots\", \"description\": \"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.\", \"price\": 109.99}], \"filters\": []}","tool_call_id":"call_abc123final","part_kind":"tool-call"}],"model_name":"test-model","timestamp":"1970-01-01T00:00:00Z","kind":"response"},{"parts":[{"tool_name":"final_result","content":"Final result processed.","tool_call_id":"call_abc123final","timestamp":"2024-01-01T12:00:00Z","part_kind":"tool-return"}],"instructions":null,"kind":"request"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}},{"title":"Search using generated search arguments","description":"capital of France","props":{"top":1,"vector_search":true,"text_search":true,"filters":[]}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"parts":[{"content":"Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51].","timestamp":"2024-01-01T12:00:00Z","dynamic_ref":null,"part_kind":"system-prompt"},{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","timestamp":"2024-01-01T12:00:00Z","part_kind":"user-prompt"}],"instructions":null,"kind":"request"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} +{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Prompt to generate search arguments","description":[{"role":"user","content":"good options for climbing gear that can be used outside?"},{"id":"madeup","call_id":"call_abc123","name":"search_database","arguments":"{\"search_query\":\"climbing gear outside\"}","type":"function_call"},{"id":"madeupoutput","call_id":"call_abc123","output":"Search results for climbing gear that can be used outside: ...","type":"function_call_output"},{"role":"user","content":"are there any shoes less than $50?"},{"id":"madeup","call_id":"call_abc456","name":"search_database","arguments":"{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}","type":"function_call"},{"id":"madeupoutput","call_id":"call_abc456","output":"Search results for shoes cheaper than 50: ...","type":"function_call_output"},{"role":"user","content":"Find search results for user query: What is the capital of France?"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}},{"title":"Search using generated search arguments","description":"climbing gear outside","props":{"top":1,"vector_search":true,"text_search":true,"filters":[]}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","role":"user"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} {"delta":{"content":"The capital of France is Paris. [Benefit_Options-2.pdf].","role":"assistant"},"context":null,"sessionState":null} diff --git a/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json b/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json index 337a67d3..91840e7e 100644 --- a/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json +++ b/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json @@ -42,32 +42,8 @@ "title": "Prompt to generate answer", "description": [ { - "parts": [ - { - "content": "Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51].", - "timestamp": "2024-01-01T12:00:00Z", - "dynamic_ref": null, - "part_kind": "system-prompt" - }, - { - "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", - "timestamp": "2024-01-01T12:00:00Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" - }, - { - "parts": [ - { - "content": "The capital of France is Paris. [Benefit_Options-2.pdf].", - "part_kind": "text" - } - ], - "model_name": "test-model", - "timestamp": "1970-01-01T00:00:00Z", - "kind": "response" + "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", + "role": "user" } ], "props": { diff --git a/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json b/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json index 6bc9d4ec..dcdbd300 100644 --- a/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json +++ b/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json @@ -42,48 +42,16 @@ "title": "Prompt to generate answer", "description": [ { - "parts": [ - { - "content": "What is the capital of France?", - "timestamp": "2024-01-01T12:00:00Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" + "content": "What is the capital of France?", + "role": "user" }, { - "parts": [ - { - "content": "The capital of France is Paris.", - "part_kind": "text" - } - ], - "model_name": null, - "timestamp": "2024-01-01T12:00:00Z", - "kind": "response" + "role": "assistant", + "content": "The capital of France is Paris." }, { - "parts": [ - { - "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", - "timestamp": "2024-01-01T12:00:00Z", - "part_kind": "user-prompt" - } - ], - "instructions": null, - "kind": "request" - }, - { - "parts": [ - { - "content": "The capital of France is Paris. [Benefit_Options-2.pdf].", - "part_kind": "text" - } - ], - "model_name": "test-model", - "timestamp": "1970-01-01T00:00:00Z", - "kind": "response" + "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", + "role": "user" } ], "props": { diff --git a/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines b/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines index 28bfd00f..8fd708f4 100644 --- a/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines +++ b/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines @@ -1,2 +1,2 @@ -{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Search query for database","description":"What is the capital of France?","props":{"top":1,"vector_search":true,"text_search":true}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"parts":[{"content":"Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51].","timestamp":"2024-01-01T12:00:00Z","dynamic_ref":null,"part_kind":"system-prompt"},{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","timestamp":"2024-01-01T12:00:00Z","part_kind":"user-prompt"}],"instructions":null,"kind":"request"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} +{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Search query for database","description":"What is the capital of France?","props":{"top":1,"vector_search":true,"text_search":true}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","role":"user"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} {"delta":{"content":"The capital of France is Paris. [Benefit_Options-2.pdf].","role":"assistant"},"context":null,"sessionState":null} From 09f47ada414fe740a5e0d1a65078ce7e63faaf07 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 05:19:36 +0000 Subject: [PATCH 4/8] Update package requirements --- src/backend/pyproject.toml | 3 --- src/backend/requirements.txt | 49 +++++++++++------------------------- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index ff537a28..7ede97c9 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -13,13 +13,10 @@ dependencies = [ "SQLAlchemy[asyncio]>=2.0.30,<3.0.0", "pgvector>=0.3.0,<0.4.0", "openai>=1.34.0,<2.0.0", - "tiktoken>=0.7.0,<0.8.0", - "openai-messages-token-helper>=0.1.8,<0.2.0", "azure-monitor-opentelemetry>=1.6.0,<2.0.0", "opentelemetry-instrumentation-sqlalchemy", "opentelemetry-instrumentation-aiohttp-client", "opentelemetry-instrumentation-openai", - "pydantic-ai-slim[openai]", "openai-agents" ] diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index bc349b03..77d3974d 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -65,12 +65,8 @@ distro==1.9.0 # via openai environs==14.1.1 # via fastapi-app (pyproject.toml) -eval-type-backport==0.2.2 - # via pydantic-ai-slim exceptiongroup==1.2.2 - # via - # anyio - # pydantic-ai-slim + # via anyio fastapi==0.115.8 # via fastapi-app (pyproject.toml) fixedint==0.1.6 @@ -82,7 +78,7 @@ frozenlist==1.5.0 greenlet==3.1.1 # via sqlalchemy griffe==1.7.3 - # via pydantic-ai-slim + # via openai-agents h11==0.14.0 # via # httpcore @@ -90,10 +86,7 @@ h11==0.14.0 httpcore==1.0.7 # via httpx httpx==0.28.0 - # via - # openai - # pydantic-ai-slim - # pydantic-graph + # via openai idna==3.10 # via # anyio @@ -106,8 +99,6 @@ isodate==0.7.2 # via msrest jiter==0.8.0 # via openai -logfire-api==3.14.1 - # via pydantic-graph marshmallow==3.23.1 # via environs msal==1.31.1 @@ -129,9 +120,8 @@ oauthlib==3.2.2 openai==1.77.0 # via # fastapi-app (pyproject.toml) - # openai-messages-token-helper - # pydantic-ai-slim -openai-messages-token-helper==0.1.11 + # openai-agents +openai-agents==0.0.14 # via fastapi-app (pyproject.toml) opentelemetry-api==1.30.0 # via @@ -153,7 +143,6 @@ opentelemetry-api==1.30.0 # opentelemetry-instrumentation-wsgi # opentelemetry-sdk # opentelemetry-semantic-conventions - # pydantic-ai-slim opentelemetry-instrumentation==0.51b0 # via # opentelemetry-instrumentation-aiohttp-client @@ -241,8 +230,6 @@ packaging==24.2 # opentelemetry-instrumentation-sqlalchemy pgvector==0.3.6 # via fastapi-app (pyproject.toml) -pillow==11.0.0 - # via openai-messages-token-helper portalocker==2.10.1 # via msal-extensions propcache==0.2.1 @@ -257,14 +244,9 @@ pydantic==2.10.2 # via # fastapi # openai - # pydantic-ai-slim - # pydantic-graph -pydantic-ai-slim==0.1.10 - # via fastapi-app (pyproject.toml) + # openai-agents pydantic-core==2.27.1 # via pydantic -pydantic-graph==0.1.10 - # via pydantic-ai-slim pyjwt==2.10.1 # via msal python-dotenv==1.0.1 @@ -278,6 +260,7 @@ requests==2.32.3 # azure-core # msal # msrest + # openai-agents # requests-oauthlib # tiktoken requests-oauthlib==2.0.0 @@ -293,34 +276,32 @@ sqlalchemy==2.0.36 starlette==0.41.3 # via fastapi tiktoken==0.7.0 - # via - # fastapi-app (pyproject.toml) - # openai-messages-token-helper - # opentelemetry-instrumentation-openai + # via opentelemetry-instrumentation-openai tqdm==4.67.1 # via openai +types-requests==2.32.0.20250328 + # via openai-agents typing-extensions==4.12.2 # via # anyio # asgiref # azure-core # azure-identity + # environs # fastapi # multidict # openai + # openai-agents # opentelemetry-sdk # pydantic # pydantic-core # sqlalchemy # starlette - # typing-inspection # uvicorn -typing-inspection==0.4.0 - # via - # pydantic-ai-slim - # pydantic-graph urllib3==2.4.0 - # via requests + # via + # requests + # types-requests uvicorn==0.32.1 # via fastapi-app (pyproject.toml) wrapt==1.17.0 From e65ee3d25757adaae25595b289bac61902ae6b1f Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 05:24:44 +0000 Subject: [PATCH 5/8] More dep/mypy updates --- .github/workflows/app-tests.yaml | 2 +- pyproject.toml | 1 - requirements-dev.txt | 1 - tests/conftest.py | 8 -------- 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/app-tests.yaml b/.github/workflows/app-tests.yaml index 0c59b5a2..b432baa3 100644 --- a/.github/workflows/app-tests.yaml +++ b/.github/workflows/app-tests.yaml @@ -123,7 +123,7 @@ jobs: key: mypy${{ matrix.os }}-${{ matrix.python_version }}-${{ hashFiles('requirements-dev.txt', 'src/backend/requirements.txt', 'src/backend/pyproject.toml') }} - name: Run MyPy - run: python3 -m mypy . + run: python3 -m mypy . --python-version ${{ matrix.python_version }} - name: Run Pytest run: python3 -m pytest -s -vv --cov --cov-fail-under=85 diff --git a/pyproject.toml b/pyproject.toml index aa248487..d84731a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ lint.isort.known-first-party = ["fastapi_app"] [tool.mypy] check_untyped_defs = true -python_version = 3.9 exclude = [".venv/*"] [tool.pytest.ini_options] diff --git a/requirements-dev.txt b/requirements-dev.txt index 632cfe91..e73ac0c7 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,4 +14,3 @@ pytest-snapshot locust psycopg2 dotenv-azd -freezegun diff --git a/tests/conftest.py b/tests/conftest.py index f3800dd3..5fe67053 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,6 @@ import pytest import pytest_asyncio from fastapi.testclient import TestClient -from freezegun import freeze_time from openai.types import CreateEmbeddingResponse, Embedding from openai.types.chat import ChatCompletion, ChatCompletionChunk from openai.types.chat.chat_completion import ( @@ -336,13 +335,6 @@ async def mock_acreate(*args, **kwargs): yield -@pytest.fixture(autouse=True) -def frozen_time(): - """Freeze time for all tests to ensure consistent timestamps""" - with freeze_time("2024-01-01 12:00:00"): - yield - - @pytest.fixture(scope="function") def mock_azure_credential(mock_session_env): """Mock the Azure credential for testing.""" From 281397224fbcd6be1e98241e4d42843ab7e8c151 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 05:44:49 +0000 Subject: [PATCH 6/8] Update snapshot --- .../simple_chat_flow_message_history_response.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json b/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json index dcdbd300..5ac5c17a 100644 --- a/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json +++ b/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json @@ -46,8 +46,8 @@ "role": "user" }, { - "role": "assistant", - "content": "The capital of France is Paris." + "content": "The capital of France is Paris.", + "role": "assistant" }, { "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", From 2a3552d733f602788d9ab115c34449689d9e1a5b Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 06:53:22 +0000 Subject: [PATCH 7/8] Add system message to thoughts --- src/backend/fastapi_app/__init__.py | 2 +- src/backend/fastapi_app/rag_advanced.py | 6 +++--- src/backend/fastapi_app/rag_simple.py | 4 ++-- .../advanced_chat_flow_response.json | 6 ++++++ .../advanced_chat_streaming_flow_response.jsonlines | 2 +- .../test_simple_chat_flow/simple_chat_flow_response.json | 3 +++ .../simple_chat_flow_message_history_response.json | 3 +++ .../simple_chat_streaming_flow_response.jsonlines | 2 +- 8 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/backend/fastapi_app/__init__.py b/src/backend/fastapi_app/__init__.py index 4f2fd484..b760fdb2 100644 --- a/src/backend/fastapi_app/__init__.py +++ b/src/backend/fastapi_app/__init__.py @@ -58,7 +58,7 @@ def create_app(testing: bool = False): else: if not testing: load_dotenv(override=True) - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.INFO) # Turn off particularly noisy INFO level logs from Azure Core SDK: logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) diff --git a/src/backend/fastapi_app/rag_advanced.py b/src/backend/fastapi_app/rag_advanced.py index 158afc4c..ed67f5ca 100644 --- a/src/backend/fastapi_app/rag_advanced.py +++ b/src/backend/fastapi_app/rag_advanced.py @@ -124,7 +124,7 @@ async def prepare_context(self) -> tuple[list[ItemPublic], list[ThoughtStep]]: thoughts = [ ThoughtStep( title="Prompt to generate search arguments", - description=run_results.input, + description=[{"content": self.search_agent.instructions}] + run_results.input, props=self.model_for_thoughts, ), ThoughtStep( @@ -163,7 +163,7 @@ async def answer( + [ ThoughtStep( title="Prompt to generate answer", - description=run_results.input, + description=[{"content": self.answer_agent.instructions}] + run_results.input, props=self.model_for_thoughts, ), ], @@ -188,7 +188,7 @@ async def answer_stream( + [ ThoughtStep( title="Prompt to generate answer", - description=run_results.input, + description=[{"content": self.answer_agent.instructions}] + run_results.input, props=self.model_for_thoughts, ), ], diff --git a/src/backend/fastapi_app/rag_simple.py b/src/backend/fastapi_app/rag_simple.py index a351cd9e..9f538a9c 100644 --- a/src/backend/fastapi_app/rag_simple.py +++ b/src/backend/fastapi_app/rag_simple.py @@ -98,7 +98,7 @@ async def answer( + [ ThoughtStep( title="Prompt to generate answer", - description=run_results.input, + description=[{"content": self.answer_agent.instructions}] + run_results.input, props=self.model_for_thoughts, ), ], @@ -123,7 +123,7 @@ async def answer_stream( + [ ThoughtStep( title="Prompt to generate answer", - description=run_results.input, + description=[{"content": self.answer_agent.instructions}] + run_results.input, props=self.model_for_thoughts, ), ], diff --git a/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json b/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json index 6117ae62..612be773 100644 --- a/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json +++ b/tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json @@ -18,6 +18,9 @@ { "title": "Prompt to generate search arguments", "description": [ + { + "content": "Your job is to find search results based off the user's question and past messages.\nYou have access to only these tools:\n1. **search_database**: This tool allows you to search a table for items based on a query.\n You can pass in a search query and optional filters.\nOnce you get the search results, you're done.\n" + }, { "role": "user", "content": "good options for climbing gear that can be used outside?" @@ -89,6 +92,9 @@ { "title": "Prompt to generate answer", "description": [ + { + "content": "Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51]." + }, { "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", "role": "user" diff --git a/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines b/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines index 4f9f5646..d29b85c4 100644 --- a/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines +++ b/tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines @@ -1,2 +1,2 @@ -{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Prompt to generate search arguments","description":[{"role":"user","content":"good options for climbing gear that can be used outside?"},{"id":"madeup","call_id":"call_abc123","name":"search_database","arguments":"{\"search_query\":\"climbing gear outside\"}","type":"function_call"},{"id":"madeupoutput","call_id":"call_abc123","output":"Search results for climbing gear that can be used outside: ...","type":"function_call_output"},{"role":"user","content":"are there any shoes less than $50?"},{"id":"madeup","call_id":"call_abc456","name":"search_database","arguments":"{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}","type":"function_call"},{"id":"madeupoutput","call_id":"call_abc456","output":"Search results for shoes cheaper than 50: ...","type":"function_call_output"},{"role":"user","content":"Find search results for user query: What is the capital of France?"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}},{"title":"Search using generated search arguments","description":"climbing gear outside","props":{"top":1,"vector_search":true,"text_search":true,"filters":[]}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","role":"user"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} +{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Prompt to generate search arguments","description":[{"content":"Your job is to find search results based off the user's question and past messages.\nYou have access to only these tools:\n1. **search_database**: This tool allows you to search a table for items based on a query.\n You can pass in a search query and optional filters.\nOnce you get the search results, you're done.\n"},{"role":"user","content":"good options for climbing gear that can be used outside?"},{"id":"madeup","call_id":"call_abc123","name":"search_database","arguments":"{\"search_query\":\"climbing gear outside\"}","type":"function_call"},{"id":"madeupoutput","call_id":"call_abc123","output":"Search results for climbing gear that can be used outside: ...","type":"function_call_output"},{"role":"user","content":"are there any shoes less than $50?"},{"id":"madeup","call_id":"call_abc456","name":"search_database","arguments":"{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}","type":"function_call"},{"id":"madeupoutput","call_id":"call_abc456","output":"Search results for shoes cheaper than 50: ...","type":"function_call_output"},{"role":"user","content":"Find search results for user query: What is the capital of France?"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}},{"title":"Search using generated search arguments","description":"climbing gear outside","props":{"top":1,"vector_search":true,"text_search":true,"filters":[]}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"content":"Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51]."},{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","role":"user"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} {"delta":{"content":"The capital of France is Paris. [Benefit_Options-2.pdf].","role":"assistant"},"context":null,"sessionState":null} diff --git a/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json b/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json index 91840e7e..e311917b 100644 --- a/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json +++ b/tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json @@ -41,6 +41,9 @@ { "title": "Prompt to generate answer", "description": [ + { + "content": "Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51]." + }, { "content": "What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear", "role": "user" diff --git a/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json b/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json index 5ac5c17a..d0456cd7 100644 --- a/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json +++ b/tests/snapshots/test_api_routes/test_simple_chat_flow_message_history/simple_chat_flow_message_history_response.json @@ -41,6 +41,9 @@ { "title": "Prompt to generate answer", "description": [ + { + "content": "Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51]." + }, { "content": "What is the capital of France?", "role": "user" diff --git a/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines b/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines index 8fd708f4..65d3ae5b 100644 --- a/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines +++ b/tests/snapshots/test_api_routes/test_simple_chat_streaming_flow/simple_chat_streaming_flow_response.jsonlines @@ -1,2 +1,2 @@ -{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Search query for database","description":"What is the capital of France?","props":{"top":1,"vector_search":true,"text_search":true}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","role":"user"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} +{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Search query for database","description":"What is the capital of France?","props":{"top":1,"vector_search":true,"text_search":true}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":[{"content":"Assistant helps customers with questions about products.\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\nAnswer ONLY with the product details listed in the products.\nIf there isn't enough information below, say you don't know.\nDo not generate answers that don't use the sources below.\nEach product has an ID in brackets followed by colon and the product details.\nAlways include the product ID for each product you use in the response.\nUse square brackets to reference the source, for example [52].\nDon't combine citations, list each product separately, for example [27][51]."},{"content":"What is the capital of France?Sources:\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear","role":"user"}],"props":{"model":"gpt-4o-mini","deployment":"gpt-4o-mini"}}],"followup_questions":null},"sessionState":null} {"delta":{"content":"The capital of France is Paris. [Benefit_Options-2.pdf].","role":"assistant"},"context":null,"sessionState":null} From 0d0572c7d89aa15995744e825ec1e1d1b34e19c9 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 07:13:15 +0000 Subject: [PATCH 8/8] Make mypy happy --- src/backend/fastapi_app/rag_advanced.py | 12 ++++++++---- src/backend/fastapi_app/rag_simple.py | 8 +++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/backend/fastapi_app/rag_advanced.py b/src/backend/fastapi_app/rag_advanced.py index ed67f5ca..eb53aa6a 100644 --- a/src/backend/fastapi_app/rag_advanced.py +++ b/src/backend/fastapi_app/rag_advanced.py @@ -4,6 +4,7 @@ from agents import ( Agent, + ItemHelpers, ModelSettings, OpenAIChatCompletionsModel, Runner, @@ -124,7 +125,8 @@ async def prepare_context(self) -> tuple[list[ItemPublic], list[ThoughtStep]]: thoughts = [ ThoughtStep( title="Prompt to generate search arguments", - description=[{"content": self.search_agent.instructions}] + run_results.input, + description=[{"content": self.query_prompt_template}] + + ItemHelpers.input_to_new_input_list(run_results.input), props=self.model_for_thoughts, ), ThoughtStep( @@ -163,7 +165,8 @@ async def answer( + [ ThoughtStep( title="Prompt to generate answer", - description=[{"content": self.answer_agent.instructions}] + run_results.input, + description=[{"content": self.answer_prompt_template}] + + ItemHelpers.input_to_new_input_list(run_results.input), props=self.model_for_thoughts, ), ], @@ -178,7 +181,7 @@ async def answer_stream( run_results = Runner.run_streamed( self.answer_agent, input=self.chat_params.past_messages - + [{"content": self.prepare_rag_request(self.chat_params.original_user_query, items), "role": "user"}], + + [{"content": self.prepare_rag_request(self.chat_params.original_user_query, items), "role": "user"}], # noqa ) yield RetrievalResponseDelta( @@ -188,7 +191,8 @@ async def answer_stream( + [ ThoughtStep( title="Prompt to generate answer", - description=[{"content": self.answer_agent.instructions}] + run_results.input, + description=[{"content": self.answer_prompt_template}] + + ItemHelpers.input_to_new_input_list(run_results.input), props=self.model_for_thoughts, ), ], diff --git a/src/backend/fastapi_app/rag_simple.py b/src/backend/fastapi_app/rag_simple.py index 9f538a9c..69126618 100644 --- a/src/backend/fastapi_app/rag_simple.py +++ b/src/backend/fastapi_app/rag_simple.py @@ -1,7 +1,7 @@ from collections.abc import AsyncGenerator from typing import Optional, Union -from agents import Agent, ModelSettings, OpenAIChatCompletionsModel, Runner, set_tracing_disabled +from agents import Agent, ItemHelpers, ModelSettings, OpenAIChatCompletionsModel, Runner, set_tracing_disabled from openai import AsyncAzureOpenAI, AsyncOpenAI from openai.types.responses import ResponseInputItemParam, ResponseTextDeltaEvent @@ -98,7 +98,8 @@ async def answer( + [ ThoughtStep( title="Prompt to generate answer", - description=[{"content": self.answer_agent.instructions}] + run_results.input, + description=[{"content": self.answer_prompt_template}] + + ItemHelpers.input_to_new_input_list(run_results.input), props=self.model_for_thoughts, ), ], @@ -123,7 +124,8 @@ async def answer_stream( + [ ThoughtStep( title="Prompt to generate answer", - description=[{"content": self.answer_agent.instructions}] + run_results.input, + description=[{"content": self.answer_agent.instructions}] + + ItemHelpers.input_to_new_input_list(run_results.input), props=self.model_for_thoughts, ), ],