fixup! fixup! 🎉(conversations) bootstrap backend & frontend

This commit is contained in:
Quentin BEY
2025-07-17 15:08:36 +02:00
parent 8970543085
commit 7b99a18c20
6 changed files with 109 additions and 26 deletions
+8 -10
View File
@@ -134,7 +134,7 @@ services:
- ".:/app"
kc_postgresql:
image: postgres:14.3
image: postgres:17.5
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 1s
@@ -146,7 +146,7 @@ services:
- env.d/development/kc_postgresql
keycloak:
image: quay.io/keycloak/keycloak:20.0.1
image: quay.io/keycloak/keycloak:26.3
volumes:
- ./docker/auth/realm.json:/opt/keycloak/data/import/realm.json
command:
@@ -154,20 +154,20 @@ services:
- --features=preview
- --import-realm
- --proxy=edge
- --hostname-url=http://localhost:8083
- --hostname-admin-url=http://localhost:8083/
- --hostname=http://localhost:8083
- --hostname-strict=false
- --hostname-strict-https=false
- --health-enabled=true
- --metrics-enabled=true
- --http-enabled=true
healthcheck:
test: ["CMD", "curl", "--head", "-fsS", "http://localhost:8080/health/ready"]
test: ['CMD-SHELL', '[ -f /tmp/HealthCheck.java ] || echo "public class HealthCheck { public static void main(String[] args) throws java.lang.Throwable { java.net.URI uri = java.net.URI.create(args[0]); System.exit(java.net.HttpURLConnection.HTTP_OK == ((java.net.HttpURLConnection)uri.toURL().openConnection()).getResponseCode() ? 0 : 1); } }" > /tmp/HealthCheck.java && java /tmp/HealthCheck.java http://localhost:9000/health/live']
start_period: 10s
interval: 1s
timeout: 2s
retries: 300
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
KC_DB: postgres
KC_DB_URL_HOST: kc_postgresql
KC_DB_URL_DATABASE: keycloak
@@ -175,8 +175,6 @@ services:
KC_DB_USERNAME: conversations
KC_DB_SCHEMA: public
PROXY_ADDRESS_FORWARDING: 'true'
ports:
- "8080:8080"
depends_on:
kc_postgresql:
condition: service_healthy
+33 -14
View File
@@ -30,7 +30,6 @@ from chat.ai_sdk_types import (
UIMessage,
)
from chat.mcp_servers import get_mcp_servers
from chat.tools import agent_get_current_weather
logger = logging.getLogger(__name__)
@@ -171,28 +170,30 @@ class AIAgentService:
async def stream_text_async(self, messages: List[UIMessage]):
"""Async generator for streaming agent events."""
openai_messages = self._convert_to_openai_messages(messages)
logger.info("[stream_data_async] Received messages: %s", openai_messages)
logger.debug("[stream_data_async] Received messages: %s", openai_messages)
mcp_servers = get_mcp_servers()
async with mcp_servers[0] as mcp_server:
async with AsyncExitStack() as stack:
initialized_mcp_servers = [
await stack.enter_async_context(mcp_server) for mcp_server in get_mcp_servers()
]
agent = Agent(
name=settings.AI_AGENT_NAME,
instructions=settings.settings.AI_AGENT_INSTRUCTIONS,
model=self.model,
tools=[agent_get_current_weather],
mcp_servers=[mcp_server],
#tools=[agent_get_current_weather],
mcp_servers=initialized_mcp_servers,
)
result = Runner.run_streamed(
agent,
input=openai_messages,
)
async for event in result.stream_events():
logger.info("[stream_text_async] Received event: %s", event)
logger.debug("[stream_text_async] Received event: %s", event)
if event.type == "raw_response_event":
data = event.data
logger.info("[stream_text_async] - data: %s", data)
logger.debug("[stream_text_async] - data: %s", data)
if data.type == "response.output_text.delta":
yield data.delta
@@ -215,19 +216,38 @@ class AIAgentService:
finish_reason = "stop"
openai_messages = self._convert_to_openai_messages(messages)
logger.info("[stream_data_async] Received messages: %s", openai_messages)
logger.debug("[stream_data_async] Received messages: %s", openai_messages)
async with AsyncExitStack() as stack:
initialized_mcp_servers = [
await stack.enter_async_context(mcp_server) for mcp_server in get_mcp_servers()
]
# websearch_agent = Agent(
# name="web search",
# instructions=(
# "You are a web search agent. "
# "Your task is to search the web for up-to-date information."
# " You will be called by the main agent to perform web searches."
# " You will receive a query and return the search results."
# " The results should be in the format of a list of dictionaries, "
# "each containing 'link', 'title', and 'snippet' keys."
# " If you cannot find any results, return an empty list."
# " Do not include any other information in your response."
# " Do not include any additional text or explanations."
# " You must always annotate the response mentioning the url, title, and snippet."
# ),
# handoff_description="You are a web search agent. Your task is to search the web for up-to-date information.",
# model=self.model,
# tools=[agent_web_search_tavily],
# )
agent = Agent(
name=settings.AI_AGENT_NAME,
instructions=settings.AI_AGENT_INSTRUCTIONS,
model=self.model,
tools=[agent_get_current_weather],
mcp_servers=initialized_mcp_servers,
# handoffs=[websearch_agent],
)
result = Runner.run_streamed(
agent,
@@ -236,11 +256,10 @@ class AIAgentService:
try:
async for event in result.stream_events():
# logger.info("[stream_data_async] Received event: %s", event)
logger.debug("[stream_data_async] Received event: %s", event)
if event.type == "raw_response_event":
data = event.data
# logger.info("[stream_data_async] - data: %s", data)
if data.type == "response.output_text.delta":
yield f"0:{json.dumps(data.delta)}\n"
@@ -267,7 +286,7 @@ class AIAgentService:
}
yield f"a:{json.dumps(_tool_call_result)}\n"
elif event.type == "agent_updated_stream_event":
logger.info(
logger.debug(
"[stream_data_async] Agent switched to: %s", event.new_agent.name
)
+15
View File
@@ -0,0 +1,15 @@
"""Tools for the chat agent."""
from agents import FunctionTool
from .fake_current_weather import agent_get_current_weather
from .web_search_tavily import agent_web_search_tavily
def get_tool_by_name(name: str) -> FunctionTool:
"""Get a tool by its name."""
tool_dict = {
"get_current_weather": agent_get_current_weather,
"tavily_web_search": agent_web_search_tavily,
}
return tool_dict[name] # will raise on purpose if name is not found
@@ -1,9 +1,9 @@
"""Tools for the chat agent."""
"""Fake weather tool for the chat agent."""
from agents import function_tool
from openai.types.chat import ChatCompletionToolParam
from openai.types.shared_params import FunctionDefinition
current_weather = ChatCompletionToolParam(
type="function",
function=FunctionDefinition(
@@ -37,3 +37,4 @@ def get_current_weather(location: str, unit: str):
def agent_get_current_weather(location: str, unit: str) -> dict:
"""Get the current weather in a given location."""
return get_current_weather(location, unit)
@@ -0,0 +1,38 @@
"""Web search tool using Tavily for the chat agent."""
import requests
from agents import function_tool
from django.conf import settings
@function_tool(name_override="tavily_web_search")
def agent_web_search_tavily(query: str) -> list[dict]:
"""
Search the web for up-to-date information
Args:
query (str): The query to search for.
Returns:
list[dict]: A list of search results, each represented as a dictionary.
"""
url = "https://api.tavily.com/search"
data = {
"query": query,
"api_key": settings.TAVILY_API_KEY,
"max_results": settings.TAVILY_MAX_RESULTS,
}
response = requests.post(url, json=data)
response.raise_for_status()
json_response = response.json()
raw_search_results = json_response.get("results", [])
return [
dict(
link=result["url"],
title=result.get("title", ""),
snippet=result.get("content"),
)
for result in raw_search_results
]
+12
View File
@@ -477,6 +477,18 @@ class Base(Configuration):
environ_prefix=None,
)
# Web search
TAVILY_API_KEY = values.Value(
None, # Tavily API key is not set by default
environ_name="TAVILY_API_KEY",
environ_prefix=None,
)
TAVILY_MAX_RESULTS = values.PositiveIntegerValue(
default=5,
environ_name="TAVILY_MAX_RESULTS",
environ_prefix=None,
)
# Logging
# We want to make it easy to log to console but by default we log production
# to Sentry and don't want to log to console.