Bring back AI titles! (#109)

* Rewrite ticket title gen to use the new HC AI

* Use implicit string concatenation

* Use async OpenAI

* Guard against single-letter or all-whitespace AI responses

* Add a singular space
This commit is contained in:
Mish 2025-11-16 12:45:47 +00:00 committed by GitHub
parent c2b73dcb96
commit 4c3775d7c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 16 deletions

View file

@ -3,6 +3,7 @@ from datetime import datetime
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from openai import OpenAIError
from slack_sdk.errors import SlackApiError from slack_sdk.errors import SlackApiError
from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.async_client import AsyncWebClient
@ -330,27 +331,35 @@ async def on_message(event: Dict[str, Any], client: AsyncWebClient):
async def generate_ticket_title(text: str): async def generate_ticket_title(text: str):
async with env.session.post( if not env.ai_client:
"https://ai.hackclub.com/chat/completions", return "No title available from AI."
json={
"messages": [ model = "qwen/qwen3-32b"
try:
response = await env.ai_client.chat.completions.create(
model=model,
messages=[
{ {
"role": "system", "role": "system",
"content": "You are a helpful assistant that helps organise tickets for Hack Club's support team. You're going to take in a message and give it a title. You will return no other content. Even if it's silly please summarise it. Use no more than 7 words, but as few as possible.", "content": (
"You are a helpful assistant that helps organise tickets for Hack Club's support team. You're going to take in a message and give it a title. "
"You will return no other content. Do *NOT* use title case. Avoid quote marks. Even if it's silly please summarise it. Use no more than 7 words, but as few as possible."
),
}, },
{ {
"role": "user", "role": "user",
"content": f"Here is a message from a user: {text}\n\nPlease give this ticket a title.", "content": f"Here is a message from a user: {text}\n\nPlease give this ticket a title.",
}, },
] ],
}, )
) as res: except OpenAIError as e:
if res.status != 200: await send_heartbeat(f"Failed to get AI response for ticket creation: {e}")
await send_heartbeat( return "No title provided by AI."
f"Failed to get AI response for ticket creation: {res.status} - {await res.text()}"
) if not (len(response.choices) and response.choices[0].message.content):
title = "No title provided by AI." await send_heartbeat(f"AI title generation is missing content: {response}")
else: return "No title provided by AI."
data = await res.json() title = response.choices[0].message.content.strip()
title = data["choices"][0]["message"]["content"].strip() # Capitalise first letter
title = title[0].upper() + title[1:] if len(title) > 1 else title.upper()
return title return title

View file

@ -4,6 +4,7 @@ from typing import Literal
from aiohttp import ClientSession from aiohttp import ClientSession
from dotenv import load_dotenv from dotenv import load_dotenv
from openai import AsyncOpenAI
from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.async_client import AsyncWebClient
from nephthys.transcripts import transcripts from nephthys.transcripts import transcripts
@ -23,6 +24,7 @@ class Environment:
self.uptime_url = os.environ.get("UPTIME_URL") self.uptime_url = os.environ.get("UPTIME_URL")
self.site_url = os.environ.get("SITE_URL", "https://summer.hackclub.com") self.site_url = os.environ.get("SITE_URL", "https://summer.hackclub.com")
self.site_api_key = os.environ.get("SITE_API_KEY", "unset") self.site_api_key = os.environ.get("SITE_API_KEY", "unset")
self.hack_club_ai_api_key = os.environ.get("HACK_CLUB_AI_API_KEY")
self.environment = os.environ.get("ENVIRONMENT", "development") self.environment = os.environ.get("ENVIRONMENT", "development")
self.log_level = os.environ.get( self.log_level = os.environ.get(
@ -70,6 +72,14 @@ class Environment:
) )
self.slack_client = AsyncWebClient(token=self.slack_bot_token) self.slack_client = AsyncWebClient(token=self.slack_bot_token)
self.ai_client = (
AsyncOpenAI(
base_url="https://ai.hackclub.com/proxy/v1",
api_key=self.hack_club_ai_api_key,
)
if self.hack_club_ai_api_key
else None
)
# Cache whether the user token has workspace admin privileges # Cache whether the user token has workspace admin privileges
self._workspace_admin_available: bool | Literal["unchecked"] = "unchecked" self._workspace_admin_available: bool | Literal["unchecked"] = "unchecked"