# 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](https://pypi.org/project/forgebase/) 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:
```python
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
```bash
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:
```python
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
```python
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
```python
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:
```python
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.
```python
# 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)
```
```python
# 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:
```python
# 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
```python
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)
```python
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
```python
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+)
```python
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."))
```
<details>
<summary>📜 Código antigo (deprecated - clique para expandir)</summary>
```python
# ⚠️ 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
```
</details>
##### 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`:
```python
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
```python
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:
```python
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:
```bash
PYTHONPATH=shared/src:apps/llm_client/src python -m llm_client.example_full_usage
```
No Windows (PowerShell):
```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:
```bash
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`:
```bash
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.
Raw data
{
"_id": null,
"home_page": "https://github.com/palhanobrazil/forgebase",
"name": "forgebase",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.11",
"maintainer_email": null,
"keywords": "forgebase, framework, llm, openai",
"author": "palhano",
"author_email": "palhanobrazil@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/6e/76/c215105a050c181cbc4b22ddbeafa17199432ac3ee0fe874b355a1ddb8e7/forgebase-0.2.12.tar.gz",
"platform": null,
"description": "# Forgebase\n\nForgebase re\u00fane tr\u00eas blocos principais usados neste monorepo:\n\n- **forge_utils** \u2013 logging estruturado e utilidades de paths/configura\u00e7\u00e3o.\n- **forgebase** \u2013 framework MVC-C enxuto baseado em Pydantic v2 (modelos, commands,\n controllers, views e persist\u00eancia).\n- **llm_client** \u2013 cliente agn\u00f3stico para a OpenAI Responses API com suporte a streaming,\n tool calling e replays offline.\n\nA biblioteca est\u00e1 dispon\u00edvel em [PyPI](https://pypi.org/project/forgebase/) e tamb\u00e9m no\nTestPyPI para valida\u00e7\u00e3o pr\u00e9via.\n\n---\n\n## \u26a1 Quick Start \u2014 System Instructions (v0.2.11+)\n\nAgora o Forgebase exp\u00f5e o par\u00e2metro `instructions` (mensagem de sistema da OpenAI\nResponses API). N\u00e3o confunda com o prompt: as instructions definem o comportamento do\nmodelo e acompanham as chamadas (incluindo follow\u2011ups de tool calling).\n\nExemplos diretos:\n\n```python\nfrom forgebase import LLMOpenAIClient, LLMClientFactory\n\n# Cliente direto\nclient = LLMOpenAIClient(api_key=\"sk-...\", model=\"gpt-4o\")\nresp = client.send_prompt(\n \"Explique idempot\u00eancia.\",\n instructions=\"Voc\u00ea \u00e9 um revisor s\u00eanior. Responda em uma frase.\"\n)\n\n# Provider via Factory (recomendado)\nprovider = LLMClientFactory.create(\"openai\", api_key=\"sk-...\")\nout = provider.send_message(\n \"Liste 3 boas pr\u00e1ticas de logging.\",\n instructions=\"Responda de forma objetiva, em t\u00f3picos.\"\n)\n\n# Streaming com instructions\nfor delta in provider.send_stream(\n \"Resuma o papel do backoff exponencial.\",\n instructions=\"Resuma em at\u00e9 2 frases.\"\n):\n print(delta, end=\"\")\n```\n\nDetalhes completos e observa\u00e7\u00f5es em \u201cUsando system instructions (Responses API)\u201d, abaixo.\n\n## Instala\u00e7\u00e3o r\u00e1pida\n\n```bash\npip install forgebase\n```\n\nCrie um ambiente virtual limpo antes de instalar (`python -m venv .venv && source .venv/bin/activate`).\n\n---\n\n## \u2b50 Novo na v0.2.2: Streaming + Tools (Responses API)\n\nEsta vers\u00e3o foca em robustez do streaming com tool calling sobre a OpenAI Responses API.\n\n- Headers de streaming: adicionamos `Accept: text/event-stream` e melhoramos timeouts (SSE sem read timeout) no cliente HTTP.\n- Normaliza\u00e7\u00e3o de tools: agora aceitamos tanto o formato top\u2011level da Responses API\n (`{\\\"type\\\":\\\"function\\\",\\\"name\\\":...}`) quanto o formato \u201caninhado\u201d comum no Chat Completions\n (`{\\\"type\\\":\\\"function\\\",\\\"function\\\": {\\\"name\\\":..., \\\"parameters\\\":...}}`). O cliente normaliza para o contrato oficial da Responses API antes de enviar.\n- Sem fallback para Chat Completions: n\u00e3o utilizamos Chat Completions; todo o fluxo permanece na Responses API.\n- Header opcional de beta: \u00e9 poss\u00edvel habilitar `OpenAI-Beta: responses=v1` via `OPENAI_BETA_RESPONSES=1` caso seu ambiente exija este gate.\n\nDocumenta\u00e7\u00e3o relacionada: `docs/api/openai_responses.md`, `docs/api/openai_responses_tool_calling.md` e `docs/api/streaming_tools_guide.md`.\n\nImpacto: corrige erros intermitentes 400 observados durante streaming + tools e melhora compatibilidade com formatos de tools enviados por clientes legados.\n\n---\n\n## \u2b50 Novo na v0.2.3: Robustez de Streaming + Follow\u2011up de Tools\n\nEsta vers\u00e3o refina o fluxo de streaming e o follow\u2011up de ferramentas:\n\n- Tratamento de erros em SSE:\n - Em 4xx, o cliente drena o corpo e lan\u00e7a `APIResponseError` com a mensagem do provider (elimina o erro \"without read()\" e evita conex\u00f5es em mau estado).\n- Follow\u2011ups de tool calling (Responses API):\n - O follow\u2011up inclui o descritor bruto `tool_use` (al\u00e9m de `function_call`) antes do respectivo `*_output`, alinhando com variantes da Responses API em cen\u00e1rios multi\u2011turn.\n- Testes e documenta\u00e7\u00e3o:\n - Novos testes de streaming e follow\u2011up.\n - Guia: `docs/api/streaming_tools_guide.md`.\n\nCompatibilidade: sem mudan\u00e7as de interface \u2014 sua integra\u00e7\u00e3o atual continua funcionando.\n\n---\n\n## \ud83e\uddea Desenvolvimento (v0.2.6+)\n\nEsta se\u00e7\u00e3o resume como depurar e validar cen\u00e1rios de streaming + tools no ForgeBase.\n\n- Defaults a partir da v0.2.5/0.2.6\n - Follow\u2011up em streaming: `outputs-only` habilitado (n\u00e3o reenvia descriptors brutos)\n - `tool_choice` no follow\u2011up: `none`\n - `name` inclu\u00eddo nos outputs\n\n- Flags de compatibilidade (override)\n - `FORGEBASE_FOLLOWUP_OUTPUTS_ONLY=0|1`\n - `FORGEBASE_FOLLOWUP_TOOL_CHOICE=none|auto|required`\n - `FORGEBASE_OUTPUT_NAME=0|1`\n - `FORGEBASE_OUTPUT_KIND=function_result|tool_result`\n\n- Diagn\u00f3stico (sem vazar conte\u00fado)\n - `FORGEBASE_DEBUG_FOLLOWUP=1` emite em n\u00edvel INFO a linha:\n `[follow-up] prev=... outputs=N types=[...] call_ids=[...] unique=N`\n - Recomenda\u00e7\u00f5es:\n - Habilitar `DEBUG` no logger durante testes locais\n - Usar hooks do provider para imprimir o payload do follow\u2011up quando necess\u00e1rio\n\n- Reconciliac\u0327a\u0303o de call_id (v0.2.8+)\n - IDs `call_*` coletados dos eventos de streaming e, se preciso, via `input_items`\n - Substituem IDs alternativos ao montar os outputs no follow\u2011up\n - Buffer de outputs particionado por `previous_response_id`\n\n- Testes\n - Rodar toda a su\u00edte: `pytest -q`\n - Casos relevantes:\n - `tests/test_streaming_error_handling.py`\n - `tests/test_streaming_tool_use_followup.py`\n - `tests/test_followup_flags_overrides.py`\n - O guia `docs/api/streaming_tools_guide.md` refor\u00e7a cen\u00e1rios, flags e troubleshooting\n\n\n## \u2b50 Novo na v0.2.1: LLMClientFactory\n\nA partir da v0.2.1, use `LLMClientFactory` para criar providers LLM de forma desacoplada:\n\n```python\nfrom forgebase import LLMClientFactory, Tool\n\n# Op\u00e7\u00e3o 1: Cria\u00e7\u00e3o manual\nprovider = LLMClientFactory.create(\"openai\", api_key=\"sk-...\", timeout=60)\n\n# Op\u00e7\u00e3o 2: Auto-configura\u00e7\u00e3o via vari\u00e1vel de ambiente (OPENAI_API_KEY)\nprovider = LLMClientFactory.create_from_env(\"openai\")\n\n# Usar o provider\nresponse = provider.send_message(\"Hello, world!\")\nprint(response)\n\n# Trocar provider transparentemente (quando dispon\u00edvel)\n# provider = LLMClientFactory.create(\"llama\") # Mesma interface!\n```\n\n**Por que usar a factory?**\n- \u2705 **Desacoplamento**: C\u00f3digo n\u00e3o depende de implementa\u00e7\u00e3o espec\u00edfica (OpenAI, Llama, etc.)\n- \u2705 **Extensibilidade**: Trocar provider mudando apenas 1 string\n- \u2705 **Testabilidade**: Mock de interface `ILLMClient` em vez de classe concreta\n- \u2705 **Preparado para o futuro**: Suporte a m\u00faltiplos providers sem breaking changes\n\n> **\u26a0\ufe0f Depreca\u00e7\u00e3o:** `OpenAIProvider` ainda funciona na v0.2.1 (backward compatible), mas recomendamos migrar para `LLMClientFactory`. Veja exemplos abaixo.\n\n---\n\n## Pacote por pacote\n\n### forge_utils\n\n- `forge_utils.log_service.LogService` configura logging com console/arquivo rotativo e filtros.\n- `forge_utils.log_service.logger` \u00e9 a inst\u00e2ncia global pronta para uso.\n- `forge_utils.paths` oferece helpers (`build_app_paths`, `ensure_dirs`) para organizar arquivos\n de configura\u00e7\u00e3o, hist\u00f3rico e cache.\n\n### forgebase\n\nReexporta o framework MVC-C b\u00e1sico. Os pontos de entrada mais usados s\u00e3o:\n\n- `CustomBaseModel` / `BaseModelData`: modelos Pydantic com suporte a *dirty tracking* e observers.\n- `CustomCommandBase` + `guard_errors`: encapsulam regras de neg\u00f3cio, padronizando\n o tratamento de exce\u00e7\u00f5es (`CommandException`).\n- `CustomBaseController` / `CustomBaseView`: composi\u00e7\u00e3o MVC-C m\u00ednima.\n- `PersistenceFactory` + `JsonPersistence`: persist\u00eancia compat\u00edvel com Pydantic v2.\n\nTodos estes nomes est\u00e3o dispon\u00edveis diretamente com `from forgebase import ...`.\n\n### llm_client\n\nO cliente LLM tamb\u00e9m \u00e9 reexportado por `forgebase` para facilitar o consumo:\n\n**API P\u00fablica (v0.2.1+):**\n- `LLMClientFactory`: factory para criar providers sem conhecer implementa\u00e7\u00e3o (\u2705 **use este!**)\n- `ILLMClient`: interface Protocol para type hints e extensibilidade\n- `Tool`: modelo Pydantic que representa o schema JSON das ferramentas\n- `APIResponseError` / `ConfigurationError`: exce\u00e7\u00f5es espec\u00edficas do cliente\n- `ContentPart`, `OutputMessage`, `ResponseResult`, `TextFormat`, `TextOutputConfig`: modelos\n retornados pelo Responses API\n\n**Legacy (deprecated - mantido para compatibilidade):**\n- `OpenAIProvider`: \u26a0\ufe0f usar `LLMClientFactory.create(\"openai\")` em vez disso\n- `LLMOpenAIClient`: \u26a0\ufe0f interno - n\u00e3o usar diretamente\n\n**Multi-provider ready:** A arquitetura est\u00e1 preparada para m\u00faltiplos providers (Llama, Anthropic, OpenRouter)\nsem breaking changes no c\u00f3digo cliente. Use `LLMClientFactory` para garantir compatibilidade futura.\n\n## Guia r\u00e1pido de uso\n\n### Core MVC-C\n\n```python\nfrom forgebase import CustomBaseModel, CustomCommandBase, JsonPersistence, guard_errors\n\nclass User(CustomBaseModel):\n id: int\n name: str\n\nclass CreateUserCommand(CustomCommandBase):\n @guard_errors\n def execute(self, payload: dict) -> User:\n model = User(**payload)\n # ... l\u00f3gica de neg\u00f3cio ...\n return model\n\nstorage = JsonPersistence(\"users.json\")\n```\n\n### Cliente LLM \u2013 configura\u00e7\u00e3o e chamadas\n\nCrie um arquivo `.env` na raiz do projeto (ou exporte no shell) com:\n\n```\nOPENAI_API_KEY=sk-...\n```\n\nTodos os exemplos abaixo carregam essa chave automaticamente via `python-dotenv`.\n\n#### Chamada s\u00edncrona\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom forgebase import APIResponseError, ConfigurationError, LLMOpenAIClient\n\nload_dotenv()\nclient = LLMOpenAIClient(api_key=os.environ[\"OPENAI_API_KEY\"], model=\"gpt-4o-mini\")\n\ntry:\n # instructions: mensagem de sistema (n\u00e3o confundir com o prompt)\n response = client.send_prompt(\n \"Por que o c\u00e9u \u00e9 azul?\",\n instructions=\"Voc\u00ea \u00e9 um assistente t\u00e9cnico. Responda de forma objetiva.\"\n )\n answer = \"\\n\".join(\n part.text.strip()\n for item in response.output\n for part in getattr(item, \"content\", [item])\n if getattr(part, \"text\", None)\n )\n print(answer)\nexcept (APIResponseError, ConfigurationError) as exc:\n print(f\"Falha na chamada: {exc}\")\n```\n\n#### Usando system instructions (Responses API)\n\n\"Instructions\" s\u00e3o a mensagem de sistema da Responses API. Elas definem o comportamento do modelo, e n\u00e3o devem ser confundidas com o prompt do usu\u00e1rio. No Forgebase, voc\u00ea pode pass\u00e1-las explicitamente nas chamadas do cliente e do provider. Se omitidas, a API do provider usar\u00e1 as instru\u00e7\u00f5es padr\u00e3o do servidor.\n\nExemplos:\n\n```python\nfrom forgebase import LLMOpenAIClient, OpenAIProvider, LLMClientFactory\n\n# 1) Cliente direto\nclient = LLMOpenAIClient(api_key=os.environ[\"OPENAI_API_KEY\"], model=\"gpt-4o\")\nresp = client.send_prompt(\n \"Liste 3 boas pr\u00e1ticas para logs.\",\n instructions=(\n \"Voc\u00ea \u00e9 um revisor s\u00eanior. Use linguagem clara, sem jarg\u00f5es desnecess\u00e1rios.\"\n ),\n)\n\n# 2) Provider (recomendado via Factory)\nprovider = LLMClientFactory.create_from_env(\"openai\")\nout = provider.send_message(\n \"Explique o conceito de idempot\u00eancia.\",\n instructions=\"Padr\u00e3o: respostas curtas, com um exemplo pr\u00e1tico.\"\n)\n\n# 3) Streaming com instructions\nfor chunk in provider.send_stream(\n \"Resuma o papel do backoff exponencial.\",\n instructions=\"Resuma em at\u00e9 2 frases.\"\n):\n print(chunk, end=\"\")\n```\n\nObserva\u00e7\u00f5es:\n- Instructions s\u00e3o propagadas em m\u00faltiplas rodadas durante tool calling (follow\u2011ups).\n- Instructions n\u00e3o substituem o prompt; s\u00e3o complementares e tratadas como \u201csystem message\u201d.\n- O campo \u00e9 enviado como `ResponseRequest.instructions` para a Responses API.\n\n##### Migrando do uso direto da OpenAI API\n\nAntes utiliz\u00e1vamos o SDK oficial diretamente. Agora a camada `LLMOpenAIClient` traz timeout,\nhooks e tool calling prontos, al\u00e9m de facilitar a troca de provider.\n\n```python\n# Legado: uso direto do SDK da OpenAI\nfrom openai import OpenAI\n\nclient = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])\nresp = client.responses.create(model=\"gpt-4o-mini\", input=\"Por que o c\u00e9u \u00e9 azul?\")\nprint(resp.output[0].content[0].text)\n```\n\n```python\n# Atual: via LLMOpenAIClient encapsulado\nfrom forgebase import LLMOpenAIClient\n\nclient = LLMOpenAIClient(api_key=os.environ[\"OPENAI_API_KEY\"], model=\"gpt-4o-mini\")\nresp = client.send_prompt(\"Por que o c\u00e9u \u00e9 azul?\")\nprint(resp.output[0].content[0].text)\n```\n\n#### Configurando timeout (\u2b50 novo na v0.2.0)\n\nPor padr\u00e3o, as chamadas HTTP usam timeout de 120 segundos. Voc\u00ea pode customizar:\n\n```python\n# Timeout de 45 segundos\nclient = LLMOpenAIClient(\n api_key=os.environ[\"OPENAI_API_KEY\"],\n timeout=45 # segundos\n)\n\n# Ou via OpenAIProvider\nfrom forgebase import OpenAIProvider\n\nprovider = OpenAIProvider(timeout=45)\nprovider.set_api_key(os.environ[\"OPENAI_API_KEY\"])\nresult = provider.send_message(\"Hello\")\n```\n\n**Importante:** O timeout se aplica ao tempo total incluindo retries. Com `timeout=45` e `max_tries=4`, o sistema n\u00e3o ultrapassar\u00e1 45 segundos mesmo com m\u00faltiplas tentativas.\n\n#### Chamada com streaming\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom forgebase import LLMOpenAIClient\n\nload_dotenv()\nclient = LLMOpenAIClient(api_key=os.environ[\"OPENAI_API_KEY\"], model=\"gpt-4o-mini\")\n\nstream = client.send_prompt(\"Conte uma hist\u00f3ria curta sobre um rob\u00f4 e uma crian\u00e7a.\", streamed=True)\nfor delta in stream:\n print(delta, end=\"\", flush=True)\nprint()\n```\n\n#### Chamada multimodal (imagem + \u00e1udio)\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom forgebase import LLMOpenAIClient\n\nload_dotenv()\nclient = LLMOpenAIClient(api_key=os.environ[\"OPENAI_API_KEY\"], model=\"gpt-4o\")\n\nresponse = client.send_prompt(\n \"Descreva a imagem e comente o \u00e1udio anexado.\",\n images=[\"https://upload.wikimedia.org/wikipedia/commons/9/99/Colorful_sunset.jpg\"],\n audio={\"base64\": \"ZGF0YQ==\", \"mime_type\": \"audio/wav\"},\n)\nprint(response)\n```\n\n#### Listar modelos dispon\u00edveis\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom forgebase import LLMOpenAIClient\n\nload_dotenv()\nclient = LLMOpenAIClient(api_key=os.environ[\"OPENAI_API_KEY\"])\nmodels = client.list_models()\nprint(models[\"data\"][0])\n```\n\n#### Tool calling s\u00edncrono (v0.2.1+)\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom forgebase import LLMClientFactory, Tool\n\nload_dotenv()\n\n# \u2705 Novo: usar factory\nprovider = LLMClientFactory.create_from_env(\"openai\")\n\n# 1) Descreva a ferramenta com JSON Schema compat\u00edvel com a Responses API\ntool = Tool(\n type=\"function\",\n name=\"say_hello\",\n parameters={\"type\": \"object\", \"properties\": {\"who\": {\"type\": \"string\"}}, \"required\": [\"who\"]},\n)\n# 2) Conte ao provider quais ferramentas est\u00e3o dispon\u00edveis\nprovider.configure_tools([tool], tool_choice=\"required\")\n# 3) Registre o handler Python que ser\u00e1 chamado quando o modelo disparar a ferramenta\nprovider.register_tool(\"say_hello\", lambda args: f\"Ol\u00e1, {args['who']}!\")\n\n# 4) Use send_message normalmente: o provider executa as tools e devolve o texto final\nprint(provider.send_message(\"Cumprimente Forgebase.\"))\n```\n\n<details>\n<summary>\ud83d\udcdc C\u00f3digo antigo (deprecated - clique para expandir)</summary>\n\n```python\n# \u26a0\ufe0f Deprecated: usando OpenAIProvider diretamente\nfrom forgebase import OpenAIProvider, Tool\n\nprovider = OpenAIProvider()\nprovider.set_api_key(os.environ[\"OPENAI_API_KEY\"])\n# ... resto do c\u00f3digo igual\n```\n</details>\n\n##### Tool calling com orquestra\u00e7\u00e3o autom\u00e1tica\n\nProviders criados pela factory encapsulam todos os ciclos de *tool calling* da Responses API.\nBasta definir o schema das ferramentas, registrar os handlers e chamar `send_message`:\n\n```python\nfrom forgebase import LLMClientFactory, Tool\n\nprovider = LLMClientFactory.create(\"openai\", api_key=os.environ[\"OPENAI_API_KEY\"], timeout=60)\n\nweather_tool = Tool(\n type=\"function\",\n name=\"get_weather\",\n description=\"Retorna a temperatura atual para uma cidade brasileira.\",\n parameters={\n \"type\": \"object\",\n \"properties\": {\"city\": {\"type\": \"string\"}},\n \"required\": [\"city\"],\n },\n)\n\nprovider.configure_tools([weather_tool], tool_choice=\"required\")\n\ndef get_weather(args: dict[str, str]) -> str:\n city = args[\"city\"]\n return f\"{city}, 30 graus.\"\n\nprovider.register_tool(\"get_weather\", get_weather)\n\nprint(provider.send_message(\"Qual a temperatura de hoje em S\u00e3o Paulo?\"))\n```\n\nInternamente o provider:\n\n- Executa m\u00faltiplas rodadas de tool calling at\u00e9 chegar no texto final \u2014 o mesmo fluxo usado nos testes `test_demo_tool_call_api_forced` e `test_openai_provider_hooks_with_tools`.\n- 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`.\n- Exp\u00f5e hooks (`before_tool_call`, `after_tool_call`, `tool_error`, etc.) que voc\u00ea pode monitorar para m\u00e9tricas e logs.\n- Funciona tanto com chamadas s\u00edncronas (`send_message`) quanto streaming (`send_stream`) sem alterar o c\u00f3digo dos handlers.\n\n**Passo a passo resumido**\n\n- Configure o provider com `set_api_key`.\n- Descreva as ferramentas com `forgebase.Tool` em `configure_tools`.\n- Registre cada handler com `register_tool`.\n- Dispare `send_message` (ou `send_stream`) \u2014 o provider executa as ferramentas e devolve apenas o texto final.\n- Opcional: adicione hooks (`register_hook`) para inspecionar `before_tool_call`, `after_tool_call` ou erros.\n\nPara validar fluxos sem acessar a API real, use `LLMOpenAIClient` com uma resposta fake\n(`response=<objeto compat\u00edvel>`) ou rode `python -m llm_client.demo_tool_call_api` que inclui\num modo offline utilizando as mesmas rotinas de orquestra\u00e7\u00e3o (vide `tests/test_demo_tool_call_api.py`).\n\n#### Tool calling com streaming\n\n```python\nimport os\nfrom dotenv import load_dotenv\nfrom forgebase import OpenAIProvider, Tool\n\nload_dotenv()\nprovider = OpenAIProvider()\nprovider.set_api_key(os.environ[\"OPENAI_API_KEY\"])\n\ntool = Tool(\n type=\"function\",\n name=\"summarize_numbers\",\n parameters={\"type\": \"object\", \"properties\": {\"nums\": {\"type\": \"array\", \"items\": {\"type\": \"number\"}}}},\n)\nprovider.configure_tools([tool], tool_choice=\"auto\")\nprovider.register_tool(\"summarize_numbers\", lambda args: sum(args.get(\"nums\", [])))\n\nfor chunk in provider.send_stream(\"Considere os n\u00fameros 2, 4, 6 e mostre a soma.\"):\n print(chunk, end=\"\", flush=True)\nprint()\n\n# Verifique tests/test_openai_tool_calling.py::test_streaming_tool_call_flow\n# para um cen\u00e1rio completo de m\u00faltiplas rodadas no modo streaming.\n```\n\n#### Hooks de eventos\n\nTanto o `LLMOpenAIClient` quanto o `OpenAIProvider` exp\u00f5em um sistema simples de\nhooks para instrumentar o fluxo:\n\n```python\nfrom forgebase import LLMOpenAIClient, OpenAIProvider\n\nclient = LLMOpenAIClient(api_key=os.environ[\"OPENAI_API_KEY\"])\nclient.register_hook(\"before_request\", lambda ctx: print(\"\u25b6\", ctx[\"prompt\"]))\nclient.register_hook(\"after_response\", lambda ctx: print(\"\u25c0\", ctx.get(\"response\")))\n\nprovider = OpenAIProvider(client=client)\nprovider.register_hook(\"before_tool_call\", lambda ctx: print(\"tool\", ctx[\"tool\"]))\nprovider.register_hook(\"after_tool_call\", lambda ctx: print(\"tool result\", ctx[\"result\"]))\n```\n\nEventos dispon\u00edveis:\n\n- `before_request`, `after_response`, `on_error`, `on_cache_hit` (cliente LLM)\n- `before_send`, `after_send`, `on_error`, `before_tool_call`, `after_tool_call`, `tool_error`, `cache_hit` (provider)\n\n### Demo completo\n\nO projeto inclui uma demo mais abrangente que cobre respostas diretas, streaming,\n(tool calling) e replays offline:\n\n```bash\nPYTHONPATH=shared/src:apps/llm_client/src python -m llm_client.example_full_usage\n```\n\nNo Windows (PowerShell):\n\n```powershell\n$env:PYTHONPATH = \"shared/src;apps/llm_client/src\"\npython -m llm_client.example_full_usage\n```\n\nO arquivo `apps/llm_client/src/llm_client/example_full_usage.py` comenta cada etapa\n(passos para configurar `OPENAI_API_KEY`, habilitar tool calling real com\n`DEMO_TOOL_CALLING=1`, e como funciona o replay offline).\n\n## Configura\u00e7\u00e3o do ambiente\n\n### Op\u00e7\u00e3o 1 \u2013 Poetry (recomendada)\n\nO `pyproject.toml` j\u00e1 descreve todos os pacotes via `path`. Basta executar na raiz:\n\n```bash\npoetry install\npoetry run pytest -q\npoetry run python -m llm_client.example_full_usage\n```\n\nO Poetry cria e gerencia o ambiente virtual automaticamente; n\u00e3o \u00e9 necess\u00e1rio ajustar o\n`PYTHONPATH` manualmente.\n\n### Op\u00e7\u00e3o 2 \u2013 pip + requirements\n\nSe preferir `pip`, gere um virtualenv e instale as depend\u00eancias de desenvolvimento com\no arquivo `requirements-dev.txt` gerado a partir do `pyproject`:\n\n```bash\npython -m venv .venv\nsource .venv/bin/activate\npython -m pip install -U pip\npython -m pip install -r requirements-dev.txt\n```\n\nO arquivo pode ser sincronizado com o `pyproject.toml` executando `python requirements-dev.py`.\nDepois disso, exporte `PYTHONPATH=shared/src:framework/src:apps/llm_client/src:cli/src` (ou\nuse `python -m` para os m\u00f3dulos) e rode `pytest -q` normalmente.\n\n## Desenvolvimento local\n\n- Testes: `pytest -q` (ou `poetry run pytest -q`).\n- Linters: `ruff check .` e `mypy --config-file mypy.ini` (com `poetry run` se estiver usando Poetry).\n- Build: `python -m build` gera wheel/sdist para publicar em TestPyPI/PyPI.\n\n## Onde continuar\n\n- `docs/api/openai_responses.md`: detalhes da Responses API e eventos de streaming.\n - `docs/api/openai_responses_tool_calling.md`: guia de tool calling, payloads e replays.\n - `docs/api/streaming_tools_guide.md`: guia extensivo de streaming + tools (SSE, timeouts, follow\u2011ups).\n- `docs/architecture/forgebase-architecture.md`: vis\u00e3o completa da arquitetura e fluxos.\n- `docs/release-guide.md`: processo recomendado de versionamento e publica\u00e7\u00e3o.\n- `docs/testing-strategy.md`: abordagem de testes e boas pr\u00e1ticas.\n- `docs/configuration.md`: vari\u00e1veis de ambiente e diret\u00f3rios importantes.\n- `docs/providers/adding-new-provider.md`: instru\u00e7\u00f5es para suportar novos LLMs.\n- `docs/cli/usage.md`: comandos expostos pela CLI.\n- `docs/CONTRIBUTING.md`: conven\u00e7\u00f5es de contribui\u00e7\u00e3o.\n- `docs/adr/README.md`: decis\u00f5es arquiteturais registradas.\n- `docs/tech-debts/TD-001-Robustez-Tool-Calling-Responses.md`: backlog de melhorias planejadas.\n\nSinta-se \u00e0 vontade para abrir issues ou PRs com sugest\u00f5es e corre\u00e7\u00f5es.\n",
"bugtrack_url": null,
"license": "LGPL",
"summary": "Forgebase core libraries (utils, framework e cliente LLM)",
"version": "0.2.12",
"project_urls": {
"Homepage": "https://github.com/palhanobrazil/forgebase",
"Repository": "https://github.com/palhanobrazil/forgebase"
},
"split_keywords": [
"forgebase",
" framework",
" llm",
" openai"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "3a1a01737aa7f765a98cadccd7720546c053acea3865de33d8622cf287f5abad",
"md5": "f7a3f450d7acd92e4c40538f4a0333f8",
"sha256": "e91e6fb0bdfc830cf1af0cba8ee3dde408af08bc0ec9824c8ea2a668c9ebb775"
},
"downloads": -1,
"filename": "forgebase-0.2.12-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f7a3f450d7acd92e4c40538f4a0333f8",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.11",
"size": 47275,
"upload_time": "2025-10-21T14:51:58",
"upload_time_iso_8601": "2025-10-21T14:51:58.458361Z",
"url": "https://files.pythonhosted.org/packages/3a/1a/01737aa7f765a98cadccd7720546c053acea3865de33d8622cf287f5abad/forgebase-0.2.12-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6e76c215105a050c181cbc4b22ddbeafa17199432ac3ee0fe874b355a1ddb8e7",
"md5": "27870a37c9a26bc9a77cc8ea108c7993",
"sha256": "d8f58824d33fe7c7929e4c0f1ffed7a7258543fa252ad75c73a0a481b95d7f97"
},
"downloads": -1,
"filename": "forgebase-0.2.12.tar.gz",
"has_sig": false,
"md5_digest": "27870a37c9a26bc9a77cc8ea108c7993",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.11",
"size": 44131,
"upload_time": "2025-10-21T14:52:01",
"upload_time_iso_8601": "2025-10-21T14:52:01.570008Z",
"url": "https://files.pythonhosted.org/packages/6e/76/c215105a050c181cbc4b22ddbeafa17199432ac3ee0fe874b355a1ddb8e7/forgebase-0.2.12.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-21 14:52:01",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "palhanobrazil",
"github_project": "forgebase",
"github_not_found": true,
"lcname": "forgebase"
}