Voice-to-Field-Report with Groq

Record voice observations and generate structured field reports using Groq AI.

Introduction

30 Second Summary

Field workers spend hours typing up reports from scribbled notes after a long day of inspections. What if you could just talk about what you saw and get a professional report back in seconds?

In this project, you will build a Python command-line tool that records your voice, transcribes it with AI, and transforms your spoken observations into a structured field report with risk levels and action items.

What You'll Build

You speak your observations into the microphone, press Enter to stop, and a professionally formatted Markdown report appears in your terminal and saves to a file.

By the end of this project, you'll have:

  • A working voice-to-report pipeline where you speak naturally and a structured report with Location, Findings, Risk Level, and Recommended Actions appears in seconds.
  • Hands-on experience orchestrating two AI models through a single API. Groq Whisper transcribes your audio, and Llama 3.3 70B structures it into professional report sections.
  • Timestamped Markdown report files saved to your directory, ready to share or archive.
  • Secret Mission: Add selectable report templates for Environmental Surveys and Maintenance Checks with domain-specific fields.

Are there any prerequisites?

You need Python 3.10 or higher installed on your machine and a free Groq API key. Step 1 walks you through installing the required packages and setting up your credentials.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Taking a moment to commit to the goal helps you stay focused through the setup and coding ahead.

Set Up Your Environment

Before you can record audio or generate reports with AI, your Python environment needs the right packages and API credentials in place.

This step installs the three libraries that power the tool and configures your Groq API key so the script can authenticate when it calls the transcription and LLM endpoints.

In this step, get ready to:
  • Install the groq, sounddevice, and numpy packages.
  • Set your Groq API key as an environment variable.
  • Verify all packages import successfully.
Install the required Python packages
  • Install the three packages your project needs by running this command:
pip install groq sounddevice numpy

What do these packages do?

  • groq is the official Python SDK for the Groq API. It handles both speech transcription and LLM chat completions through a single client.
  • sounddevice records live audio from your microphone using the PortAudio library under the hood.
  • numpy manages the raw audio data as numerical arrays so it can be saved to a WAV file.
  • You should see "Successfully installed" messages for each package in the terminal output.

Seeing an error during install?

  • If you see "pip: command not found", try pip3 instead, or run python -m pip install groq sounddevice numpy.
  • On Linux, sounddevice requires the PortAudio system library. Install it with your package manager, then re-run the pip command.
  • The Groq SDK requires Python 3.10 or higher. Run python --version to confirm your version meets this requirement.

help me fix a pip install error for groq, sounddevice, or numpy

Set your Groq API key

Your script needs to authenticate with the Groq API to use Whisper transcription and Llama for report generation. Environment variables keep credentials out of your source code.

  • Set your API key as an environment variable by running the command for your operating system:

macOS

export GROQ_API_KEY=your-api-key-here
  • Replace your-api-key-here with the actual API key from your Groq account.
  • This same command works on Linux.

Windows

set GROQ_API_KEY=your-api-key-here
  • Replace your-api-key-here with the actual API key from your Groq account.

Why an environment variable?

Storing your API key in an environment variable means it never appears in your Python file. If you share your code later, the key stays private on your machine.

Key not working later?

  • The export or set command only lasts for the current terminal session. If you close and reopen your terminal, you need to run it again.
  • Make sure there are no spaces around the = sign. The format is GROQ_API_KEY=gsk_... with no gaps.

help me troubleshoot my GROQ_API_KEY environment variable setup

Verify the installation

Before you run this, what do you expect to see if all three packages installed correctly and your API key is set?

  • Confirm everything is working by running this import check:
python -c "import sounddevice; import numpy; from groq import Groq; print('All packages ready')"

You should see All packages ready printed with no errors.

Seeing a ModuleNotFoundError?

  • If the error names a specific package, that package did not install. Re-run the pip install command and check the output for errors.
  • Make sure you are running python in the same terminal session where you ran pip install. A different terminal or virtual environment will not see the packages.
  • On some systems the command is python3 instead of python. Try replacing python with python3 in the command above.

help me fix a ModuleNotFoundError when verifying my packages

Your environment is ready. Next up, you'll build the audio recording function that captures your spoken observations from the microphone.

Record Live Audio

Your packages are installed and your API key is set. Now you need actual audio to work with.

Before you can transcribe anything, you need a way to capture your voice. In this step, you will build the recording function that listens to your microphone and saves the audio as a WAV file.

In this step, get ready to:
  • Create field_report.py with audio recording configuration.
  • Build a recording function that captures microphone input until you press Enter.
  • Test the recording by speaking and verifying the WAV file is saved.
Create the recording script

The recording approach uses a sounddevice InputStream with a callback function. The callback runs in a background thread, collecting audio frames into a list while the main thread waits for you to press Enter. This lets you record for as long as you want without specifying a duration up front.

  • Create a new file called field_report.py in the same folder where you ran your pip install.
  • Add the imports and configuration constants by pasting this code:
import os
import sys
import wave
import numpy as np
import sounddevice as sd
from pathlib import Path
from datetime import datetime
from groq import Groq


# --- Configuration ---
SAMPLE_RATE = 16000
CHANNELS = 1
DTYPE = "int16"
RECORDING_FILE = "field_recording.wav"

What does this configuration do?

SAMPLE_RATE = 16000 sets the audio to 16kHz, which is what Whisper models are trained on. Higher sample rates waste bandwidth with no accuracy gain for speech.

CHANNELS = 1 captures mono audio (one channel). Speech transcription does not benefit from stereo. DTYPE = "int16" stores samples as 16-bit integers, which is the standard for WAV files.

  • Save field_report.py.
  • Confirm the file exists by running:
ls field_report.py

You should see field_report.py printed in the output.

Now add the recording function below your configuration constants. This function does the heavy lifting of capturing audio from your microphone.

  • Add the following record_audio() function below the RECORDING_FILE line in field_report.py:
def record_audio():
    """Record audio from microphone until Enter is pressed."""
    recorded_frames = []

    def callback(indata, frames, time, status):
        if status:
            print(status, file=sys.stderr)
        recorded_frames.append(indata.copy())

    print("\n" + "=" * 50)
    print("RECORDING - Speak your field observations now.")
    print("Press Enter to stop recording.")
    print("=" * 50 + "\n")

    with sd.InputStream(
        samplerate=SAMPLE_RATE, channels=CHANNELS, dtype=DTYPE, callback=callback
    ):
        input()

    audio_data = np.concatenate(recorded_frames)

    with wave.open(RECORDING_FILE, "wb") as wf:
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(np.dtype(DTYPE).itemsize)
        wf.setframerate(SAMPLE_RATE)
        wf.writeframes(audio_data.tobytes())

    duration = len(audio_data) / SAMPLE_RATE
    print(f"Recording saved: {RECORDING_FILE} ({duration:.1f} seconds)")
    return RECORDING_FILE

What does this function do?

The callback function runs in a separate PortAudio thread every time the microphone captures a chunk of audio. It appends a copy of each chunk to recorded_frames. You must use indata.copy() because PortAudio reuses the buffer after the callback returns.

The sd.InputStream context manager starts recording. Meanwhile, input() blocks the main thread until you press Enter. Once you do, the stream closes and the collected frames are concatenated into a single numpy array and saved as a WAV file using Python's built-in wave module.

Why 16kHz instead of 44.1kHz?

Whisper models are trained on 16kHz audio. Recording at a higher sample rate like 44.1kHz would create a larger file that Groq would need to downsample anyway. Using 16kHz keeps files small and avoids unnecessary processing.

Add the entry point and test your microphone

The recording function is ready but has no way to run yet. You need an entry point that initializes the Groq client and calls the recorder.

  • Add the following main() function and entry point at the bottom of field_report.py:
def main():
    client = Groq(api_key=os.environ.get("GROQ_API_KEY"))

    # Record audio
    audio_path = record_audio()


if __name__ == "__main__":
    main()

What does this code do?

The main() function creates a Groq client using the API key you set in your environment. It then calls record_audio() to capture your voice.

The if __name__ == "__main__" guard ensures main() only runs when you execute the file directly, not when it's imported as a module.

  • Save field_report.py.

Microphone permission prompt

When you run this script, your operating system may ask for microphone permission. On macOS, you will see a prompt asking to allow your Terminal app to access the microphone. On Windows, ensure microphone access is enabled in Settings then Privacy & Security then Microphone. Click Allow when prompted.

Before you run this, what do you expect to happen after you press Enter to stop recording?

  • Run the script by executing:
python field_report.py
  • When you see the "RECORDING" banner, speak a few words into your microphone (anything will do).
  • Press Enter to stop recording.

You should see output like this:

Recording saved: field_recording.wav (3.2 seconds)
  • Confirm the WAV file exists by running:
ls field_recording.wav

You should see field_recording.wav listed. The duration shown will match roughly how long you spoke before pressing Enter.

Seeing an error about microphone access?

On macOS, if you see silent recording or zeros, make sure you ran the script from Terminal (not an IDE that may not trigger the permission prompt). Go to System Settings then Privacy & Security then Microphone and enable your Terminal app.

On Windows, open Settings then Privacy & Security then Microphone and ensure Let desktop apps access your microphone is turned on.

help me fix a microphone permission error with sounddevice in Python

✔️ Awesome, I've got everything!

Great. Double check you have saved field_report.py and that field_recording.wav exists in your directory.

ⓧ I'd like to double check the full code

Scroll up and confirm your field_report.py contains all three sections in this order:

  • Imports and configuration constants (SAMPLE_RATE, CHANNELS, DTYPE, RECORDING_FILE).
  • The record_audio() function with the callback, InputStream, and WAV-saving logic.
  • The main() function and if __name__ == "__main__" entry point at the bottom.

You have a working microphone recorder that saves WAV files. Next up, you will send that audio to Groq Whisper and see your spoken words turned into text.

Transcribe with Groq Whisper

Your recording pipeline works. You can speak into your microphone and get a WAV file saved to disk.

But a WAV file sitting on your hard drive is not useful on its own. You need your spoken words converted to text so you can work with them. In this step, you will send the recording to Groq Whisper for transcription and see what comes back.

In this step, get ready to:
  • Add a transcription function that sends your WAV file to Groq Whisper.
  • Run the full pipeline and see your spoken words printed as text.
Add the transcription function

Groq's speech-to-text API accepts an audio file and returns the transcribed text. The whisper-large-v3-turbo model processes audio at 216x real-time speed, meaning a 30-second recording transcribes in a fraction of a second.

  • In field_report.py, find the main() function. Add the following transcribe_audio() function directly above it:
def transcribe_audio(client, audio_path):
    """Transcribe audio file using Groq Whisper."""
    print("\nTranscribing audio with Groq Whisper...")

    transcription = client.audio.transcriptions.create(
        model="whisper-large-v3-turbo",
        file=Path(audio_path),
        language="en",
        temperature=0.0,
    )

    print("\n--- Raw Transcript ---")
    print(transcription.text)
    print("--- End Transcript ---\n")
    return transcription.text

What does this code do?

  • client.audio.transcriptions.create() sends the WAV file to Groq's Whisper model for transcription.
  • file=Path(audio_path) passes the file path. The Groq SDK accepts a Path object and handles the upload.
  • language="en" tells Whisper to expect English, improving accuracy.
  • temperature=0.0 produces the most deterministic transcription with no randomness.
  • transcription.text contains the raw text output from the model.
  • Save field_report.py.
Call the transcription function from main

The function exists, but nothing calls it yet. You need to wire it into your main() pipeline so it runs after recording.

  • In field_report.py, scroll down to your main() function.
  • Find this line:
    # Record audio
    audio_path = record_audio()
  • Add the following lines directly below it:
    # Transcribe
    transcript = transcribe_audio(client, audio_path)

What does this addition do?

This passes the Groq client and the path to your saved WAV file into the transcription function. The returned text is stored in transcript so future steps can process it further.

  • Save field_report.py.
Run the full pipeline and observe the raw transcript

Before you run this, think about what the output will look like. You are speaking naturally into a microphone. Do you expect the transcript to come back as clean, organized text, or as a messy stream of words?

  • Run the script by entering this command:
python field_report.py
  • When prompted, speak some field observations for a few seconds. Describe something you can see around you as if you were conducting an inspection.
  • Press Enter to stop recording.

You should see your spoken words printed between --- Raw Transcript --- and --- End Transcript --- markers. Notice how it is a single block of unstructured text. Filler words, run-on sentences, and conversational phrasing all come through. This is exactly what Whisper heard you say.

That messy output is the designed limitation of this step. Transcription alone does not produce a professional report. The next step fixes that gap.

Not seeing a transcript?

  • If you see an authentication error, confirm your GROQ_API_KEY environment variable is set in the same terminal session where you ran the script.
  • If the recording is very short (under 1 second), Whisper may return an empty string. Try speaking for at least 3-5 seconds before pressing Enter.
  • If you see a file-not-found error, make sure you are running the script from the same folder that contains field_recording.wav.

Why isn't my Groq Whisper transcription working?

✔️ Awesome, I've got everything!

Great. Double check you have saved field_report.py with both the new function and the updated main().

ⓧ I'd like to double check the full code

Your field_report.py should contain these sections in order. Scroll through and confirm each one is present.

  • Imports at the top (os, sys, wave, numpy, sounddevice, Path, datetime, Groq).
  • Configuration constants (SAMPLE_RATE, CHANNELS, DTYPE, RECORDING_FILE).
  • record_audio() function with the InputStream and WAV save logic.
  • transcribe_audio(client, audio_path) function with the Groq Whisper API call.
  • main() function that creates the client, calls record_audio(), then calls transcribe_audio().
  • if __name__ == "__main__": block at the bottom.

You now have a working voice-to-text pipeline. Speak into your microphone and get a transcript in seconds. But that transcript is not something you would submit as a professional report. Next up, you will feed it to an LLM that transforms the messy text into a structured field report with proper sections and risk levels.

Generate the Structured Field Report

In the previous step, you recorded audio and printed the raw transcript. That output is full of filler words, half-finished thoughts, and zero structure.

You would never submit that as a professional report. In this step, you feed the messy transcript to Llama 3.3 70B with a carefully crafted system prompt that transforms stream-of-consciousness observations into structured sections with risk levels and action items.

In this step, get ready to:
  • Add a generate_report() function with a system prompt that defines the report structure.
  • Add a save_report() function that writes timestamped Markdown files.
  • Run the full pipeline end-to-end and see a professional field report appear.
Add the generate_report function

The key to turning messy speech into a professional report is prompt engineering. You give the LLM a detailed template defining exactly which sections to produce and which formatting rules to follow.

  • In field_report.py, find the transcribe_audio function. Add the following new function directly below it (above main()):
def generate_report(client, transcript):
    """Generate structured field report from transcript using Groq LLM."""
    print("Generating structured field report...")

    system_prompt = """You are a professional field report writer. Convert the following raw field observations into a structured field report. Use this exact format:

# Field Report

**Date:** [today's date]
**Inspector:** [if mentioned, otherwise "Field Inspector"]
**Report Type:** [Environmental Survey / Maintenance Check / General Inspection]

## Location
[Extract location details from the observations]

## Findings
[Organize findings into clear, numbered points]

## Condition Assessment
[Summarize overall condition: Good / Fair / Poor / Critical]

## Risk Level
[Low / Medium / High / Critical - with brief justification]

## Recommended Actions
[List specific actions with priority: Immediate / Short-term / Long-term]

## Additional Notes
[Any other relevant observations]

What does this prompt do?

The system prompt acts as instructions for the LLM. It defines six report sections (Location, Findings, Condition Assessment, Risk Level, Recommended Actions, Additional Notes) and sets the exact Markdown format the output must follow.

By spelling out the headings and placeholder descriptions, the model knows exactly where to slot each piece of information from your messy transcript.

  • Continue adding to the same function directly below what you just pasted. This completes the system prompt rules and adds the API call:

Rules:
- Remove filler words and conversational language
- Use professional, technical language
- If information is not mentioned, write "Not specified"
- Prioritize safety-related findings"""

    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"Raw field observations:\n\n{transcript}"},
        ],
        model="llama-3.3-70b-versatile",
        temperature=0.3,
        max_tokens=2000,
    )

    report = chat_completion.choices[0].message.content
    return report

What do these parameters do?

  • The Rules section tells the model to strip filler words, use professional language, and prioritize safety findings.
  • model="llama-3.3-70b-versatile" selects the Llama 3.3 70B model on Groq for high-quality text generation.
  • temperature=0.3 keeps output consistent while allowing natural language variation. Lower values produce more predictable reports.
  • max_tokens=2000 gives the model enough space to write a full report without cutting off mid-sentence.
  • Save field_report.py.
  • Confirm there are no syntax errors by running:
python -c "import field_report; print('No syntax errors')"

You should see "No syntax errors" printed. If you see a SyntaxError, check that the triple-quoted string closes properly with three double quotes on the same line as the last rule.

Seeing a SyntaxError?

  • Make sure the system_prompt string ends with three double-quote characters on the same line as the last rule.
  • Check that the chat_completion block is indented at the same level as system_prompt (inside the function).
  • Confirm the f-string uses a backslash-n (\n\n) not a literal newline inside the quotes.

Still stuck? Help me fix a SyntaxError in my generate_report function.

Add the save_report function

The generated report needs to be saved as a file you can share or archive. A timestamped filename ensures each run produces a unique file that never overwrites a previous report.

  • In field_report.py, add this function directly below generate_report (still above main()):
def save_report(report):
    """Save the generated report as a markdown file."""
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    filename = f"field_report_{timestamp}.md"

    with open(filename, "w") as f:
        f.write(report)

    print(f"Report saved: {filename}")
    return filename

What does this function do?

  • datetime.now().strftime("%Y-%m-%d_%H-%M-%S") creates a timestamp like 2026-07-11_14-30-05 so every report file is unique.
  • The file is saved as Markdown (.md) so it renders with proper headings and bold text in any Markdown viewer.
  • Save field_report.py.
Update main() and run the full pipeline

Now you wire everything together. The main() function needs to call generate_report() and save_report(), then display the finished report in the terminal.

  • In field_report.py, replace your entire main() function with this updated version:
def main():
    client = Groq(api_key=os.environ.get("GROQ_API_KEY"))

    # Record audio
    audio_path = record_audio()

    # Transcribe
    transcript = transcribe_audio(client, audio_path)

    # Generate report
    report = generate_report(client, transcript)

    # Display and save
    print("\n" + "=" * 50)
    print("GENERATED FIELD REPORT")
    print("=" * 50)
    print(report)
    print("=" * 50)

    save_report(report)
    print("\nDone! Your field report is ready.")

What changed in main()?

  • generate_report(client, transcript) sends the raw transcript to Llama 3.3 70B and gets back the structured report.
  • The display block prints the report between separator lines so you can read it clearly in the terminal.
  • save_report(report) writes the report to a timestamped Markdown file in your current directory.
  • Save field_report.py.

Before you run the full pipeline, what do you think the output will look like? Will it still be a messy transcript, or will it have clear sections and headings?

  • Run the full pipeline by executing:
python field_report.py
  • Speak some field observations into your microphone. Try something like: "I'm at the north parking structure, level three. There's a large crack running along the east wall, about two meters long. The railing on the stairwell is loose. Water damage on the ceiling near the elevator shaft."
  • Press Enter to stop recording.

You should see the raw transcript printed first, followed by a professionally formatted field report with clear sections: Location, Findings, Condition Assessment, Risk Level, and Recommended Actions.

  • Confirm the Markdown file was saved by running:
ls field_report_*.md

You should see a file named something like field_report_2026-07-11_14-30-05.md (with your current date and time).

Report not generating?

  • If you see an authentication error, confirm your GROQ_API_KEY environment variable is still set in this terminal session.
  • If the script hangs after "Generating structured field report...", check your internet connection. The Groq API call requires network access.
  • If you see a model not found error, confirm the model string is exactly llama-3.3-70b-versatile with no typos.

Need more help? Help me debug why my Groq report generation is failing.

✔️ Awesome, I've got everything!

Great. Double check you have saved field_report.py and confirmed the timestamped .md file exists in your directory.

ⓧ I'd like to double check the full code

Your field_report.py is too long to show in one block. Scroll through your file and confirm these sections are present in this order:

  • Imports (os, sys, wave, numpy, sounddevice, Path, datetime, Groq).
  • Configuration constants (SAMPLE_RATE, CHANNELS, DTYPE, RECORDING_FILE).
  • record_audio() function with the callback and WAV file saving.
  • transcribe_audio(client, audio_path) function calling Whisper.
  • generate_report(client, transcript) function with the full system prompt and the llama-3.3-70b-versatile API call.
  • save_report(report) function writing a timestamped .md file.
  • main() calling record, transcribe, generate, display, and save in sequence.
  • if __name__ == "__main__": main() at the bottom.

Your voice-to-report pipeline is fully working. You speak, the tool transcribes, the LLM structures, and a professional Markdown file lands in your directory. Next up, you will make this tool even more versatile with selectable report templates.

Secret mission

Add Report Type Selection

Different field work requires different report formats. Environmental surveys need flora/fauna sections and compliance notes, while maintenance checks need asset condition ratings and parts lists. Can you extend your tool with selectable templates that generate domain-specific reports?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine. Groq API calls are pay-per-use with no idle charges, so there are no ongoing costs to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Local project files: field_report.py, field_recording.wav, generated field_report_*.md and transcript_*.txt files.
  • Python packages: groq, sounddevice, numpy.
  • Environment variable: GROQ_API_KEY.

Keep everything running

No action needed. Choose this if you plan to keep using the field report tool or want to extend it further.

  • Your field_report.py script is ready to run anytime you need a new report.
  • Your GROQ_API_KEY environment variable stays active for future sessions.
  • The Python packages remain installed and available for other projects.

Pause - I'll come back to this later

Remove the temporary recording file to free up disk space, but keep your script and packages for later.

  • Delete the temporary WAV file by running:
  • macOS/Linux: rm field_recording.wav.
  • Windows: del field_recording.wav.
  • Keep field_report.py and your generated reports for when you return.

Delete - I don't want to use this again

Remove all project files and uninstall the Python packages.

  • Delete all project files. Navigate to the folder containing field_report.py and run:

macOS/Linux:

rm -f field_report.py field_recording.wav field_report_*.md transcript_*.txt

Windows:

del field_report.py, field_recording.wav, field_report_*.md, transcript_*.txt
  • Uninstall the Python packages by running:
pip uninstall groq sounddevice numpy -y
  • Remove the GROQ_API_KEY environment variable from your shell profile.

macOS/Linux:

unset GROQ_API_KEY

Windows:

set GROQ_API_KEY=

Make it permanent

The unset command only clears the variable for the current session. If you added it to your .bashrc, .zshrc, or system environment variables, remove the line from that file as well.

Nice Work!

Nice Work!

Nice work! You just built a complete voice-to-field-report pipeline that records your spoken observations, transcribes them with Groq Whisper, and outputs professionally structured Markdown reports.

You've learned how to:

  • Orchestrate two AI models (Whisper for transcription and Llama for structuring) through the Groq API to build a practical automation tool.
  • Record live audio from your microphone using sounddevice and transcribe it at 216x real-time speed with Groq Whisper.
  • Transform unstructured transcripts into professional field reports with risk levels and action items using LLM prompt engineering.
  • Secret Mission: Added selectable report templates for Environmental Surveys and Maintenance Checks with domain-specific sections.

Ready to quiz yourself?