Agent example: minutes from audio
The job: after the weekly review, someone has to write the minutes and chase the action items. The agent takes the recording, pulls out decisions and action items for your task tracker, and creates the minutes of meeting (MoM) as a Word file.
Tools used: audio reading and Word file creation on POST /chat, and structured JSON on
/v1/chat/completions.
The code
Section titled “The code”import base64, json, osfrom pathlib import Pathimport requestsfrom openai import OpenAIfrom pydantic import BaseModel, Field
BASE = "https://dummydomain"KEY = os.environ["API_KEY"]HEADERS = {"Authorization": "Bearer " + KEY}client = OpenAI(base_url=BASE + "/v1", api_key=KEY)
def chat(body: dict) -> dict: r = requests.post(f"{BASE}/chat", headers=HEADERS, json={**body, "stream": False}, timeout=900) r.raise_for_status() return r.json()
recording = Path("weekly-review.wav")first = chat({ "message": "List every decision and every action item from this meeting, with the owner and due date " "when they are said. Say 'not said' when they are not.", "attachments": [{"name": recording.name, "data": base64.b64encode(recording.read_bytes()).decode()}],})session_id = first["session_id"]
class Action(BaseModel): task: str owner: str = Field(description="Person's name, or 'not said'") due: str = Field(description="DD-MM-YYYY, or 'not said'")
class Outcome(BaseModel): decisions: list[str] actions: list[Action]
reply = client.chat.completions.create( model="Fast", messages=[{"role": "user", "content": "Turn these notes into JSON:\n" + first["reply"]}], response_format={"type": "json_schema", "json_schema": { "name": "Outcome", "schema": Outcome.model_json_schema(), "strict": True}},)outcome = Outcome.model_validate_json(reply.choices[0].message.content)for a in outcome.actions: print("Add to tracker:", a.task, "|", a.owner, "|", a.due)
# Same conversation, so the recording is not sent again.mom = chat({"session_id": session_id, "message": "Create a Word document with the minutes of meeting: date, attendees, decisions, " "and an action items table with owner and due date."})art = mom.get("office_artifact")if art: file = requests.get(f"{BASE}/office/artifacts/{art['artifact_id']}/download", headers=HEADERS, timeout=120) file.raise_for_status() Path(art["filename"]).write_bytes(file.content) print("Minutes saved as", art["filename"])else: print("No Word file was made. The notes are in the reply:\n" + mom["reply"])See it run
Section titled “See it run”A real run of the code above, on made-up sample files. Press Replay to watch the steps in order.
What went in
What the agent did
POST /chat4.7 sSent weekly-review.wav to ZenithAI, which listened to it and answered.
Request and response
Request { "message": "List every decision and every action item from this meeting, with the owner and due date when they are said. Say 'not said' when they are not.", "attachments": [ { "name": "weekly-review.wav", "data": "<file, 1,241,364 bytes>" } ], "stream": false }Response 200 { "attempted_route": "general", "current_route": "general", "finish_reason": "stop", "max_tokens": 4096, "num_predict": 4096, "reply": "Based on the audio transcript provided, here are the decisions and action items:\n\n### **Decisions**\n* **Decision:** Move the pun launch to the 1st of November.\n * **Owner:** Not said\n * **Due Date:** Not said\n\n### **Action Items**\n* **Action Item:** Send the revised price list to all dealers.\n * **Owner:** Rahul\n * **Due Date:** 10th of October\n* **Action Item:** Update the launch plan and share it.\n * **Owner:** Priya\n * **Due Date:** 15th of October\n* **Action Item:** Book the venue in Pune.\n * **Owner:** Anil\n * **Due Date:** 20th of October", "route_disagreement": false, "session_id": "444349e8-e57e-4db2-923d-f77a59cf818e", "shadow_confidence": 0.7, "shadow_evidence_status": "n/a", "shadow_reason": "no tool route necessary; general knowledge", "shadow_route": "general" }POST /v1/chat/completionsFast2.8 sFast returned JSON that matches the Outcome schema.
Request and response
Request { "messages": [ { "role": "user", "content": "Turn these notes into JSON:\nBased on the audio transcript provided, here are the decisions and action items:\n\n### **Decisions**\n* **Decision:** Move the pun launch to the 1st of November.\n * **Owner:** Not said\n * **Due Date:** Not said\n\n### **Action Items**\n* **Action Item:** Send the revised price list to all dealers.\n * **Owner:** Rahul\n * **Due Date:** 10th of October\n* **Action Item:** Update the launch plan and share it.\n * **Owner:** Priya\n * **Due Date:** 15th of October\n* **Action Item:** Book the venue in Pune.\n * **Owner:** Anil\n * **Due Date:** 20th of October" } ], "response_format": { "type": "json_schema", "json_schema": { "name": "Outcome", "schema": { "$defs": { "Action": { "properties": { "task": { "title": "Task", "type": "string" }, "owner": { "description": "Person's name, or 'not said'", "title": "Owner", "type": "string" }, "due": { "description": "DD-MM-YYYY, or 'not said'", "title": "Due", "type": "string" } }, "required": [ "task", "owner", "due" ], "title": "Action", "type": "object" } }, "properties": { "decisions": { "items": { "type": "string" }, "title": "Decisions", "type": "array" }, "actions": { "items": { "$ref": "#/$defs/Action" }, "title": "Actions", "type": "array" } }, "required": [ "decisions", "actions" ], "title": "Outcome", "type": "object" }, "strict": true } } }Response 200 { "choices": [ { "finish_reason": "stop", "index": 0, "message": { "content": "{\"decisions\": [\"Move the pun launch to the 1st of November.\"], \"actions\": [{\"task\": \"Send the revised price list to all dealers.\", \"owner\": \"Rahul\", \"due\": \"10-10-2024\"}, {\"task\": \"Update the launch plan and share it.\", \"owner\": \"Priya\", \"due\": \"15-10-2024\"}, {\"task\": \"Book the venue in Pune.\", \"owner\": \"Anil\", \"due\": \"20-10-2024\"}]}", "role": "assistant" } } ], "object": "chat.completion", "usage": { "completion_tokens": 122, "prompt_tokens": 381, "total_tokens": 503 } }POST /chat15.3 sIn the same conversation, made a Word file: Minutes of Weekly Review Meeting.docx.
Request and response
Request { "session_id": "444349e8-e57e-4db2-923d-f77a59cf818e", "message": "Create a Word document with the minutes of meeting: date, attendees, decisions, and an action items table with owner and due date.", "stream": false }Response 200 { "office_artifact": { "artifact_id": "ssedmzOVUD-ca61-rq_abG5Om2Xsjw1p", "available": true, "created_at": "2026-10-07T00:33:58.725709Z", "expires_at": "2026-10-14T00:33:58.725709Z", "filename": "Minutes of Weekly Review Meeting.docx", "kind": "docx_report", "mime": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "size": 38487, "title": "Minutes of Weekly Review Meeting" }, "reply": "Created Minutes of Weekly Review Meeting.docx.", "session_id": "444349e8-e57e-4db2-923d-f77a59cf818e", "turn_plan_actions": "office_create", "turn_plan_ambiguity": "resolved_for_artifact", "turn_plan_artifact_archetype": "meeting_minutes", "turn_plan_artifact_kind": "docx_report", "turn_plan_artifact_source": "recent_chat", "turn_plan_auth_scope_digest": "19016770fb39deac", "turn_plan_conflict": false, "turn_plan_continuity_digest": "", "turn_plan_decision": "office_intent", "turn_plan_evidence_policy": "authorized_executor_receipts_only", "turn_plan_receipt_status": "verified", "turn_plan_reference": "current_turn", "turn_plan_required_receipts": "office_artifact", "turn_plan_route": "office", "turn_plan_subject_digest": "7f5821326c00ea2d", "turn_plan_version": "1" }GET /office/artifacts/{id}/download0.1 sDownloaded the file.
Request and response
Request (empty)
Response 200 <application/vnd.openxmlformats-officedocument.wordprocessingml.document, 38,487 bytes>
What the program printed
Add to tracker: Send the revised price list to all dealers. | Rahul | 10-10-2024 Add to tracker: Update the launch plan and share it. | Priya | 15-10-2024 Add to tracker: Book the venue in Pune. | Anil | 20-10-2024 Minutes saved as Minutes of Weekly Review Meeting.docx
File made: Minutes of Weekly Review Meeting.docx (38 KB)
How it works
Section titled “How it works”- Listen once. The recording goes up once, with the first question. ZenithAI turns the speech into text and answers.
- Keep the
session_id. Follow-up messages in the same conversation can use the recording without sending it again. - JSON for your tracker, Word for people. Fast turns the notes into fixed fields your code can send to your task tool. The Word file is for the people in the meeting.
- Check for the file. Now and then ZenithAI writes the minutes as text instead of making a file.
No
office_artifactmeans no file, so the agent prints the text. Ask again if you need the Word file.
Where else this works
Section titled “Where else this works”Sales calls, customer support calls, interviews, training sessions and site visit voice notes. For the web app version, see Audio and minutes.
Common questions
Which audio formats can the meeting agent use?
WAV, MP3, M4A, OGG and FLAC. Most phone and meeting-room recorders make one of these. Keep a file under about 100 MB, because base64 makes it a third bigger and the request limit is 150 MB.
Why does the meeting agent use POST /chat instead of /v1/chat/completions?
Audio is read through POST /chat. It also keeps a conversation, so the agent can ask about the recording, then ask for the Word minutes in the same conversation without sending the file again.