Como Gerar +100 Configurações de Agentes com LLMs e Processamento em Lote

Ashley Innocent

Ashley Innocent

19 março 2026

Como Gerar +100 Configurações de Agentes com LLMs e Processamento em Lote

Introdução

Configurar centenas de agentes de IA para uma simulação de mídia social parece assustador. Cada agente precisa de cronogramas de atividade, frequências de postagem, atrasos de resposta, pesos de influência e posições de postura. Fazer isso manualmente levaria horas.

O MiroFish automatiza isso com a geração de configuração alimentada por LLM. O sistema analisa seus documentos, grafo de conhecimento e requisitos de simulação, então gera configurações detalhadas para cada agente.

O desafio: LLMs podem falhar. Saídas são truncadas. JSON quebra. Limites de token são um problema.

Este guia aborda a implementação completa:

💡
O pipeline de geração de configuração processa mais de 100 agentes através de uma série de chamadas de API. O Apidog foi usado para validar esquemas de solicitação/resposta em cada estágio, capturar erros de formato JSON antes que chegassem à produção e gerar casos de teste para cenários de ponta, como saídas LLM truncadas.
button

Todo o código vem do uso em produção no MiroFish.

Visão Geral da Arquitetura

O gerador de configuração utiliza uma abordagem em pipeline:

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Context       │ ──► │   Time Config   │ ──► │   Event Config  │
│   Builder       │     │   Generator     │     │   Generator     │
│                 │     │                 │     │                 │
│ - Simulation    │     │ - Total hours   │     │ - Initial posts │
│   requirement   │     │ - Minutes/round │     │ - Hot topics    │
│ - Entity summary│     │ - Peak hours    │     │ - Narrative     │
│ - Document text │     │ - Activity mult │     │   direction     │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                                        │
                                                        ▼
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Final Config  │ ◄── │   Platform      │ ◄── │   Agent Config  │
│   Assembly      │     │   Config        │     │   Batches       │
│                 │     │                 │     │                 │
│ - Merge all     │     │ - Twitter params│     │ - 15 agents     │
│ - Validate      │     │ - Reddit params │     │   per batch     │
│ - Save JSON     │     │ - Viral threshold│    │ - N batches     │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Estrutura de Arquivos

backend/app/services/
├── simulation_config_generator.py  # Lógica principal de geração de configuração
├── ontology_generator.py           # Geração de ontologia (compartilhada)
└── zep_entity_reader.py            # Filtragem de entidades

backend/app/models/
├── task.py                         # Rastreamento de tarefas
└── project.py                      # Estado do projeto

Estratégia de Geração Passo a Passo

Gerar todas as configurações de uma só vez excederia os limites de token. Em vez disso, o sistema gera em estágios:

class SimulationConfigGenerator:
    # Cada lote gera configurações para 15 agentes
    AGENTS_PER_BATCH = 15

    # Limites de contexto
    MAX_CONTEXT_LENGTH = 50000
    TIME_CONFIG_CONTEXT_LENGTH = 10000
    EVENT_CONFIG_CONTEXT_LENGTH = 8000
    ENTITY_SUMMARY_LENGTH = 300
    AGENT_SUMMARY_LENGTH = 300
    ENTITIES_PER_TYPE_DISPLAY = 20

    def generate_config(
        self,
        simulation_id: str,
        project_id: str,
        graph_id: str,
        simulation_requirement: str,
        document_text: str,
        entities: List[EntityNode],
        enable_twitter: bool = True,
        enable_reddit: bool = True,
        progress_callback: Optional[Callable[[int, int, str], None]] = None,
    ) -> SimulationParameters:

        # Calcular total de etapas
        num_batches = math.ceil(len(entities) / self.AGENTS_PER_BATCH)
        total_steps = 3 + num_batches  # Tempo + Eventos + N Lotes de Agentes + Plataforma
        current_step = 0

        def report_progress(step: int, message: str):
            nonlocal current_step
            current_step = step
            if progress_callback:
                progress_callback(step, total_steps, message)
            logger.info(f"[{step}/{total_steps}] {message}")

        # Construir contexto
        context = self._build_context(
            simulation_requirement=simulation_requirement,
            document_text=document_text,
            entities=entities
        )

        reasoning_parts = []

        # Etapa 1: Gerar configuração de tempo
        report_progress(1, "Gerando configuração de tempo...")
        time_config_result = self._generate_time_config(context, len(entities))
        time_config = self._parse_time_config(time_config_result, len(entities))
        reasoning_parts.append(f"Configuração de tempo: {time_config_result.get('reasoning', 'Sucesso')}")

        # Etapa 2: Gerar configuração de eventos
        report_progress(2, "Gerando configuração de eventos e tópicos quentes...")
        event_config_result = self._generate_event_config(context, simulation_requirement, entities)
        event_config = self._parse_event_config(event_config_result)
        reasoning_parts.append(f"Configuração de eventos: {event_config_result.get('reasoning', 'Sucesso')}")

        # Etapas 3-N: Gerar configurações de agente em lotes
        all_agent_configs = []
        for batch_idx in range(num_batches):
            start_idx = batch_idx * self.AGENTS_PER_BATCH
            end_idx = min(start_idx + self.AGENTS_PER_BATCH, len(entities))
            batch_entities = entities[start_idx:end_idx]

            report_progress(
                3 + batch_idx,
                f"Gerando configuração de agente ({start_idx + 1}-{end_idx}/{len(entities)})..."
            )

            batch_configs = self._generate_agent_configs_batch(
                context=context,
                entities=batch_entities,
                start_idx=start_idx,
                simulation_requirement=simulation_requirement
            )
            all_agent_configs.extend(batch_configs)

        reasoning_parts.append(f"Configuração de agente: Gerados {len(all_agent_configs)} agentes")

        # Atribuir publicadores de postagens iniciais
        event_config = self._assign_initial_post_agents(event_config, all_agent_configs)

        # Etapa final: Configuração de plataforma
        report_progress(total_steps, "Gerando configuração de plataforma...")
        twitter_config = PlatformConfig(platform="twitter", ...) if enable_twitter else None
        reddit_config = PlatformConfig(platform="reddit", ...) if enable_reddit else None

        # Montar configuração final
        params = SimulationParameters(
            simulation_id=simulation_id,
            project_id=project_id,
            graph_id=graph_id,
            simulation_requirement=simulation_requirement,
            time_config=time_config,
            agent_configs=all_agent_configs,
            event_config=event_config,
            twitter_config=twitter_config,
            reddit_config=reddit_config,
            generation_reasoning=" | ".join(reasoning_parts)
        )

        return params

Esta abordagem em etapas:

Construindo Contexto

O construtor de contexto reúne informações relevantes enquanto respeita os limites de token:

def _build_context(
    self,
    simulation_requirement: str,
    document_text: str,
    entities: List[EntityNode]
) -> str:

    # Sumário de entidades
    entity_summary = self._summarize_entities(entities)

    context_parts = [
        f"## Requisito de Simulação\n{simulation_requirement}",
        f"\n## Informações da Entidade ({len(entities)} entidades)\n{entity_summary}",
    ]

    # Adicionar texto do documento se houver espaço
    current_length = sum(len(p) for p in context_parts)
    remaining_length = self.MAX_CONTEXT_LENGTH - current_length - 500  # Buffer de 500 caracteres

    if remaining_length > 0 and document_text:
        doc_text = document_text[:remaining_length]
        if len(document_text) > remaining_length:
            doc_text += "\n...(documento truncado)"
        context_parts.append(f"\n## Documento Original\n{doc_text}")

    return "\n".join(context_parts)

Sumarização de Entidades

Entidades são sumarizadas por tipo:

def _summarize_entities(self, entities: List[EntityNode]) -> str:
    lines = []

    # Agrupar por tipo
    by_type: Dict[str, List[EntityNode]] = {}
    for e in entities:
        t = e.get_entity_type() or "Unknown"
        if t not in by_type:
            by_type[t] = []
        by_type[t].append(e)

    for entity_type, type_entities in by_type.items():
        lines.append(f"\n### {entity_type} ({len(type_entities)} entidades)")

        # Exibir número limitado com comprimento de sumário limitado
        display_count = self.ENTITIES_PER_TYPE_DISPLAY
        summary_len = self.ENTITY_SUMMARY_LENGTH

        for e in type_entities[:display_count]:
            summary_preview = (e.summary[:summary_len] + "...") if len(e.summary) > summary_len else e.summary
            lines.append(f"- {e.name}: {summary_preview}")

        if len(type_entities) > display_count:
            lines.append(f"  ... e mais {len(type_entities) - display_count}")

    return "\n".join(lines)

Isso produz uma saída como:

### Estudante (45 entidades)
- Zhang Wei: Ativo no grêmio estudantil, frequentemente posta sobre eventos no campus e pressão acadêmica...
- Li Ming: Estudante de pós-graduação pesquisando ética da IA, frequentemente compartilha notícias de tecnologia...
... e mais 43

### Universidade (3 entidades)
- Universidade de Wuhan: Conta oficial, posta anúncios e notícias...

Geração da Configuração de Tempo

A configuração de tempo determina a duração da simulação e os padrões de atividade:

def _generate_time_config(self, context: str, num_entities: int) -> Dict[str, Any]:
    # Truncar contexto para esta etapa específica
    context_truncated = context[:self.TIME_CONFIG_CONTEXT_LENGTH]

    # Calcular valor máximo permitido (90% da contagem de agentes)
    max_agents_allowed = max(1, int(num_entities * 0.9))

    prompt = f"""Com base nos seguintes requisitos de simulação, gere a configuração de tempo.

{context_truncated}

## Tarefa
Gere o JSON da configuração de tempo.

### Princípios Básicos (ajuste com base no tipo de evento e grupos de participantes):
- A base de usuários é chinesa, deve seguir os hábitos de fuso horário de Pequim
- 0-5h: Quase nenhuma atividade (coeficiente 0.05)
- 6-8h: Acordando gradualmente (coeficiente 0.4)
- 9-18h: Horário de trabalho, atividade moderada (coeficiente 0.7)
- 19-22h: Pico noturno, mais ativo (coeficiente 1.5)
- 23h: Atividade diminuindo (coeficiente 0.5)

### Formato de retorno JSON (sem markdown):

Exemplo:
{{
    "total_simulation_hours": 72,
    "minutes_per_round": 60,
    "agents_per_hour_min": 5,
    "agents_per_hour_max": 50,
    "peak_hours": [19, 20, 21, 22],
    "off_peak_hours": [0, 1, 2, 3, 4, 5],
    "morning_hours": [6, 7, 8],
    "work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
    "reasoning": "Explicação da configuração de tempo"
}}

Descrições dos campos:
- total_simulation_hours (int): 24-168 horas, menor para notícias de última hora, maior para tópicos em andamento
- minutes_per_round (int): 30-120 minutos, recomenda-se 60
- agents_per_hour_min (int): Faixa 1-{max_agents_allowed}
- agents_per_hour_max (int): Faixa 1-{max_agents_allowed}
- peak_hours (array de int): Ajustar com base nos grupos de participantes
- off_peak_hours (array de int): Geralmente tarde da noite/madrugada
- morning_hours (array de int): Horas da manhã
- work_hours (array de int): Horário de trabalho
- reasoning (string): Breve explicação"""

    system_prompt = "Você é um especialista em simulação de mídia social. Retorne o formato JSON puro."

    try:
        return self._call_llm_with_retry(prompt, system_prompt)
    except Exception as e:
        logger.warning(f"Falha na geração de LLM da configuração de tempo: {e}, usando padrão")
        return self._get_default_time_config(num_entities)

Análise e Validação da Configuração de Tempo

def _parse_time_config(self, result: Dict[str, Any], num_entities: int) -> TimeSimulationConfig:
    # Obter valores brutos
    agents_per_hour_min = result.get("agents_per_hour_min", max(1, num_entities // 15))
    agents_per_hour_max = result.get("agents_per_hour_max", max(5, num_entities // 5))

    # Validar e corrigir: garantir que não exceda a contagem total de agentes
    if agents_per_hour_min > num_entities:
        logger.warning(f"agents_per_hour_min ({agents_per_hour_min}) excede o total de agentes ({num_entities}), corrigido")
        agents_per_hour_min = max(1, num_entities // 10)

    if agents_per_hour_max > num_entities:
        logger.warning(f"agents_per_hour_max ({agents_per_hour_max}) excede o total de agentes ({num_entities}), corrigido")
        agents_per_hour_max = max(agents_per_hour_min + 1, num_entities // 2)

    # Garantir que min < max
    if agents_per_hour_min >= agents_per_hour_max:
        agents_per_hour_min = max(1, agents_per_hour_max // 2)
        logger.warning(f"agents_per_hour_min >= max, corrigido para {agents_per_hour_min}")

    return TimeSimulationConfig(
        total_simulation_hours=result.get("total_simulation_hours", 72),
        minutes_per_round=result.get("minutes_per_round", 60),
        agents_per_hour_min=agents_per_hour_min,
        agents_per_hour_max=agents_per_hour_max,
        peak_hours=result.get("peak_hours", [19, 20, 21, 22]),
        off_peak_hours=result.get("off_peak_hours", [0, 1, 2, 3, 4, 5]),
        off_peak_activity_multiplier=0.05,
        morning_activity_multiplier=0.4,
        work_activity_multiplier=0.7,
        peak_activity_multiplier=1.5
    )

Configuração de Tempo Padrão (Fuso Horário Chinês)

def _get_default_time_config(self, num_entities: int) -> Dict[str, Any]:
    return {
        "total_simulation_hours": 72,
        "minutes_per_round": 60,  # 1 hora por rodada
        "agents_per_hour_min": max(1, num_entities // 15),
        "agents_per_hour_max": max(5, num_entities // 5),
        "peak_hours": [19, 20, 21, 22],
        "off_peak_hours": [0, 1, 2, 3, 4, 5],
        "morning_hours": [6, 7, 8],
        "work_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
        "reasoning": "Usando configuração padrão de fuso horário chinês"
    }

Geração da Configuração de Eventos

A configuração de eventos define postagens iniciais, tópicos quentes e direção narrativa:

def _generate_event_config(
    self,
    context: str,
    simulation_requirement: str,
    entities: List[EntityNode]
) -> Dict[str, Any]:

    # Obter tipos de entidade disponíveis para referência do LLM
    entity_types_available = list(set(
        e.get_entity_type() or "Unknown" for e in entities
    ))

    # Mostrar exemplos por tipo
    type_examples = {}
    for e in entities:
        etype = e.get_entity_type() or "Unknown"
        if etype not in type_examples:
            type_examples[etype] = []
        if len(type_examples[etype]) < 3:
            type_examples[etype].append(e.name)

    type_info = "\n".join([
        f"- {t}: {', '.join(examples)}"
        for t, examples in type_examples.items()
    ])

    context_truncated = context[:self.EVENT_CONFIG_CONTEXT_LENGTH]

    prompt = f"""Com base nos seguintes requisitos de simulação, gere a configuração de eventos.

Requisito de Simulação: {simulation_requirement}

{context_truncated}

## Tipos de Entidade Disponíveis e Exemplos
{type_info}

## Tarefa
Gere o JSON da configuração de eventos:
- Extraia palavras-chave de tópicos quentes
- Descreva a direção narrativa
- Projete postagens iniciais, **cada postagem deve especificar poster_type**

**Importante**: poster_type deve ser selecionado entre os "Tipos de Entidade Disponíveis" acima, para que as postagens iniciais possam ser atribuídas aos agentes apropriados.

Por exemplo: Declarações oficiais devem ser postadas por tipos Oficial/Universidade, notícias por Veículos de Mídia, opiniões de estudantes por Estudantes.

Retorne o formato JSON (sem markdown):
{{
    "hot_topics": ["palavra_chave1", "palavra_chave2", ...],
    "narrative_direction": "<descrição da direção narrativa>",
    "initial_posts": [
        {{"content": "Conteúdo da postagem", "poster_type": "Tipo de Entidade (deve corresponder aos tipos disponíveis)"}},
        ...
    ],
    "reasoning": "<breve explicação>"
}}"""

    system_prompt = "Você é um especialista em análise de opinião. Retorne o formato JSON puro."

    try:
        return self._call_llm_with_retry(prompt, system_prompt)
    except Exception as e:
        logger.warning(f"Falha na geração de LLM da configuração de eventos: {e}, usando padrão")
        return {
            "hot_topics": [],
            "narrative_direction": "",
            "initial_posts": [],
            "reasoning": "Usando configuração padrão"
        }

Atribuindo Publicadores de Postagens Iniciais

Após gerar as postagens iniciais, associe-as aos agentes reais:

def _assign_initial_post_agents(
    self,
    event_config: EventConfig,
    agent_configs: List[AgentActivityConfig]
) -> EventConfig:

    if not event_config.initial_posts:
        return event_config

    # Indexar agentes por tipo
    agents_by_type: Dict[str, List[AgentActivityConfig]] = {}
    for agent in agent_configs:
        etype = agent.entity_type.lower()
        if etype not in agents_by_type:
            agents_by_type[etype] = []
        agents_by_type[etype].append(agent)

    # Mapeamento de apelidos de tipo (lida com variações LLM)
    type_aliases = {
        "official": ["official", "university", "governmentagency", "government"],
        "university": ["university", "official"],
        "mediaoutlet": ["mediaoutlet", "media"],
        "student": ["student", "person"],
        "professor": ["professor", "expert", "teacher"],
        "alumni": ["alumni", "person"],
        "organization": ["organization", "ngo", "company", "group"],
        "person": ["person", "student", "alumni"],
    }

    # Rastrear índices usados para evitar reutilizar o mesmo agente
    used_indices: Dict[str, int] = {}

    updated_posts = []
    for post in event_config.initial_posts:
        poster_type = post.get("poster_type", "").lower()
        content = post.get("content", "")

        matched_agent_id = None

        # 1. Correspondência direta
        if poster_type in agents_by_type:
            agents = agents_by_type[poster_type]
            idx = used_indices.get(poster_type, 0) % len(agents)
            matched_agent_id = agents[idx].agent_id
            used_indices[poster_type] = idx + 1
        else:
            # 2. Correspondência por apelido
            for alias_key, aliases in type_aliases.items():
                if poster_type in aliases or alias_key == poster_type:
                    for alias in aliases:
                        if alias in agents_by_type:
                            agents = agents_by_type[alias]
                            idx = used_indices.get(alias, 0) % len(agents)
                            matched_agent_id = agents[idx].agent_id
                            used_indices[alias] = idx + 1
                            break
                    if matched_agent_id is not None:
                        break

        # 3. Fallback: usar agente de maior influência
        if matched_agent_id is None:
            logger.warning(f"Nenhum agente correspondente para o tipo '{poster_type}', usando agente de maior influência")
            if agent_configs:
                sorted_agents = sorted(agent_configs, key=lambda a: a.influence_weight, reverse=True)
                matched_agent_id = sorted_agents[0].agent_id
            else:
                matched_agent_id = 0

        updated_posts.append({
            "content": content,
            "poster_type": post.get("poster_type", "Desconhecido"),
            "poster_agent_id": matched_agent_id
        })

        logger.info(f"Atribuição de postagem inicial: poster_type='{poster_type}' -> agent_id={matched_agent_id}")

    event_config.initial_posts = updated_posts
    return event_config

Geração da Configuração de Agentes em Lote

Gerar configurações para centenas de agentes de uma vez excederia os limites de token. O sistema processa em lotes de 15:

def _generate_agent_configs_batch(
    self,
    context: str,
    entities: List[EntityNode],
    start_idx: int,
    simulation_requirement: str
) -> List[AgentActivityConfig]:

    # Construir informações da entidade com comprimento de sumário limitado
    entity_list = []
    summary_len = self.AGENT_SUMMARY_LENGTH
    for i, e in enumerate(entities):
        entity_list.append({
            "agent_id": start_idx + i,
            "entity_name": e.name,
            "entity_type": e.get_entity_type() or "Unknown",
            "summary": e.summary[:summary_len] if e.summary else ""
        })

    prompt = f"""Com base nas seguintes informações, gere a configuração de atividade de mídia social para cada entidade.

Requisito de Simulação: {simulation_requirement}

## Lista de Entidades
```json
{json.dumps(entity_list, ensure_ascii=False, indent=2)}

Tarefa

Gere a configuração de atividade para cada entidade. Observação:

system_prompt = "Você é um especialista em análise de comportamento de mídia social. Retorne o formato JSON puro."

try:
    result = self._call_llm_with_retry(prompt, system_prompt)
    llm_configs = {cfg["agent_id"]: cfg for cfg in result.get("agent_configs", [])}
except Exception as e:
    logger.warning(f"Falha na geração de LLM do lote de configuração de agente: {e}, usando geração baseada em regras")
    llm_configs = {}

# Construir objetos AgentActivityConfig
configs = []
for i, entity in enumerate(entities):
    agent_id = start_idx + i
    cfg = llm_configs.get(agent_id, {})

    # Usar fallback baseado em regras se o LLM falhar
    if not cfg:
        cfg = self._generate_agent_config_by_rule(entity)

    config = AgentActivityConfig(
        agent_id=agent_id,
        entity_uuid=entity.uuid,
        entity_name=entity.name,
        entity_type=entity.get_entity_type() or "Unknown",
        activity_level=cfg.get("activity_level", 0.5),
        posts_per_hour=cfg.get("posts_per_hour", 0.5),
        comments_per_hour=cfg.get("comments_per_hour", 1.0),
        active_hours=cfg.get("active_hours", list(range(9, 23))),
        response_delay_min=cfg.get("response_delay_min", 5),
        response_delay_max=cfg.get("response_delay_max", 60),
        sentiment_bias=cfg.get("sentiment_bias", 0.0),
        stance=cfg.get("stance", "neutral"),
        influence_weight=cfg.get("influence_weight", 1.0)
    )
    configs.append(config)

return configs

### Configurações de Fallback Baseadas em Regras

Quando o LLM falha, use padrões predefinidos:

```python
def _generate_agent_config_by_rule(self, entity: EntityNode) -> Dict[str, Any]:
    entity_type = (entity.get_entity_type() or "Unknown").lower()

    if entity_type in ["university", "governmentagency", "ngo"]:
        # Instituição oficial: horário de trabalho, baixa frequência, alta influência
        return {
            "activity_level": 0.2,
            "posts_per_hour": 0.1,
            "comments_per_hour": 0.05,
            "active_hours": list(range(9, 18)),  # 9:00-17:59
            "response_delay_min": 60,
            "response_delay_max": 240,
            "sentiment_bias": 0.0,
            "stance": "neutral",
            "influence_weight": 3.0
        }

    elif entity_type in ["mediaoutlet"]:
        # Mídia: atividade durante todo o dia, frequência moderada, alta influência
        return {
            "activity_level": 0.5,
            "posts_per_hour": 0.8,
            "comments_per_hour": 0.3,
            "active_hours": list(range(7, 24)),  # 7:00-23:59
            "response_delay_min": 5,
            "response_delay_max": 30,
            "sentiment_bias": 0.0,
            "stance": "observer",
            "influence_weight": 2.5
        }

    elif entity_type in ["professor", "expert", "official"]:
        # Especialista/Professor: trabalho + noite, frequência moderada
        return {
            "activity_level": 0.4,
            "posts_per_hour": 0.3,
            "comments_per_hour": 0.5,
            "active_hours": list(range(8, 22)),  # 8:00-21:59
            "response_delay_min": 15,
            "response_delay_max": 90,
            "sentiment_bias": 0.0,
            "stance": "neutral",
            "influence_weight": 2.0
        }

    elif entity_type in ["student"]:
        # Estudante: pico noturno, alta frequência
        return {
            "activity_level": 0.8,
            "posts_per_hour": 0.6,
            "comments_per_hour": 1.5,
            "active_hours": [8, 9, 10, 11, 12, 13, 18, 19, 20, 21, 22, 23],
            "response_delay_min": 1,
            "response_delay_max": 15,
            "sentiment_bias": 0.0,
            "stance": "neutral",
            "influence_weight": 0.8
        }

    elif entity_type in ["alumni"]:
        # Ex-aluno: foco na noite
        return {
            "activity_level": 0.6,
            "posts_per_hour": 0.4,
            "comments_per_hour": 0.8,
            "active_hours": [12, 13, 19, 20, 21, 22, 23],  # Almoço + noite
            "response_delay_min": 5,
            "response_delay_max": 30,
            "sentiment_bias": 0.0,
            "stance": "neutral",
            "influence_weight": 1.0
        }

    else:
        # Pessoa padrão: pico noturno
        return {
            "activity_level": 0.7,
            "posts_per_hour": 0.5,
            "comments_per_hour": 1.2,
            "active_hours": [9, 10, 11, 12, 13, 18, 19, 20, 21, 22, 23],
            "response_delay_min": 2,
            "response_delay_max": 20,
            "sentiment_bias": 0.0,
            "stance": "neutral",
            "influence_weight": 1.0
        }

Chamada LLM com Tentativa e Reparo de JSON

Chamadas LLM falham. Saídas são truncadas. JSON quebra. O sistema lida com tudo isso:

def _call_llm_with_retry(self, prompt: str, system_prompt: str) -> Dict[str, Any]:
    import re

    max_attempts = 3
    last_error = None

    for attempt in range(max_attempts):
        try:
            response = self.client.chat.completions.create(
                model=self.model_name,
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": prompt}
                ],
                response_format={"type": "json_object"},
                temperature=0.7 - (attempt * 0.1)  # Diminuir temperatura na tentativa
            )

            content = response.choices[0].message.content
            finish_reason = response.choices[0].finish_reason

            # Verificar se foi truncado
            if finish_reason == 'length':
                logger.warning(f"Saída LLM truncada (tentativa {attempt+1})")
                content = self._fix_truncated_json(content)

            # Tentar analisar JSON
            try:
                return json.loads(content)
            except json.JSONDecodeError as e:
                logger.warning(f"Falha na análise de JSON (tentativa {attempt+1}): {str(e)[:80]}")

                # Tentar reparar JSON
                fixed = self._try_fix_config_json(content)
                if fixed:
                    return fixed

                last_error = e

        except Exception as e:
            logger.warning(f"Falha na chamada LLM (tentativa {attempt+1}): {str(e)[:80]}")
            last_error = e
            import time
            time.sleep(2 * (attempt + 1))

    raise last_error or Exception("Falha na chamada LLM")

Corrigindo JSON Truncado

def _fix_truncated_json(self, content: str) -> str:
    content = content.strip()

    # Contar chaves não fechadas
    open_braces = content.count('{') - content.count('}')
    open_brackets = content.count('[') - content.count(']')

    # Verificar string não fechada
    if content and content[-1] not in '",}]':
        content += '"'

    # Fechar chaves
    content += ']' * open_brackets
    content += '}' * open_braces

    return content

Reparo Avançado de JSON

def _try_fix_config_json(self, content: str) -> Optional[Dict[str, Any]]:
    import re

    # Corrigir truncamento
    content = self._fix_truncated_json(content)

    # Extrair porção JSON
    json_match = re.search(r'\{[\s\S]*\}', content)
    if json_match:
        json_str = json_match.group()

        # Remover quebras de linha em strings
        def fix_string(match):
            s = match.group(0)
            s = s.replace('\n', ' ').replace('\r', ' ')
            s = re.sub(r'\s+', ' ', s)
            return s

        json_str = re.sub(r'"[^"\\]*(?:\\.[^"\\]*)*"', fix_string, json_str)

        try:
            return json.loads(json_str)
        except:
            # Tentar remover caracteres de controle
            json_str = re.sub(r'[\x00-\x1f\x7f-\x9f]', ' ', json_str)
            json_str = re.sub(r'\s+', ' ', json_str)
            try:
                return json.loads(json_str)
            except:
                pass

    return None

Estruturas de Dados da Configuração

Configuração de Atividade do Agente

@dataclass
class AgentActivityConfig:
    """Configuração de atividade de agente único"""
    agent_id: int
    entity_uuid: str
    entity_name: str
    entity_type: str

    # Nível de atividade (0.0-1.0)
    activity_level: float = 0.5

    # Frequência de postagem (por hora)
    posts_per_hour: float = 1.0
    comments_per_hour: float = 2.0

    # Horas ativas (formato 24 horas, 0-23)
    active_hours: List[int] = field(default_factory=lambda: list(range(8, 23)))

    # Velocidade de resposta (atraso de reação em minutos simulados)
    response_delay_min: int = 5
    response_delay_max: int = 60

    # Tendência de sentimento (-1.0 a 1.0, negativo a positivo)
    sentiment_bias: float = 0.0

    # Postura sobre tópicos específicos
    stance: str = "neutral"  # solidário, opositor, neutro, observador

    # Peso de influência (afeta a probabilidade de ser visto)
    influence_weight: float = 1.0

Configuração de Simulação de Tempo

@dataclass
class TimeSimulationConfig:
    """Configuração de simulação de tempo (fuso horário chinês)"""
    total_simulation_hours: int = 72  # Padrão 72 horas (3 dias)
    minutes_per_round: int = 60  # 60 minutos por rodada

    # Agentes ativados por hora
    agents_per_hour_min: int = 5
    agents_per_hour_max: int = 20

    # Horas de pico (noite 19-22, mais ativo na China)
    peak_hours: List[int] = field(default_factory=lambda: [19, 20, 21, 22])
    peak_activity_multiplier: float = 1.5

    # Horas fora do pico (madrugada 0-5, quase nenhuma atividade)
    off_peak_hours: List[int] = field(default_factory=lambda: [0, 1, 2, 3, 4, 5])
    off_peak_activity_multiplier: float = 0.05

    # Horas da manhã
    morning_hours: List[int] = field(default_factory=lambda: [6, 7, 8])
    morning_activity_multiplier: float = 0.4

    # Horário de trabalho
    work_hours: List[int] = field(default_factory=lambda: [9, 10, 11, 12, 13, 14, 15, 16, 17, 18])
    work_activity_multiplier: float = 0.7

Parâmetros Completos da Simulação

@dataclass
class SimulationParameters:
    """Configuração completa de parâmetros de simulação"""
    simulation_id: str
    project_id: str
    graph_id: str
    simulation_requirement: str

    time_config: TimeSimulationConfig = field(default_factory=TimeSimulationConfig)
    agent_configs: List[AgentActivityConfig] = field(default_factory=list)
    event_config: EventConfig = field(default_factory=EventConfig)
    twitter_config: Optional[PlatformConfig] = None
    reddit_config: Optional[PlatformConfig] = None

    llm_model: str = ""
    llm_base_url: str = ""

    generated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    generation_reasoning: str = ""

    def to_dict(self) -> Dict[str, Any]:
        time_dict = asdict(self.time_config)
        return {
            "simulation_id": self.simulation_id,
            "project_id": self.project_id,
            "graph_id": self.graph_id,
            "simulation_requirement": self.simulation_requirement,
            "time_config": time_dict,
            "agent_configs": [asdict(a) for a in self.agent_configs],
            "event_config": asdict(self.event_config),
            "twitter_config": asdict(self.twitter_config) if self.twitter_config else None,
            "reddit_config": asdict(self.reddit_config) if self.reddit_config else None,
            "llm_model": self.llm_model,
            "llm_base_url": self.llm_base_url,
            "generated_at": self.generated_at,
            "generation_reasoning": self.generation_reasoning,
        }

Tabela Resumo: Padrões de Tipo de Agente

Tipo de Agente Atividade Horas Ativas Postagens/Hora Comentários/Hora Resposta (min) Influência
University 0.2 9-17 0.1 0.05 60-240 3.0
GovernmentAgency 0.2 9-17 0.1 0.05 60-240 3.0
MediaOutlet 0.5 7-23 0.8 0.3 5-30 2.5
Professor 0.4 8-21 0.3 0.5 15-90 2.0
Student 0.8 8-12, 18-23 0.6 1.5 1-15 0.8
Alumni 0.6 12-13, 19-23 0.4 0.8 5-30 1.0
Person (default) 0.7 9-13, 18-23 0.5 1.2 2-20 1.0

Conclusão

A geração de configuração alimentada por LLM requer um tratamento cuidadoso de:

button

Pratique o design de API no Apidog

Descubra uma forma mais fácil de construir e usar APIs