From b000a71a7ed5d0f0582e69fbbf4ee47ac89d652e Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Fri, 9 May 2025 09:02:40 -0700 Subject: [PATCH 1/5] Additional extensions for Dev Container (Rainbow CSV, AI Foundry, AI Toolkit) (#210) * Add rainbow CSV extension (#208) * Add extensions to dev container --- .devcontainer/devcontainer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3a19de41..c9d9131b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -29,11 +29,15 @@ "extensions": [ "ms-python.python", "ms-python.vscode-pylance", + "ms-python.vscode-python-envs", "charliermarsh.ruff", "mtxr.sqltools", "mtxr.sqltools-driver-pg", + "esbenp.prettier-vscode", + "mechatroner.rainbow-csv", "ms-vscode.vscode-node-azure-pack", - "esbenp.prettier-vscode" + "teamsdevapp.vscode-ai-foundry", + "ms-windows-ai-studio.windows-ai-studio" ], // Set *default* container specific settings.json values on container create. "settings": { From 5a3deacbf2cb4ec572fe6294af06e5251f8b4de2 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 00:23:40 -0700 Subject: [PATCH 2/5] Port from pydantic-ai to openai-agents SDK (#211) * Port to OpenAI-agents SDK * Port to OpenAI-agents SDK * Fix tests, mypy * Update package requirements * More dep/mypy updates * Update snapshot * Add system message to thoughts * Make mypy happy --- .github/workflows/app-tests.yaml | 2 +- pyproject.toml | 1 - requirements-dev.txt | 1 - src/backend/fastapi_app/api_models.py | 9 +- .../fastapi_app/prompts/query_fewshots.json | 84 ++------ src/backend/fastapi_app/rag_advanced.py | 146 ++++++++------ src/backend/fastapi_app/rag_base.py | 25 +-- src/backend/fastapi_app/rag_simple.py | 80 ++++---- src/backend/pyproject.toml | 6 +- src/backend/requirements.txt | 49 ++--- tests/conftest.py | 8 - .../advanced_chat_flow_response.json | 188 +++--------------- ...ced_chat_streaming_flow_response.jsonlines | 2 +- .../simple_chat_flow_response.json | 27 +-- ...le_chat_flow_message_history_response.json | 43 +--- ...ple_chat_streaming_flow_response.jsonlines | 2 +- 16 files changed, 212 insertions(+), 461 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/src/backend/fastapi_app/api_models.py b/src/backend/fastapi_app/api_models.py index 46574c4e..06d14a6b 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 openai.types.responses import ResponseInputItemParam from pydantic import BaseModel, Field -from pydantic_ai.messages import ModelRequest, ModelResponse class AIChatRoles(str, Enum): @@ -37,7 +36,7 @@ class ChatRequestContext(BaseModel): class ChatRequest(BaseModel): - messages: list[ChatCompletionMessageParam] + messages: list[ResponseInputItemParam] context: ChatRequestContext sessionState: Optional[Any] = None @@ -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[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 d5ab7f2b..0ef450fd 100644 --- a/src/backend/fastapi_app/prompts/query_fewshots.json +++ b/src/backend/fastapi_app/prompts/query_fewshots.json @@ -1,76 +1,36 @@ [ { - "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" } ] diff --git a/src/backend/fastapi_app/rag_advanced.py b/src/backend/fastapi_app/rag_advanced.py index 3541d8c7..eb53aa6a 100644 --- a/src/backend/fastapi_app/rag_advanced.py +++ b/src/backend/fastapi_app/rag_advanced.py @@ -1,13 +1,19 @@ +import json from collections.abc import AsyncGenerator from typing import Optional, Union +from agents import ( + Agent, + ItemHelpers, + ModelSettings, + OpenAIChatCompletionsModel, + Runner, + ToolCallOutputItem, + 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.responses import EasyInputMessageParam, ResponseInputItemParam, ResponseTextDeltaEvent from fastapi_app.api_models import ( AIChatRoles, @@ -24,7 +30,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): @@ -34,7 +42,7 @@ class AdvancedRAGChat(RAGChatBase): def __init__( self, *, - messages: list[ChatCompletionMessageParam], + messages: list[ResponseInputItemParam], overrides: ChatRequestOverrides, searcher: PostgresSearcher, openai_chat_client: Union[AsyncOpenAI, AsyncAzureOpenAI], @@ -46,34 +54,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 +100,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 +110,63 @@ async def search_database( ) async def prepare_context(self) -> tuple[list[ItemPublic], list[ThoughtStep]]: - few_shots = ModelMessagesTypeAdapter.validate_json(self.query_fewshots) + few_shots: list[ResponseInputItemParam] = json.loads(self.query_fewshots) 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_shots + self.chat_params.past_messages + [new_user_message] + + run_results = await Runner.run(self.search_agent, input=all_messages) + 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( title="Prompt to generate search arguments", - description=results.all_messages(), + description=[{"content": self.query_prompt_template}] + + ItemHelpers.input_to_new_input_list(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=[{"content": self.answer_prompt_template}] + + ItemHelpers.input_to_new_input_list(run_results.input), props=self.model_for_thoughts, ), ], @@ -168,24 +178,28 @@ 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"}], # noqa + ) + + yield RetrievalResponseDelta( + context=RAGContext( + data_points={item.id: item for item in items}, + thoughts=earlier_thoughts + + [ + ThoughtStep( + title="Prompt to generate answer", + description=[{"content": self.answer_prompt_template}] + + ItemHelpers.input_to_new_input_list(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..54e633c2 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 openai.types.responses import ResponseInputItemParam from fastapi_app.api_models import ( ChatParams, @@ -20,32 +18,17 @@ 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.") - # 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 +40,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/fastapi_app/rag_simple.py b/src/backend/fastapi_app/rag_simple.py index 2d41bb9d..69126618 100644 --- a/src/backend/fastapi_app/rag_simple.py +++ b/src/backend/fastapi_app/rag_simple.py @@ -1,12 +1,9 @@ from collections.abc import AsyncGenerator from typing import Optional, Union +from agents import Agent, ItemHelpers, 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 ResponseInputItemParam, ResponseTextDeltaEvent from fastapi_app.api_models import ( AIChatRoles, @@ -21,12 +18,14 @@ from fastapi_app.postgres_searcher import PostgresSearcher from fastapi_app.rag_base import RAGChatBase +set_tracing_disabled(disabled=True) + class SimpleRAGChat(RAGChatBase): def __init__( self, *, - messages: list[ChatCompletionMessageParam], + messages: list[ResponseInputItemParam], overrides: ChatRequestOverrides, searcher: PostgresSearcher, openai_chat_client: Union[AsyncOpenAI, AsyncAzureOpenAI], @@ -38,17 +37,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 +84,22 @@ 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=[{"content": self.answer_prompt_template}] + + ItemHelpers.input_to_new_input_list(run_results.input), props=self.model_for_thoughts, ), ], @@ -109,24 +111,28 @@ 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=[{"content": self.answer_agent.instructions}] + + ItemHelpers.input_to_new_input_list(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 diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 7f5ac750..7ede97c9 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -6,20 +6,18 @@ 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", "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" ] [build-system] 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 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.""" 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..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 @@ -19,154 +19,45 @@ "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" + "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" }, { - "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" + "role": "user", + "content": "good options for climbing gear that can be used outside?" }, { - "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": "madeup", + "call_id": "call_abc123", + "name": "search_database", + "arguments": "{\"search_query\":\"climbing gear outside\"}", + "type": "function_call" }, { - "parts": [ - { - "content": "are there any shoes less than $50?", - "timestamp": "2025-05-07T19:02:46.977501Z", - "part_kind": "user-prompt" - } - ], - "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": [ - { - "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" + "role": "user", + "content": "are there any shoes less than $50?" }, { - "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": "madeup", + "call_id": "call_abc456", + "name": "search_database", + "arguments": "{\"search_query\":\"shoes\",\"price_filter\":{\"comparison_operator\":\"<\",\"value\":50}}", + "type": "function_call" }, { - "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" + "id": "madeupoutput", + "call_id": "call_abc456", + "output": "Search results for shoes cheaper than 50: ...", + "type": "function_call_output" }, { - "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 +67,7 @@ }, { "title": "Search using generated search arguments", - "description": "capital of France", + "description": "climbing gear outside", "props": { "top": 1, "vector_search": true, @@ -202,32 +93,11 @@ "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" + "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]." }, { - "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..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":[{"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":[{"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 337a67d3..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 @@ -42,32 +42,11 @@ "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" + "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]." }, { - "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..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 @@ -42,48 +42,19 @@ "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": "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]." }, { - "parts": [ - { - "content": "The capital of France is Paris.", - "part_kind": "text" - } - ], - "model_name": null, - "timestamp": "2024-01-01T12:00:00Z", - "kind": "response" + "content": "What is the capital of France?", + "role": "user" }, { - "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" + "content": "The capital of France is Paris.", + "role": "assistant" }, { - "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..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":[{"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":"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 2735ce2b1ac6a9de3bb60697a42e8fcb8956fb89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 00:27:16 -0700 Subject: [PATCH 3/5] Bump @babel/runtime from 7.22.15 to 7.27.1 in /src/frontend (#205) Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.22.15 to 7.27.1. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-runtime) --- updated-dependencies: - dependency-name: "@babel/runtime" dependency-version: 7.27.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/frontend/package-lock.json | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 92f7e615..252ddabd 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -308,11 +308,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.15", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } @@ -4072,10 +4071,6 @@ "node": ">=6" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.4", "license": "MIT", @@ -4606,10 +4601,9 @@ } }, "@babel/runtime": { - "version": "7.22.15", - "requires": { - "regenerator-runtime": "^0.14.0" - } + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==" }, "@babel/template": { "version": "7.26.9", @@ -6924,9 +6918,6 @@ } } }, - "regenerator-runtime": { - "version": "0.14.0" - }, "resolve": { "version": "1.22.4", "requires": { From 425fb2a3306cfddd0442aa3b1ef3b8d0ab275184 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 11 May 2025 00:40:47 -0700 Subject: [PATCH 4/5] Update h11 and trans deps (#212) --- src/backend/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 77d3974d..f7c76310 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -79,13 +79,13 @@ greenlet==3.1.1 # via sqlalchemy griffe==1.7.3 # via openai-agents -h11==0.14.0 +h11==0.16.0 # via # httpcore # uvicorn -httpcore==1.0.7 +httpcore==1.0.9 # via httpx -httpx==0.28.0 +httpx==0.28.1 # via openai idna==3.10 # via @@ -117,7 +117,7 @@ numpy==2.0.2 # via pgvector oauthlib==3.2.2 # via requests-oauthlib -openai==1.77.0 +openai==1.78.0 # via # fastapi-app (pyproject.toml) # openai-agents From ac4b0a65cfb0e6963d4e78e75457c75ad2475860 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Mon, 12 May 2025 09:16:20 -0700 Subject: [PATCH 5/5] Collapse references by default (#213) --- src/frontend/src/components/Answer/Answer.tsx | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/components/Answer/Answer.tsx b/src/frontend/src/components/Answer/Answer.tsx index a542064c..01b8bd3f 100644 --- a/src/frontend/src/components/Answer/Answer.tsx +++ b/src/frontend/src/components/Answer/Answer.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { Stack, IconButton } from "@fluentui/react"; import DOMPurify from "dompurify"; @@ -29,6 +29,7 @@ export const Answer = ({ onFollowupQuestionClicked, showFollowupQuestions }: Props) => { + const [isReferencesCollapsed, setIsReferencesCollapsed] = useState(true); const followupQuestions = answer.context.followup_questions; const messageContent = answer.message.content; const parsedAnswer = useMemo(() => parseAnswerToHtml(messageContent, isStreaming, onCitationClicked), [answer]); @@ -60,22 +61,32 @@ export const Answer = ({ {!!parsedAnswer.citations.length && ( - References: + + setIsReferencesCollapsed(!isReferencesCollapsed)} + /> + References: + + + {!isReferencesCollapsed && (
    - {parsedAnswer.citations.map((rowId, ind) => { - const citation = answer.context.data_points[rowId]; - if (!citation) return null; - return ( -
  1. -

    {citation.name}

    -

    Brand: {citation.brand}

    -

    Price: {citation.price}

    -

    {citation.description}

    -
  2. - ); - })} + {parsedAnswer.citations.map((rowId, ind) => { + const citation = answer.context.data_points[rowId]; + if (!citation) return null; + return ( +
  3. +

    {citation.name}

    +

    Brand: {citation.brand}

    +

    Price: {citation.price}

    +

    {citation.description}

    +
  4. + ); + })}
- + )}
)}