Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine 0ea7128e2f 🔖(minor) bump release to 0.1.40
- enhance technical documentation
- introduce external-api and service account
- fix inverted keyboard shortcuts
- allow configuring whisperX language (still wip)
- filter livekit event when sharing a single livekit instance
2025-10-12 17:11:00 +02:00
7 changed files with 31 additions and 150 deletions
+3 -3
View File
@@ -7,7 +7,7 @@ info:
#### Authentication Flow
1. Exchange application credentials for a JWT token via `/external-api/v1.0/application/token`.
1. Exchange application credentials for a JWT token via `/external-api/v1.0/applications/token`.
2. Use the JWT token in the `Authorization: Bearer <token>` header for all subsequent requests.
3. Tokens are scoped and allow applications to act on behalf of specific users.
@@ -40,7 +40,7 @@ tags:
description: Room management operations
paths:
/application/token:
/applications/token:
post:
tags:
- Authentication
@@ -283,7 +283,7 @@ components:
scheme: bearer
bearerFormat: JWT
description: |
JWT token obtained from the `/application/token` endpoint.
JWT token obtained from the `/applications/token` endpoint.
Include in requests as: `Authorization: Bearer <token>`
schemas:
+1 -1
View File
@@ -15,7 +15,7 @@ WHISPERX_DEFAULT_LANGUAGE="fr"
LLM_BASE_URL="https://configure-your-url.com"
LLM_API_KEY="dev-apikey"
LLM_MODEL="albert-large"
LLM_MODEL="Qwen/Qwen2.5-Coder-32B-Instruct-AWQ"
WEBHOOK_API_TOKEN="secret"
WEBHOOK_URL="https://configure-your-url.com"
+1 -1
View File
@@ -30,7 +30,7 @@ class LiveKitTokenAuthentication(authentication.BaseAuthentication):
raise exceptions.AuthenticationFailed("Token missing user identity")
try:
user = UserModel.objects.get(sub=user_id)
user = UserModel.objects.get(id=user_id)
except UserModel.DoesNotExist:
user = AnonymousUser()
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.14
version: 0.0.13
+2 -41
View File
@@ -428,13 +428,6 @@ posthog:
## @section summary
summary:
## @param summary.image.repository Repository to use to pull meet's summary container image
## @param summary.image.tag meet's summary container tag
## @param summary.image.pullPolicy summary container image pull policy
image:
repository: lasuite/meet-summary
pullPolicy: IfNotPresent
tag: "latest"
## @param summary.dpAnnotations Annotations to add to the summary Deployment
dpAnnotations: {}
@@ -534,27 +527,11 @@ summary:
## @section celeryTranscribe
celeryTranscribe:
## @param celeryTranscribe.image.repository Repository to use to pull meet's celeryTranscribe container image
## @param celeryTranscribe.image.tag meet's celeryTranscribe container tag
## @param celeryTranscribe.image.pullPolicy celeryTranscribe container image pull policy
image:
repository: lasuite/meet-summary
pullPolicy: IfNotPresent
tag: "latest"
## @param celeryTranscribe.dpAnnotations Annotations to add to the celeryTranscribe Deployment
dpAnnotations: {}
## @param celeryTranscribe.command Override the celeryTranscribe container command
command:
- "celery"
- "-A"
- "summary.core.celery_worker"
- "worker"
- "--pool=solo"
- "--loglevel=info"
- "-Q"
- "transcribe-queue"
command: []
## @param celeryTranscribe.args Override the celeryTranscribe container args
args: []
@@ -643,27 +620,11 @@ celeryTranscribe:
## @section celerySummarize
celerySummarize:
## @param celerySummarize.image.repository Repository to use to pull meet's celerySummarize container image
## @param celerySummarize.image.tag meet's celerySummarize container tag
## @param celerySummarize.image.pullPolicy celerySummarize container image pull policy
image:
repository: lasuite/meet-summary
pullPolicy: IfNotPresent
tag: "latest"
## @param celerySummarize.dpAnnotations Annotations to add to the celerySummarize Deployment
dpAnnotations: {}
## @param celerySummarize.command Override the celerySummarize container command
command:
- "celery"
- "-A"
- "summary.core.celery_worker"
- "worker"
- "--pool=solo"
- "--loglevel=info"
- "-Q"
- "summarize-queue"
command: []
## @param celerySummarize.args Override the celerySummarize container args
args: []
+14 -51
View File
@@ -7,7 +7,7 @@ import os
import tempfile
import time
from pathlib import Path
from typing import Any, Mapping, Optional
from typing import Optional
import openai
import sentry_sdk
@@ -22,8 +22,6 @@ from urllib3.util import Retry
from summary.core.analytics import MetadataManager, get_analytics
from summary.core.config import get_settings
from summary.core.prompt import (
FORMAT_NEXT_STEPS,
FORMAT_PLAN,
PROMPT_SYSTEM_CLEANING,
PROMPT_SYSTEM_NEXT_STEP,
PROMPT_SYSTEM_PART,
@@ -117,53 +115,24 @@ class LLMService:
base_url=settings.llm_base_url, api_key=settings.llm_api_key
)
def call(
self,
system_prompt: str,
user_prompt: str,
response_format: Optional[Mapping[str, Any]] = None,
):
def call(self, system_prompt: str, user_prompt: str):
"""Call the LLM service.
Takes a system prompt and a user prompt, and returns the LLM's response
Returns None if the call fails.
"""
try:
params: dict[str, Any] = {
"model": settings.llm_model,
"messages": [
response = self._client.chat.completions.create(
model=settings.llm_model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
}
if response_format is not None:
params["response_format"] = response_format
response = self._client.chat.completions.create(**params)
)
return response.choices[0].message.content
except Exception as e:
logger.exception("LLM call failed: %s", e)
raise LLMException("LLM call failed: {e}") from e
def format_actions(llm_output: dict) -> str:
"""Format the actions from the LLM output into a markdown list.
fomat:
- [ ] Action title Assignée à : assignee1, assignee2, Échéance : due_date
"""
lines = []
for action in llm_output.get("actions", []):
title = action.get("title", "").strip()
assignees = ", ".join(action.get("assignees", [])) or "-"
due_date = action.get("due_date") or "-"
line = f"- [ ] {title} Assignée à : {assignees}, Échéance : {due_date}"
lines.append(line)
if lines:
return "### Prochaines étapes\n\n" + "\n".join(lines)
return ""
logger.error("LLM call failed: %s", e)
raise LLMException("LLM call failed.") from e
def format_segments(transcription_data):
@@ -390,14 +359,13 @@ def summarize_transcription(self, transcript: str, email: str, sub: str, title:
logger.info("TLDR generated")
parts = llm_service.call(
PROMPT_SYSTEM_PLAN, transcript, response_format=FORMAT_PLAN
)
parts = llm_service.call(PROMPT_SYSTEM_PLAN, transcript)
logger.info("Plan generated")
res = json.loads(parts)
parts = res.get("titles", [])
logger.info("Parts to summarize: %s", parts)
parts = parts.split("\n")
parts = [x for x in parts if x.strip() != ""]
logger.info("Empty parts removed")
parts_summarized = []
for part in parts:
prompt_user_part = PROMPT_USER_PART.format(part=part, transcript=transcript)
@@ -408,12 +376,7 @@ def summarize_transcription(self, transcript: str, email: str, sub: str, title:
raw_summary = "\n\n".join(parts_summarized)
next_steps = llm_service.call(
PROMPT_SYSTEM_NEXT_STEP, transcript, response_format=FORMAT_NEXT_STEPS
)
next_steps = format_actions(json.loads(next_steps))
next_steps = llm_service.call(PROMPT_SYSTEM_NEXT_STEP, transcript)
logger.info("Next steps generated")
cleaned_summary = llm_service.call(PROMPT_SYSTEM_CLEANING, raw_summary)
+9 -52
View File
@@ -4,8 +4,12 @@ PROMPT_SYSTEM_TLDR = """Tu es un agent dont le rôle est de créer un TL;DR (ré
### Résumé TL;DR
[Résumé concis et structuré]"""
PROMPT_SYSTEM_PLAN = """Ta tâche est de diviser le contenu du transcript en sujets concrets correspondant aux grands axes discutés durant la réunion. Ne crée pas de catégories génériques. Les titres doivent être courts, précis et représentatifs des échanges. Veille à ce que chaque sujet soit distinct et quaucun thème ne soit répété. Tu te limiteras à 5 ou 6 sujets maximum.
L'introduction, ordre du jour, conclusion, etc. seront rajoutés a posteriori. Si il n'y a pas de sujets clairs, réponds "Général".
PROMPT_SYSTEM_PLAN = """Ta tâche est de diviser le contenu du transcript en sujets concrets correspondant aux grands axes discutés durant la réunion. Ne crée pas de catégories génériques. Les titres doivent être courts, précis et représentatifs des échanges. Veille à ce que chaque sujet soit distinct et quaucun thème ne soit répété. Tu te limiteras à 5 ou 6 sujets maximum.
L'introduction, ordre du jour, conclusion, etc. seront rajoutés a posteriori. Tu répondras dans le format suivant sans rien ajouter d'autre:
"Titre du sujet 1
Titre du sujet 2
Titre du sujet 3
..."
"""
PROMPT_SYSTEM_PART = """Tu es un agent dont le rôle est de créer une partie du résumé d'un compte rendu de réunion. Tu utiliseras un style synthétique, administratif, à la troisième personne, sans affect. Tu recevras en entrée le transcript, et le titre du sujet correspondant. Ta tâche est de rédiger un résumé concis de cette partie et uniquement cette partie, en te concentrant uniquement sur les informations essentielles et pertinentes. Le résumé de chaque partie doit tenir en 4 à 6 phrases maximum, sans entrer dans les détails mineurs. Tu répondras dans le format suivant :
@@ -19,53 +23,6 @@ Transcript complet :
PROMPT_SYSTEM_CLEANING = """Tu es un agent dont le rôle est de nettoyer un résumé de compte rendu de réunion. Tu recevras en entrée le résumé brut, potentiellement avec des erreurs de formatage, des incohérences ou des redondances. Ta tâche est de corriger les erreurs de formatage, d'améliorer la clarté et la cohérence du texte, et de t'assurer que le résumé est bien structuré et facile à lire. Ton but principal est de retirer les redondances et les répétitions. Assure la cohérence entre les titres et homogénéise le style d’écriture entre les parties. Supprime les doublons dinformations entre les parties si présents. Si certaines parties sont plus secondaires, tu peux les fusionner ou les réduire en 1 à 2 phrases. Mets en avant les points centraux qui ont fait lobjet de décisions ou dactions. Tu répondras uniquement avec le résumé sans rien ajouter d'autre"""
PROMPT_SYSTEM_NEXT_STEP = """Tu es un agent dont le rôle est d'extraire les prochaines étapes d'un transcript de réunion. Tu utiliseras un style synthétique, administratif, à la troisième personne, sans affect. Tu recevras en entrée le transcript. Ta tâche est d'identifier et de lister toutes les actions à entreprendre, en indiquant la ou les personnes assignées et en précisant les échéances si elles sont mentionnées. Ne retiens que les actions concrètes et à venir. Ignore les remarques générales ou les constats sans suite."""
FORMAT_NEXT_STEPS = {
"type": "json_schema",
"json_schema": {
"name": "actions",
"schema": {
"type": "object",
"properties": {
"actions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"assignees": {
"type": "array",
"items": {"type": "string"},
"description": "Noms des personnes assignées",
},
"due_date": {
"type": "string",
"description": "Date d'échéance si mentionnée (si l'année nest pas précisée, ne pas l'ajouter)",
},
},
"required": ["title", "assignees"],
"additionalProperties": False,
},
}
},
"required": ["actions"],
"additionalProperties": False,
},
"strict": True,
},
}
FORMAT_PLAN = {
"type": "json_schema",
"json_schema": {
"name": "Titles",
"schema": {
"type": "object",
"properties": {"titles": {"type": "array", "items": {"type": "string"}}},
"required": ["titles"],
"additionalProperties": False,
},
"strict": True,
},
}
PROMPT_SYSTEM_NEXT_STEP = """Tu es un agent dont le rôle est d'extraire les prochaines étapes d'un transcript de réunion. Tu utiliseras un style synthétique, administratif, à la troisième personne, sans affect. Tu recevras en entrée le transcript. Ta tâche est d'identifier et de lister toutes les actions à entreprendre, en indiquant la ou les personnes assignées et en précisant les échéances si elles sont mentionnées. Ne retiens que les actions concrètes et à venir. Ignore les remarques générales ou les constats sans suite. Les actions doivent suivre ce format strict :
### Prochaines étapes
- [ ] [Action à effectuer] Assignée à : [Nom], Échéance : [Date si mentionnée]"""