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.

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.


⭐ 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:
    response = client.send_prompt("Por que o céu é azul?")
    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}")
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.6.tar.gz (37.9 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.6-py3-none-any.whl (43.7 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for forgebase-0.2.6.tar.gz
Algorithm Hash digest
SHA256 05a6605afd55f87f38eb37919122b6b04e980135f0d215cd522075d81885b308
MD5 437689b7a56859e110a294b48718e2a8
BLAKE2b-256 8f7c83b0b88e75e3cb19850ce5597cd39ade553e023c4378dd7ace333df93ca5

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for forgebase-0.2.6-py3-none-any.whl
Algorithm Hash digest
SHA256 00645af10508f578621dc71a911277560be4df0fcee9221fa9c3715287695cac
MD5 77f4b1f6aea217f2847ae2bb7494dd58
BLAKE2b-256 c0cbe6a02fae5b8140571583bc451da1de13d536b8e1c52feab1c9a3eee0cfe7

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