Accept native Outlook message shape in classifier request

This commit is contained in:
Steve W
2026-04-09 18:32:32 +00:00
parent c6ee735949
commit 8c49ce21e0
2 changed files with 84 additions and 20 deletions

View File

@@ -33,7 +33,11 @@ export EMAIL_CLASSIFIER_DB_PATH=.data/email_classifier.db
## Input shape
Designed around real Outlook message payloads. Relevant fields:
The request model accepts either:
- simplified input via `email_data`
- or native Outlook-style fields directly
Full Outlook-shaped example:
```json
{
@@ -42,38 +46,55 @@ Designed around real Outlook message payloads. Relevant fields:
"conversationId": "AAQk...",
"subject": "MB Printer",
"bodyPreview": "Good morning, ...",
"body": {
"contentType": "html",
"content": "<html>...(full HTML body)</html>"
},
"sender": {
"emailAddress": {
"name": "Bobbi Johnson",
"address": "bobbi.johnson@grandportage.com"
}
},
"from": {
"emailAddress": {
"name": "Bobbi Johnson",
"address": "bobbi.johnson@grandportage.com"
}
},
"toRecipients": [
{
"emailAddress": {
"name": "IT Helpdesk Mail",
"address": "helpdeskmail@grandportage.com"
}
}
],
"ccRecipients": [],
"bccRecipients": [],
"replyTo": [],
"receivedDateTime": "2026-02-19T15:27:35Z",
"sentDateTime": "2026-02-19T15:27:32Z",
"hasAttachments": false,
"importance": "normal",
"isRead": false,
"body": {
"contentType": "html",
"content": "..."
}
"flag": { "flagStatus": "notFlagged" },
"provider": "anthropic",
"base_url": "https://api.minimax.io/anthropic",
"model": "MiniMax-M2.7"
}
```
API request example:
Simplified request example:
```json
{
"id": "AAMk...",
"internetMessageId": "<...@...>",
"conversationId": "AAQk...",
"bodyPreview": "Good morning, ...",
"receivedDateTime": "2026-02-19T15:27:35Z",
"sentDateTime": "2026-02-19T15:27:32Z",
"hasAttachments": false,
"importance": "normal",
"isRead": false,
"email_data": {
"subject": "MB Printer",
"body": "<html>...</html>"
},
"provider": "anthropic",
"base_url": "https://api.minimax.io/anthropic",
"model": "MiniMax-M2.7"
"id": "AAMk...",
"conversationId": "AAQk..."
}
```

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, model_validator
class EmailData(BaseModel):
@@ -10,8 +10,26 @@ class EmailData(BaseModel):
body: str
class EmailAddress(BaseModel):
name: str | None = None
address: str | None = None
class Recipient(BaseModel):
emailAddress: EmailAddress | None = None
class EmailBody(BaseModel):
contentType: str | None = None
content: str | None = None
class Flag(BaseModel):
flagStatus: str | None = None
class ClassifyRequest(BaseModel):
email_data: EmailData
email_data: EmailData | None = None
provider: Literal["openai", "anthropic"] | None = None
model: str | None = None
base_url: str | None = None
@@ -21,14 +39,39 @@ class ClassifyRequest(BaseModel):
id: str | None = None
internetMessageId: str | None = None
conversationId: str | None = None
subject: str | None = None
bodyPreview: str | None = None
body: EmailBody | None = None
sender: Recipient | None = None
from_: Recipient | None = Field(default=None, alias="from")
toRecipients: list[Recipient] = Field(default_factory=list)
ccRecipients: list[Recipient] = Field(default_factory=list)
bccRecipients: list[Recipient] = Field(default_factory=list)
replyTo: list[Recipient] = Field(default_factory=list)
receivedDateTime: str | None = None
sentDateTime: str | None = None
hasAttachments: bool | None = None
importance: str | None = None
isRead: bool | None = None
flag: Flag | None = None
from_address: str | None = None
@model_validator(mode="after")
def populate_email_data(self) -> "ClassifyRequest":
subject = self.email_data.subject if self.email_data else self.subject
body = self.email_data.body if self.email_data else (self.body.content if self.body and self.body.content else None)
if not subject or not body:
raise ValueError("Request must include either email_data or Outlook subject/body.content fields")
self.email_data = EmailData(subject=subject, body=body)
if not self.from_address:
self.from_address = (
(self.from_.emailAddress.address if self.from_ and self.from_.emailAddress else None)
or (self.sender.emailAddress.address if self.sender and self.sender.emailAddress else None)
)
return self
model_config = {"populate_by_name": True}
class ClassificationDetails(BaseModel):
summary: str | None = None