Skip to main content

Forgebase core libraries (utils, framework e cliente LLM)

Project description

Forgebase

Forgebase reúne três blocos principais usados neste monorepo:

  • forge_utils – logging estruturado e utilidades de paths/configuração.
  • forgebase – framework MVC-C enxuto baseado em Pydantic v2 (modelos, commands, controllers, views e persistência).
  • llm_client – cliente agnóstico para a OpenAI Responses API com suporte a streaming, tool calling e replays offline.

A biblioteca está disponível em PyPI e também no TestPyPI para validação prévia.


⚡ Quick Start — System Instructions (v0.2.11+)

Agora o Forgebase expõe o parâmetro instructions (mensagem de sistema da OpenAI Responses API). Não confunda com o prompt: as instructions definem o comportamento do modelo e acompanham as chamadas (incluindo follow‑ups de tool calling).

Exemplos diretos:

from forgebase import LLMOpenAIClient, LLMClientFactory

# Cliente direto
client = LLMOpenAIClient(api_key="sk-...", model="gpt-4o")
resp = client.send_prompt(
    "Explique idempotência.",
    instructions="Você é um revisor sênior. Responda em uma frase."
)

# Provider via Factory (recomendado)
provider = LLMClientFactory.create("openai", api_key="sk-...")
out = provider.send_message(
    "Liste 3 boas práticas de logging.",
    instructions="Responda de forma objetiva, em tópicos."
)

# Streaming com instructions
for delta in provider.send_stream(
    "Resuma o papel do backoff exponencial.",
    instructions="Resuma em até 2 frases."
):
    print(delta, end="")

Detalhes completos e observações em “Usando system instructions (Responses API)”, abaixo.

Instalação rápida

pip install forgebase

Crie um ambiente virtual limpo antes de instalar (python -m venv .venv && source .venv/bin/activate).


⭐ Novo na v0.2.2: Streaming + Tools (Responses API)

Esta versão foca em robustez do streaming com tool calling sobre a OpenAI Responses API.

  • Headers de streaming: adicionamos Accept: text/event-stream e melhoramos timeouts (SSE sem read timeout) no cliente HTTP.
  • Normalização de tools: agora aceitamos tanto o formato top‑level da Responses API ({\"type\":\"function\",\"name\":...}) quanto o formato “aninhado” comum no Chat Completions ({\"type\":\"function\",\"function\": {\"name\":..., \"parameters\":...}}). O cliente normaliza para o contrato oficial da Responses API antes de enviar.
  • Sem fallback para Chat Completions: não utilizamos Chat Completions; todo o fluxo permanece na Responses API.
  • Header opcional de beta: é possível habilitar OpenAI-Beta: responses=v1 via OPENAI_BETA_RESPONSES=1 caso seu ambiente exija este gate.

Documentação relacionada: docs/api/openai_responses.md, docs/api/openai_responses_tool_calling.md e docs/api/streaming_tools_guide.md.

Impacto: corrige erros intermitentes 400 observados durante streaming + tools e melhora compatibilidade com formatos de tools enviados por clientes legados.


⭐ Novo na v0.2.3: Robustez de Streaming + Follow‑up de Tools

Esta versão refina o fluxo de streaming e o follow‑up de ferramentas:

  • Tratamento de erros em SSE:
    • Em 4xx, o cliente drena o corpo e lança APIResponseError com a mensagem do provider (elimina o erro "without read()" e evita conexões em mau estado).
  • Follow‑ups de tool calling (Responses API):
    • O follow‑up inclui o descritor bruto tool_use (além de function_call) antes do respectivo *_output, alinhando com variantes da Responses API em cenários multi‑turn.
  • Testes e documentação:
    • Novos testes de streaming e follow‑up.
    • Guia: docs/api/streaming_tools_guide.md.

Compatibilidade: sem mudanças de interface — sua integração atual continua funcionando.


🧪 Desenvolvimento (v0.2.6+)

Esta seção resume como depurar e validar cenários de streaming + tools no ForgeBase.

  • Defaults a partir da v0.2.5/0.2.6

    • Follow‑up em streaming: outputs-only habilitado (não reenvia descriptors brutos)
    • tool_choice no follow‑up: none
    • name incluído nos outputs
  • Flags de compatibilidade (override)

    • FORGEBASE_FOLLOWUP_OUTPUTS_ONLY=0|1
    • FORGEBASE_FOLLOWUP_TOOL_CHOICE=none|auto|required
    • FORGEBASE_OUTPUT_NAME=0|1
    • FORGEBASE_OUTPUT_KIND=function_result|tool_result
  • Diagnóstico (sem vazar conteúdo)

    • FORGEBASE_DEBUG_FOLLOWUP=1 emite em nível INFO a linha: [follow-up] prev=... outputs=N types=[...] call_ids=[...] unique=N
    • Recomendações:
      • Habilitar DEBUG no logger durante testes locais
      • Usar hooks do provider para imprimir o payload do follow‑up quando necessário
  • Reconciliação de call_id (v0.2.8+)

    • IDs call_* coletados dos eventos de streaming e, se preciso, via input_items
    • Substituem IDs alternativos ao montar os outputs no follow‑up
    • Buffer de outputs particionado por previous_response_id
  • Testes

    • Rodar toda a suíte: pytest -q
    • Casos relevantes:
      • tests/test_streaming_error_handling.py
      • tests/test_streaming_tool_use_followup.py
      • tests/test_followup_flags_overrides.py
    • O guia docs/api/streaming_tools_guide.md reforça cenários, flags e troubleshooting

⭐ Novo na v0.2.1: LLMClientFactory

A partir da v0.2.1, use LLMClientFactory para criar providers LLM de forma desacoplada:

from forgebase import LLMClientFactory, Tool

# Opção 1: Criação manual
provider = LLMClientFactory.create("openai", api_key="sk-...", timeout=60)

# Opção 2: Auto-configuração via variável de ambiente (OPENAI_API_KEY)
provider = LLMClientFactory.create_from_env("openai")

# Usar o provider
response = provider.send_message("Hello, world!")
print(response)

# Trocar provider transparentemente (quando disponível)
# provider = LLMClientFactory.create("llama")  # Mesma interface!

Por que usar a factory?

  • Desacoplamento: Código não depende de implementação específica (OpenAI, Llama, etc.)
  • Extensibilidade: Trocar provider mudando apenas 1 string
  • Testabilidade: Mock de interface ILLMClient em vez de classe concreta
  • Preparado para o futuro: Suporte a múltiplos providers sem breaking changes

⚠️ Deprecação: OpenAIProvider ainda funciona na v0.2.1 (backward compatible), mas recomendamos migrar para LLMClientFactory. Veja exemplos abaixo.


Pacote por pacote

forge_utils

  • forge_utils.log_service.LogService configura logging com console/arquivo rotativo e filtros.
  • forge_utils.log_service.logger é a instância global pronta para uso.
  • forge_utils.paths oferece helpers (build_app_paths, ensure_dirs) para organizar arquivos de configuração, histórico e cache.

forgebase

Reexporta o framework MVC-C básico. Os pontos de entrada mais usados são:

  • CustomBaseModel / BaseModelData: modelos Pydantic com suporte a dirty tracking e observers.
  • CustomCommandBase + guard_errors: encapsulam regras de negócio, padronizando o tratamento de exceções (CommandException).
  • CustomBaseController / CustomBaseView: composição MVC-C mínima.
  • PersistenceFactory + JsonPersistence: persistência compatível com Pydantic v2.

Todos estes nomes estão disponíveis diretamente com from forgebase import ....

llm_client

O cliente LLM também é reexportado por forgebase para facilitar o consumo:

API Pública (v0.2.1+):

  • LLMClientFactory: factory para criar providers sem conhecer implementação (✅ use este!)
  • ILLMClient: interface Protocol para type hints e extensibilidade
  • Tool: modelo Pydantic que representa o schema JSON das ferramentas
  • APIResponseError / ConfigurationError: exceções específicas do cliente
  • ContentPart, OutputMessage, ResponseResult, TextFormat, TextOutputConfig: modelos retornados pelo Responses API

Legacy (deprecated - mantido para compatibilidade):

  • OpenAIProvider: ⚠️ usar LLMClientFactory.create("openai") em vez disso
  • LLMOpenAIClient: ⚠️ interno - não usar diretamente

Multi-provider ready: A arquitetura está preparada para múltiplos providers (Llama, Anthropic, OpenRouter) sem breaking changes no código cliente. Use LLMClientFactory para garantir compatibilidade futura.

Guia rápido de uso

Core MVC-C

from forgebase import CustomBaseModel, CustomCommandBase, JsonPersistence, guard_errors

class User(CustomBaseModel):
    id: int
    name: str

class CreateUserCommand(CustomCommandBase):
    @guard_errors
    def execute(self, payload: dict) -> User:
        model = User(**payload)
        # ... lógica de negócio ...
        return model

storage = JsonPersistence("users.json")

Cliente LLM – configuração e chamadas

Crie um arquivo .env na raiz do projeto (ou exporte no shell) com:

OPENAI_API_KEY=sk-...

Todos os exemplos abaixo carregam essa chave automaticamente via python-dotenv.

Chamada síncrona

import os
from dotenv import load_dotenv
from forgebase import APIResponseError, ConfigurationError, LLMOpenAIClient

load_dotenv()
client = LLMOpenAIClient(api_key=os.environ["OPENAI_API_KEY"], model="gpt-4o-mini")

try:
    # instructions: mensagem de sistema (não confundir com o prompt)
    response = client.send_prompt(
        "Por que o céu é azul?",
        instructions="Você é um assistente técnico. Responda de forma objetiva."
    )
    answer = "\n".join(
        part.text.strip()
        for item in response.output
        for part in getattr(item, "content", [item])
        if getattr(part, "text", None)
    )
    print(answer)
except (APIResponseError, ConfigurationError) as exc:
    print(f"Falha na chamada: {exc}")

Usando system instructions (Responses API)

"Instructions" são a mensagem de sistema da Responses API. Elas definem o comportamento do modelo, e não devem ser confundidas com o prompt do usuário. No Forgebase, você pode passá-las explicitamente nas chamadas do cliente e do provider. Se omitidas, a API do provider usará as instruções padrão do servidor.

Exemplos:

from forgebase import LLMOpenAIClient, OpenAIProvider, LLMClientFactory

# 1) Cliente direto
client = LLMOpenAIClient(api_key=os.environ["OPENAI_API_KEY"], model="gpt-4o")
resp = client.send_prompt(
    "Liste 3 boas práticas para logs.",
    instructions=(
        "Você é um revisor sênior. Use linguagem clara, sem jargões desnecessários."
    ),
)

# 2) Provider (recomendado via Factory)
provider = LLMClientFactory.create_from_env("openai")
out = provider.send_message(
    "Explique o conceito de idempotência.",
    instructions="Padrão: respostas curtas, com um exemplo prático."
)

# 3) Streaming com instructions
for chunk in provider.send_stream(
    "Resuma o papel do backoff exponencial.",
    instructions="Resuma em até 2 frases."
):
    print(chunk, end="")

Observações:

  • Instructions são propagadas em múltiplas rodadas durante tool calling (follow‑ups).
  • Instructions não substituem o prompt; são complementares e tratadas como “system message”.
  • O campo é enviado como ResponseRequest.instructions para a Responses API.
Migrando do uso direto da OpenAI API

Antes utilizávamos o SDK oficial diretamente. Agora a camada LLMOpenAIClient traz timeout, hooks e tool calling prontos, além de facilitar a troca de provider.

# Legado: uso direto do SDK da OpenAI
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
resp = client.responses.create(model="gpt-4o-mini", input="Por que o céu é azul?")
print(resp.output[0].content[0].text)
# Atual: via LLMOpenAIClient encapsulado
from forgebase import LLMOpenAIClient

client = LLMOpenAIClient(api_key=os.environ["OPENAI_API_KEY"], model="gpt-4o-mini")
resp = client.send_prompt("Por que o céu é azul?")
print(resp.output[0].content[0].text)

Configurando timeout (⭐ novo na v0.2.0)

Por padrão, as chamadas HTTP usam timeout de 120 segundos. Você pode customizar:

# Timeout de 45 segundos
client = LLMOpenAIClient(
    api_key=os.environ["OPENAI_API_KEY"],
    timeout=45  # segundos
)

# Ou via OpenAIProvider
from forgebase import OpenAIProvider

provider = OpenAIProvider(timeout=45)
provider.set_api_key(os.environ["OPENAI_API_KEY"])
result = provider.send_message("Hello")

Importante: O timeout se aplica ao tempo total incluindo retries. Com timeout=45 e max_tries=4, o sistema não ultrapassará 45 segundos mesmo com múltiplas tentativas.

Chamada com streaming

import os
from dotenv import load_dotenv
from forgebase import LLMOpenAIClient

load_dotenv()
client = LLMOpenAIClient(api_key=os.environ["OPENAI_API_KEY"], model="gpt-4o-mini")

stream = client.send_prompt("Conte uma história curta sobre um robô e uma criança.", streamed=True)
for delta in stream:
    print(delta, end="", flush=True)
print()

Chamada multimodal (imagem + áudio)

import os
from dotenv import load_dotenv
from forgebase import LLMOpenAIClient

load_dotenv()
client = LLMOpenAIClient(api_key=os.environ["OPENAI_API_KEY"], model="gpt-4o")

response = client.send_prompt(
    "Descreva a imagem e comente o áudio anexado.",
    images=["https://upload.wikimedia.org/wikipedia/commons/9/99/Colorful_sunset.jpg"],
    audio={"base64": "ZGF0YQ==", "mime_type": "audio/wav"},
)
print(response)

Listar modelos disponíveis

import os
from dotenv import load_dotenv
from forgebase import LLMOpenAIClient

load_dotenv()
client = LLMOpenAIClient(api_key=os.environ["OPENAI_API_KEY"])
models = client.list_models()
print(models["data"][0])

Tool calling síncrono (v0.2.1+)

import os
from dotenv import load_dotenv
from forgebase import LLMClientFactory, Tool

load_dotenv()

# ✅ Novo: usar factory
provider = LLMClientFactory.create_from_env("openai")

# 1) Descreva a ferramenta com JSON Schema compatível com a Responses API
tool = Tool(
    type="function",
    name="say_hello",
    parameters={"type": "object", "properties": {"who": {"type": "string"}}, "required": ["who"]},
)
# 2) Conte ao provider quais ferramentas estão disponíveis
provider.configure_tools([tool], tool_choice="required")
# 3) Registre o handler Python que será chamado quando o modelo disparar a ferramenta
provider.register_tool("say_hello", lambda args: f"Olá, {args['who']}!")

# 4) Use send_message normalmente: o provider executa as tools e devolve o texto final
print(provider.send_message("Cumprimente Forgebase."))
📜 Código antigo (deprecated - clique para expandir)
# ⚠️ Deprecated: usando OpenAIProvider diretamente
from forgebase import OpenAIProvider, Tool

provider = OpenAIProvider()
provider.set_api_key(os.environ["OPENAI_API_KEY"])
# ... resto do código igual
Tool calling com orquestração automática

Providers criados pela factory encapsulam todos os ciclos de tool calling da Responses API. Basta definir o schema das ferramentas, registrar os handlers e chamar send_message:

from forgebase import LLMClientFactory, Tool

provider = LLMClientFactory.create("openai", api_key=os.environ["OPENAI_API_KEY"], timeout=60)

weather_tool = Tool(
    type="function",
    name="get_weather",
    description="Retorna a temperatura atual para uma cidade brasileira.",
    parameters={
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
)

provider.configure_tools([weather_tool], tool_choice="required")

def get_weather(args: dict[str, str]) -> str:
    city = args["city"]
    return f"{city}, 30 graus."

provider.register_tool("get_weather", get_weather)

print(provider.send_message("Qual a temperatura de hoje em São Paulo?"))

Internamente o provider:

  • Executa múltiplas rodadas de tool calling até chegar no texto final — o mesmo fluxo usado nos testes test_demo_tool_call_api_forced e test_openai_provider_hooks_with_tools.
  • Gerencia tool_choice, IDs de chamadas e payloads function_call_output / custom_tool_call_output, montando o input_override correto para cada rodada, como coberto em tests/test_openai_tool_calling.py.
  • Expõe hooks (before_tool_call, after_tool_call, tool_error, etc.) que você pode monitorar para métricas e logs.
  • Funciona tanto com chamadas síncronas (send_message) quanto streaming (send_stream) sem alterar o código dos handlers.

Passo a passo resumido

  • Configure o provider com set_api_key.
  • Descreva as ferramentas com forgebase.Tool em configure_tools.
  • Registre cada handler com register_tool.
  • Dispare send_message (ou send_stream) — o provider executa as ferramentas e devolve apenas o texto final.
  • Opcional: adicione hooks (register_hook) para inspecionar before_tool_call, after_tool_call ou erros.

Para validar fluxos sem acessar a API real, use LLMOpenAIClient com uma resposta fake (response=<objeto compatível>) ou rode python -m llm_client.demo_tool_call_api que inclui um modo offline utilizando as mesmas rotinas de orquestração (vide tests/test_demo_tool_call_api.py).

Tool calling com streaming

import os
from dotenv import load_dotenv
from forgebase import OpenAIProvider, Tool

load_dotenv()
provider = OpenAIProvider()
provider.set_api_key(os.environ["OPENAI_API_KEY"])

tool = Tool(
    type="function",
    name="summarize_numbers",
    parameters={"type": "object", "properties": {"nums": {"type": "array", "items": {"type": "number"}}}},
)
provider.configure_tools([tool], tool_choice="auto")
provider.register_tool("summarize_numbers", lambda args: sum(args.get("nums", [])))

for chunk in provider.send_stream("Considere os números 2, 4, 6 e mostre a soma."):
    print(chunk, end="", flush=True)
print()

# Verifique tests/test_openai_tool_calling.py::test_streaming_tool_call_flow
# para um cenário completo de múltiplas rodadas no modo streaming.

Hooks de eventos

Tanto o LLMOpenAIClient quanto o OpenAIProvider expõem um sistema simples de hooks para instrumentar o fluxo:

from forgebase import LLMOpenAIClient, OpenAIProvider

client = LLMOpenAIClient(api_key=os.environ["OPENAI_API_KEY"])
client.register_hook("before_request", lambda ctx: print("▶", ctx["prompt"]))
client.register_hook("after_response", lambda ctx: print("◀", ctx.get("response")))

provider = OpenAIProvider(client=client)
provider.register_hook("before_tool_call", lambda ctx: print("tool", ctx["tool"]))
provider.register_hook("after_tool_call", lambda ctx: print("tool result", ctx["result"]))

Eventos disponíveis:

  • before_request, after_response, on_error, on_cache_hit (cliente LLM)
  • before_send, after_send, on_error, before_tool_call, after_tool_call, tool_error, cache_hit (provider)

Demo completo

O projeto inclui uma demo mais abrangente que cobre respostas diretas, streaming, (tool calling) e replays offline:

PYTHONPATH=shared/src:apps/llm_client/src python -m llm_client.example_full_usage

No Windows (PowerShell):

$env:PYTHONPATH = "shared/src;apps/llm_client/src"
python -m llm_client.example_full_usage

O arquivo apps/llm_client/src/llm_client/example_full_usage.py comenta cada etapa (passos para configurar OPENAI_API_KEY, habilitar tool calling real com DEMO_TOOL_CALLING=1, e como funciona o replay offline).

Configuração do ambiente

Opção 1 – Poetry (recomendada)

O pyproject.toml já descreve todos os pacotes via path. Basta executar na raiz:

poetry install
poetry run pytest -q
poetry run python -m llm_client.example_full_usage

O Poetry cria e gerencia o ambiente virtual automaticamente; não é necessário ajustar o PYTHONPATH manualmente.

Opção 2 – pip + requirements

Se preferir pip, gere um virtualenv e instale as dependências de desenvolvimento com o arquivo requirements-dev.txt gerado a partir do pyproject:

python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -r requirements-dev.txt

O arquivo pode ser sincronizado com o pyproject.toml executando python requirements-dev.py. Depois disso, exporte PYTHONPATH=shared/src:framework/src:apps/llm_client/src:cli/src (ou use python -m para os módulos) e rode pytest -q normalmente.

Desenvolvimento local

  • Testes: pytest -q (ou poetry run pytest -q).
  • Linters: ruff check . e mypy --config-file mypy.ini (com poetry run se estiver usando Poetry).
  • Build: python -m build gera wheel/sdist para publicar em TestPyPI/PyPI.

Onde continuar

  • docs/api/openai_responses.md: detalhes da Responses API e eventos de streaming.
  • docs/api/openai_responses_tool_calling.md: guia de tool calling, payloads e replays.
  • docs/api/streaming_tools_guide.md: guia extensivo de streaming + tools (SSE, timeouts, follow‑ups).
  • docs/architecture/forgebase-architecture.md: visão completa da arquitetura e fluxos.
  • docs/release-guide.md: processo recomendado de versionamento e publicação.
  • docs/testing-strategy.md: abordagem de testes e boas práticas.
  • docs/configuration.md: variáveis de ambiente e diretórios importantes.
  • docs/providers/adding-new-provider.md: instruções para suportar novos LLMs.
  • docs/cli/usage.md: comandos expostos pela CLI.
  • docs/CONTRIBUTING.md: convenções de contribuição.
  • docs/adr/README.md: decisões arquiteturais registradas.
  • docs/tech-debts/TD-001-Robustez-Tool-Calling-Responses.md: backlog de melhorias planejadas.

Sinta-se à vontade para abrir issues ou PRs com sugestões e correções.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

forgebase-0.2.12.tar.gz (44.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

forgebase-0.2.12-py3-none-any.whl (47.3 kB view details)

Uploaded Python 3

File details

Details for the file forgebase-0.2.12.tar.gz.

File metadata

  • Download URL: forgebase-0.2.12.tar.gz
  • Upload date:
  • Size: 44.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.2 CPython/3.12.3 Linux/6.6.87.2-microsoft-standard-WSL2

File hashes

Hashes for forgebase-0.2.12.tar.gz
Algorithm Hash digest
SHA256 d8f58824d33fe7c7929e4c0f1ffed7a7258543fa252ad75c73a0a481b95d7f97
MD5 27870a37c9a26bc9a77cc8ea108c7993
BLAKE2b-256 6e76c215105a050c181cbc4b22ddbeafa17199432ac3ee0fe874b355a1ddb8e7

See more details on using hashes here.

File details

Details for the file forgebase-0.2.12-py3-none-any.whl.

File metadata

  • Download URL: forgebase-0.2.12-py3-none-any.whl
  • Upload date:
  • Size: 47.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.2 CPython/3.12.3 Linux/6.6.87.2-microsoft-standard-WSL2

File hashes

Hashes for forgebase-0.2.12-py3-none-any.whl
Algorithm Hash digest
SHA256 e91e6fb0bdfc830cf1af0cba8ee3dde408af08bc0ec9824c8ea2a668c9ebb775
MD5 f7a3f450d7acd92e4c40538f4a0333f8
BLAKE2b-256 3a1a01737aa7f765a98cadccd7720546c053acea3865de33d8622cf287f5abad

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page