0Pricing
AI Agents · Aula

Geração de relatórios estruturados

Relatórios baseados em modelos: resumo executivo, descobertas, evidências e recomendações.

Geração de relatórios estruturados é uma aula grátis de AI Agents no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Agents, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Agents inclui 4 aulas no total.

Dos fatos a um relatório legível

Um agente de pesquisa que produz apenas uma lista de fatos é difícil de usar. Tomadores de decisão precisam de relatórios estruturados: um resumo executivo, uma seção de contexto, principais descobertas, evidências e recomendações.

Esta lição aborda a geração de relatórios profissionais, seção por seção, a partir de fatos verificados.

O modelo do relatório

Defina a estrutura do relatório desde o início. O LLM sintetiza uma seção por vez, mantendo cada seção focada e evitando repetições.

REPORT_SECTIONS = [
    'executive_summary',
    'background',
    'key_findings',
    'evidence',
    'recommendations',
    'sources'
]

SECTION_PROMPTS = {
    'executive_summary': 'Write a 2-3 sentence executive summary of the key findings. Business audience.',
    'background':        'Provide background context for the research topic (3-5 sentences).',
    'key_findings':      'List 3-5 key findings as bullet points, each with one supporting fact.',
    'evidence':          'Summarize the evidence for each finding with inline source citations.',
    'recommendations':   'Based on the findings, provide 2-4 actionable recommendations.',
    'sources':           'List all sources cited in the report, formatted as numbered URLs.'
}

if __name__ == '__main__':
    print('Report sections:', REPORT_SECTIONS)
    for section in REPORT_SECTIONS:
        print(f'{section}: {SECTION_PROMPTS[section]}')

Gerando uma seção por vez

Gerar o relatório inteiro em um único prompt não é confiável — o LLM perde o controle dos fatos e da estrutura. Gere o relatório seção por seção, passando as seções anteriores como contexto.

import openai

client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')

def generate_section(section_name: str, question: str,
                     facts: list[dict], prior_sections: dict) -> str:
    facts_text = '\n'.join(
        f'- {f["fact"]} (source: {f["source"]})' for f in facts[:25]
    )
    prior_text = '\n\n'.join(
        f'## {k.replace("_"," ").title()}\n{v}'
        for k, v in prior_sections.items()
    )
    prompt = (
        f'Research question: "{question}"\n\n'
        f'Verified facts:\n{facts_text}\n\n'
        f'Report so far:\n{prior_text}\n\n'
        f'Now write the "{section_name}" section.\n'
        f'Instructions: {SECTION_PROMPTS[section_name]}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}]
    )
    return resp.choices[0].message.content

Construindo o relatório de forma iterativa

Percorra todas as seções, passando um contexto acumulado. Cada seção tem conhecimento do que veio antes, o que garante a coerência e evita contradições dentro do relatório.

def build_report(question: str, facts: list[dict]) -> dict:
    report = {}
    sections_to_generate = [s for s in REPORT_SECTIONS if s != 'sources']

    for section in sections_to_generate:
        print(f'Generating section: {section}...')
        report[section] = generate_section(
            section_name=section,
            question=question,
            facts=facts,
            prior_sections={k: v for k, v in report.items()}
        )

    # Sources section: generate citation list from fact URLs
    unique_sources = list(dict.fromkeys(f['source'] for f in facts))
    report['sources'] = '\n'.join(
        f'{i+1}. {url}' for i, url in enumerate(unique_sources[:20])
    )

    return report

Inserindo citações no texto

Substitua as referências aos fatos nas seções geradas por citações numeradas que apontem para a lista de fontes. Isso torna cada afirmação rastreável.

import re

def inject_citations(section_text: str, facts: list[dict]) -> str:
    source_index = {}  # url -> int
    for i, fact in enumerate(facts):
        url = fact['source']
        if url not in source_index:
            source_index[url] = len(source_index) + 1

    annotated = section_text
    for fact in facts:
        if fact['fact'] in annotated:
            num = source_index[fact['source']]
            annotated = annotated.replace(
                fact['fact'],
                f'{fact["fact"]} [{num}]',
                1  # replace first occurrence only
            )
    return annotated

if __name__ == '__main__':
    demo_text = 'Revenue grew 12% last quarter. The team also launched two new products.'
    demo_facts = [{'fact': 'Revenue grew 12% last quarter', 'source': 'https://example.com/report'}]
    print(inject_citations(demo_text, demo_facts))

Formatando como Markdown

Formate as seções do relatório como Markdown. Isso permite converter a saída para HTML, PDF ou exibi-la diretamente em ferramentas como Notion ou Confluence.

def render_markdown(report: dict, title: str) -> str:
    section_titles = {
        'executive_summary': 'Executive Summary',
        'background':        'Background',
        'key_findings':      'Key Findings',
        'evidence':          'Evidence',
        'recommendations':   'Recommendations',
        'sources':           'Sources'
    }
    lines = [f'# {title}', '']
    for key in REPORT_SECTIONS:
        if key in report:
            lines.append(f'## {section_titles[key]}')
            lines.append('')
            lines.append(report[key])
            lines.append('')
    return '\n'.join(lines)

# Usage:
# md = render_markdown(report, 'Causes of Inflation in 2024')
# with open('report.md', 'w') as f: f.write(md)

Gerando um relatório com links para as fontes

Ao preparar a versão para a web, converta as URLs das fontes em hiperlinks. Adicione também um bloco de metadados com a data de geração do relatório, o número de fontes e a taxa de verificação.

from datetime import date

def render_html_report(report: dict, title: str,
                       facts: list[dict], question: str) -> str:
    meta = (
        f'<p><em>Generated: {date.today()} | '
        f'Sources: {len(set(f["source"] for f in facts))} | '
        f'Research question: {question}</em></p>'
    )
    html_parts = [f'<h1>{title}</h1>', meta]
    section_titles = {
        'executive_summary': 'Executive Summary',
        'background':        'Background',
        'key_findings':      'Key Findings',
        'evidence':          'Evidence',
        'recommendations':   'Recommendations',
        'sources':           'Sources'
    }
    for key in REPORT_SECTIONS:
        if key in report:
            content = report[key].replace('\n', '<br>')
            html_parts.append(f'<h2>{section_titles[key]}</h2><p>{content}</p>')
    return '\n'.join(html_parts)

Verificações de qualidade antes da publicação

Execute verificações automatizadas de qualidade antes de entregar o relatório: contagem mínima de palavras por seção, pelo menos N citações, todas as recomendações usando verbos de ação e nenhum texto de espaço reservado, como '[INSERT]'.

def quality_check(report: dict) -> list[str]:
    issues = []

    for section in ['executive_summary', 'background', 'key_findings']:
        word_count = len(report.get(section, '').split())
        if word_count < 30:
            issues.append(f'{section} too short: {word_count} words (min 30)')

    if report.get('sources', '').count('http') < 3:
        issues.append('Less than 3 cited sources')

    if '[INSERT]' in str(report) or 'TODO' in str(report):
        issues.append('Report contains placeholder text')

    recs = report.get('recommendations', '')
    if recs and not any(verb in recs.lower()
                        for verb in ['should', 'recommend', 'consider', 'implement']):
        issues.append('Recommendations may not be actionable')

    return issues

if __name__ == '__main__':
    demo_report = {
        'executive_summary': 'Too short.',
        'background': 'Also short.',
        'key_findings': 'Short too.',
        'sources': 'http://a.com',
        'recommendations': 'Looks fine as is.',
    }
    for issue in quality_check(demo_report):
        print('-', issue)

Resumo executivo: restrições rigorosas

O resumo executivo deve ser autossuficiente: quem ler apenas esta seção deverá entender a principal descoberta e a ação recomendada. Use um prompt rigoroso com um limite de palavras.

def generate_executive_summary(question: str, facts: list[dict],
                               max_words: int = 80) -> str:
    top_facts = '\n'.join(f'- {f["fact"]}' for f in facts[:10])
    prompt = (
        f'Research question: "{question}"\n\n'
        f'Key facts:\n{top_facts}\n\n'
        f'Write an executive summary in EXACTLY {max_words} words or fewer.\n'
        f'Format: [Main finding]. [Why it matters]. [Recommended action].'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}]
    )
    return resp.choices[0].message.content

Relatórios para vários públicos

A mesma pesquisa pode exigir estilos de relatório diferentes para públicos distintos: análise técnica aprofundada para engenheiros, resumo executivo para a liderança e um resumo em linguagem simples para não especialistas. Gere cada variante a partir do mesmo conjunto de fatos.

AUDIENCE_STYLES = {
    'executive':   'Brief, strategic. Avoid jargon. Focus on business impact and decisions.',
    'technical':   'Detailed, precise. Include methodology, caveats, and data sources.',
    'general':     'Plain language. Avoid technical terms. Use analogies where helpful.'
}

def generate_for_audience(facts: list[dict], question: str, audience: str) -> str:
    style = AUDIENCE_STYLES.get(audience, AUDIENCE_STYLES['general'])
    facts_text = '\n'.join(f'- {f["fact"]}' for f in facts[:20])
    prompt = (
        f'Synthesize these facts into a report for a {audience} audience.\n'
        f'Style guide: {style}\n'
        f'Question: "{question}"\n\n'
        f'Facts:\n{facts_text}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}]
    )
    return resp.choices[0].message.content

Salvando e criando versões dos relatórios

Salve os relatórios com uma marca de data e hora e o resumo criptográfico da pergunta de pesquisa. Isso permite comparar versões do relatório se a pesquisa for executada novamente com fontes atualizadas.

import hashlib, json, os
from datetime import datetime, timezone

def save_report(report: dict, question: str, output_dir: str = '/tmp/reports'):
    os.makedirs(output_dir, exist_ok=True)
    q_hash = hashlib.md5(question.encode()).hexdigest()[:8]
    timestamp = datetime.now(timezone.utc).strftime('%Y%m%d_%H%M')
    filename = f'{output_dir}/report_{q_hash}_{timestamp}.json'

    with open(filename, 'w') as f:
        json.dump({
            'question':   question,
            'generated':  timestamp,
            'sections':   report
        }, f, indent=2)
    print(f'Report saved: {filename}')
    return filename

if __name__ == '__main__':
    import tempfile
    demo_dir = tempfile.mkdtemp()
    save_report({'executive_summary': 'AI agent adoption grew significantly in 2026.'},
                'What are the key AI agent trends in 2026?', output_dir=demo_dir)

Qual seção deve ser sempre autossuficiente na estrutura do relatório?

A estrutura do relatório é projetada para que leitores com pouco tempo possam extrair valor sem ler o documento inteiro. Entender qual seção deve se sustentar sozinha é importante para o projeto do relatório.

Recapitulação da geração de relatórios estruturados

Gere relatórios seção por seção usando um modelo fixo (Resumo executivo → Contexto → Principais descobertas → Evidências → Recomendações → Fontes). Insira citações no texto, execute verificações de qualidade e ofereça estilos para vários públicos a partir do mesmo conjunto de fatos.

Sempre salve os relatórios com marcas de data e hora e resumos criptográficos das perguntas para criar versões.

Perguntas Frequentes

A aula “Geração de relatórios estruturados” é grátis?

Sim — o texto completo de “Geração de relatórios estruturados” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Agents, atualize para CoddyKit PRO. O curso de AI Agents inclui 4 aulas no total.

O que vou aprender em “Geração de relatórios estruturados”?

Relatórios baseados em modelos: resumo executivo, descobertas, evidências e recomendações. Você pratica AI Agents com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar AI Agents?

Nenhuma experiência prévia é necessária. AI Agents no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Geração de relatórios estruturados”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de AI Agents?

Sim. Cada aula de AI Agents inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Projeto de ciclos de pesquisa em várias etapas
  2. Verificação de fontes e citações
  3. Geração de relatórios estruturados
  4. Verificação de fatos e prevenção de alucinações
← Voltar para AI Agents