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 b760fdb..4f2fd48 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 46574c4..a5a0338 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 d5ab7f2..3efce3b 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 3541d8c..1061ae3 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 62bdc80..646b020 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 7f5ac75..ff537a2 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 2d41bb9..9971eb3 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 a5a0338..06d14a6 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 3efce3b..0ef450f 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 1061ae3..158afc4 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 646b020..54e633c 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 9971eb3..a351cd9 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 240a638..6117ae6 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 e241106..4f9f564 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 337a67d..91840e7 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 6bc9d4e..dcdbd30 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 28bfd00..8fd708f 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 ff537a2..7ede97c 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 bc349b0..77d3974 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 0c59b5a..b432baa 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 aa24848..d84731a 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 632cfe9..e73ac0c 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 f3800dd..5fe6705 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 dcdbd30..5ac5c17 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 4f2fd48..b760fdb 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 158afc4..ed67f5c 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 a351cd9..9f538a9 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 6117ae6..612be77 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 4f9f564..d29b85c 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 91840e7..e311917 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 5ac5c17..d0456cd 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 8fd708f..65d3ae5 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 ed67f5c..eb53aa6 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 9f538a9..6912661 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, ), ],