fixup! fixup! 🎉(conversations) bootstrap backend & frontend
This commit is contained in:
+8
-10
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
]
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user