Accept native Outlook message shape in classifier request
This commit is contained in:
57
README.md
57
README.md
@@ -33,7 +33,11 @@ export EMAIL_CLASSIFIER_DB_PATH=.data/email_classifier.db
|
|||||||
|
|
||||||
## Input shape
|
## 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
|
```json
|
||||||
{
|
{
|
||||||
@@ -42,38 +46,55 @@ Designed around real Outlook message payloads. Relevant fields:
|
|||||||
"conversationId": "AAQk...",
|
"conversationId": "AAQk...",
|
||||||
"subject": "MB Printer",
|
"subject": "MB Printer",
|
||||||
"bodyPreview": "Good morning, ...",
|
"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",
|
"receivedDateTime": "2026-02-19T15:27:35Z",
|
||||||
"sentDateTime": "2026-02-19T15:27:32Z",
|
"sentDateTime": "2026-02-19T15:27:32Z",
|
||||||
"hasAttachments": false,
|
"hasAttachments": false,
|
||||||
"importance": "normal",
|
"importance": "normal",
|
||||||
"isRead": false,
|
"isRead": false,
|
||||||
"body": {
|
"flag": { "flagStatus": "notFlagged" },
|
||||||
"contentType": "html",
|
"provider": "anthropic",
|
||||||
"content": "..."
|
"base_url": "https://api.minimax.io/anthropic",
|
||||||
}
|
"model": "MiniMax-M2.7"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
API request example:
|
Simplified request example:
|
||||||
|
|
||||||
```json
|
```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": {
|
"email_data": {
|
||||||
"subject": "MB Printer",
|
"subject": "MB Printer",
|
||||||
"body": "<html>...</html>"
|
"body": "<html>...</html>"
|
||||||
},
|
},
|
||||||
"provider": "anthropic",
|
"id": "AAMk...",
|
||||||
"base_url": "https://api.minimax.io/anthropic",
|
"conversationId": "AAQk..."
|
||||||
"model": "MiniMax-M2.7"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, model_validator
|
||||||
|
|
||||||
|
|
||||||
class EmailData(BaseModel):
|
class EmailData(BaseModel):
|
||||||
@@ -10,8 +10,26 @@ class EmailData(BaseModel):
|
|||||||
body: str
|
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):
|
class ClassifyRequest(BaseModel):
|
||||||
email_data: EmailData
|
email_data: EmailData | None = None
|
||||||
provider: Literal["openai", "anthropic"] | None = None
|
provider: Literal["openai", "anthropic"] | None = None
|
||||||
model: str | None = None
|
model: str | None = None
|
||||||
base_url: str | None = None
|
base_url: str | None = None
|
||||||
@@ -21,14 +39,39 @@ class ClassifyRequest(BaseModel):
|
|||||||
id: str | None = None
|
id: str | None = None
|
||||||
internetMessageId: str | None = None
|
internetMessageId: str | None = None
|
||||||
conversationId: str | None = None
|
conversationId: str | None = None
|
||||||
|
subject: str | None = None
|
||||||
bodyPreview: 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
|
receivedDateTime: str | None = None
|
||||||
sentDateTime: str | None = None
|
sentDateTime: str | None = None
|
||||||
hasAttachments: bool | None = None
|
hasAttachments: bool | None = None
|
||||||
importance: str | None = None
|
importance: str | None = None
|
||||||
isRead: bool | None = None
|
isRead: bool | None = None
|
||||||
|
flag: Flag | None = None
|
||||||
from_address: str | 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):
|
class ClassificationDetails(BaseModel):
|
||||||
summary: str | None = None
|
summary: str | None = None
|
||||||
|
|||||||
Reference in New Issue
Block a user