mirror of
https://github.com/mcloh/poc-grok-websearch.git
synced 2026-11-06 06:14:11 +00:00
Verify native OCI web search across seven Grok model IDs
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
OCI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/v1
|
||||
OCI_MODEL=xai.grok-4.3
|
||||
OCI_API_KEY=replace-with-oci-generative-ai-api-key
|
||||
OCI_PROJECT_ID=replace-with-active-chicago-project-id
|
||||
APP_PASSWORD=replace-with-a-strong-password
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ render_check.py
|
||||
evidence/*
|
||||
!evidence/RELATORIO.md
|
||||
!evidence/validation-summary.json
|
||||
!evidence/grok-websearch-models.json
|
||||
|
||||
51
README.md
51
README.md
@@ -1,6 +1,6 @@
|
||||
# Grok Web Search via OCI
|
||||
|
||||
POC de chatbot com interface JavaScript e backend Python/FastAPI. Usa `xai.grok-4.3` pela OCI Generative AI e habilita a ferramenta nativa `web_search` na Responses API. A aplicação chama exclusivamente o endpoint OCI.
|
||||
POC de chatbot com interface JavaScript e backend Python/FastAPI. Usa Grok (padrão `xai.grok-4.3`, configurável por `OCI_MODEL`) pela OCI Generative AI e habilita a ferramenta nativa `web_search` na Responses API. A aplicação chama exclusivamente o endpoint OCI.
|
||||
|
||||
## Estrutura
|
||||
|
||||
@@ -11,14 +11,17 @@ POC de chatbot com interface JavaScript e backend Python/FastAPI. Usa `xai.grok-
|
||||
│ ├── index.html # Interface e estilos
|
||||
│ └── app.js # Chat, histórico e citações
|
||||
├── requirements.txt
|
||||
├── requirements-test.txt # TestClient para matriz de modelos
|
||||
├── Dockerfile
|
||||
├── .dockerignore
|
||||
├── .gitignore
|
||||
├── .env.example # Variáveis sem credenciais
|
||||
├── validate.py # Validação REST com inferência real
|
||||
├── test_models.py # Matriz de modelos pela mesma aplicação
|
||||
└── evidence/
|
||||
├── RELATORIO.md # Resultados da POC
|
||||
└── validation-summary.json # Evidências selecionadas
|
||||
├── validation-summary.json # Evidências selecionadas
|
||||
└── grok-websearch-models.json # Matriz de compatibilidade datada
|
||||
```
|
||||
|
||||
## Configuração
|
||||
@@ -37,6 +40,7 @@ Preencha `.env`:
|
||||
| Variável | Finalidade |
|
||||
|---|---|
|
||||
| `OCI_BASE_URL` | `https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/v1` |
|
||||
| `OCI_MODEL` | Modelo Grok; padrão `xai.grok-4.3` |
|
||||
| `OCI_API_KEY` | Chave OCI Generative AI |
|
||||
| `OCI_PROJECT_ID` | Identificador de projeto ativo em Chicago |
|
||||
| `APP_PASSWORD` | Senha forte para o usuário `poc` |
|
||||
@@ -92,6 +96,49 @@ A Oracle informa que os modelos Grok acessados pela OCI são hospedados externam
|
||||
|
||||
Nos testes de 14/10/2026, `/openai/v1/responses` rejeitou `web_search` para Grok; `/v1/responses` e `/20231130/actions/v1/responses` funcionaram com um projeto ativo de Chicago. A aplicação usa `/v1/responses`.
|
||||
|
||||
## Modelos com web_search confirmado em 14/10/2026
|
||||
|
||||
Teste da mesma rota `/api/chat` em processos locais isolados via FastAPI TestClient, com inferência real na OCI **us-chicago-1**, endpoint **`/v1/responses`**, projeto ativo e `tools: [{"type":"web_search"}]`. Não foram chamadas APIs diretas da xAI. A implantação pública original foi validada com Grok 4.3; esta matriz usa a aplicação local para variar os modelos sem interromper a VM.
|
||||
|
||||
| Modelo solicitado e retornado | Busca nativa | HTTP app/OCI | Buscas concluídas | Citações | Tempo (s) |
|
||||
|---|---|---|---:|---:|---:|
|
||||
| `xai.grok-4.20-0309-non-reasoning` | Confirmado | 200/200 | 2 | 1 | 3.62 |
|
||||
| `xai.grok-4.20-0309-reasoning` | Confirmado | 200/200 | 1 | 2 | 14.80 |
|
||||
| `xai.grok-4.20-non-reasoning` | Confirmado | 200/200 | 2 | 1 | 3.22 |
|
||||
| `xai.grok-4.20-reasoning` | Confirmado | 200/200 | 2 | 1 | 17.09 |
|
||||
| `xai.grok-4.3` | Confirmado | 200/200 | 2 | 1 | 9.24 |
|
||||
| `xai.grok-4.6` | Confirmado | 200/200 | 1 | 1 | 35.17 |
|
||||
| `xai.grok-4.7` | Confirmado | 200/200 | 3 | 1 | 12.97 |
|
||||
|
||||
Critério de confirmação: HTTP 200, pelo menos um evento `web_search_call` com ação `search` e status `completed`, fontes não vazias e citações estruturadas. HTTP 200 isolado não comprova a execução de busca.
|
||||
|
||||
Foram sete chamadas, sem retries, com até duas inferências simultâneas e 2400 tokens de saída por chamada. Todos os modelos retornaram exatamente o identificador solicitado. Os testes cobrem os sete modelos de texto Grok do catálogo local; Voice Agent, Multi-Agent e outros identificadores não foram testados. O resultado vale para esta data, região, endpoint e permissões; não confirma todas as regiões ou interfaces OCI. A validação da busca não equivale à verificação independente de todas as notícias retornadas.
|
||||
|
||||
Os tempos são observações de uma chamada por modelo, sob concorrência limitada, e não constituem benchmark. Consumo agregado: **76.374 tokens** (69.948 de entrada e 6.426 de saída), com **13 ferramentas** contabilizadas pelo serviço. `cost_in_usd_ticks` foi preservado sem conversão financeira.
|
||||
|
||||
Para escolher um modelo, defina `OCI_MODEL` no `.env` e reinicie o serviço. O padrão continua `xai.grok-4.3`; `/health` e o indicador da UI refletem a configuração.
|
||||
|
||||
Para reproduzir a matriz (sete chamadas pagas):
|
||||
|
||||
```bash
|
||||
. .venv/bin/activate
|
||||
pip install -r requirements-test.txt
|
||||
set -a
|
||||
. ./.env
|
||||
set +a
|
||||
python test_models.py
|
||||
```
|
||||
|
||||
Para testar somente um modelo:
|
||||
|
||||
```bash
|
||||
python test_models.py --models xai.grok-4.7
|
||||
```
|
||||
|
||||
O [script](test_models.py) salva respostas brutas localmente em `./evidence/model-tests-raw/` (ignoradas pelo Git) e um [resumo da matriz](evidence/grok-websearch-models.json), sem credenciais nem identificadores de projeto. Execuções parciais na mesma data preservam os resultados dos demais modelos já testados. As versões do runtime usado na matriz original estão no JSON; a reprodução instala as dependências fixadas do aplicativo.
|
||||
|
||||
Referências do fornecedor: [Grok 4.20](https://docs.oracle.com/en-us/iaas/Content/generative-ai/xai-grok-4-20.htm), [Grok 4.6](https://docs.oracle.com/en-us/iaas/Content/generative-ai/xai-grok-4-6.htm) e [Grok 4.7](https://docs.oracle.com/en-us/iaas/Content/generative-ai/x-ai-grok-4-7.htm). A confirmação nesta tabela vem dos testes de execução.
|
||||
|
||||
## API REST
|
||||
|
||||
`POST /api/chat` com HTTP Basic. Na publicação com prefixo, use `POST /grok-poc/api/chat`.
|
||||
|
||||
4
app.py
4
app.py
@@ -20,7 +20,7 @@ class Chat(BaseModel):
|
||||
message:str=Field(min_length=1,max_length=8000)
|
||||
history:list[Turn]=Field(default_factory=list,max_length=12)
|
||||
@app.get('/health')
|
||||
def health():return {'status':'ok','provider':'OCI','model':'xai.grok-4.3','tool':'web_search'}
|
||||
def health():return {'status':'ok','provider':'OCI','model':os.environ.get('OCI_MODEL','xai.grok-4.3'),'tool':'web_search'}
|
||||
@app.get('/',dependencies=[Depends(authorize)])
|
||||
def index():return FileResponse('static/index.html')
|
||||
@app.get('/app.js',dependencies=[Depends(authorize)])
|
||||
@@ -32,7 +32,7 @@ async def chat(q:Chat):
|
||||
base=os.environ['OCI_BASE_URL'].rstrip('/')
|
||||
if urlparse(base).hostname!='inference.generativeai.us-chicago-1.oci.oraclecloud.com':raise HTTPException(503,'Endpoint OCI inválido')
|
||||
now=datetime.now(ZoneInfo('America/Sao_Paulo'))
|
||||
body={'model':'xai.grok-4.3','input':[t.model_dump() for t in q.history]+[{'role':'user','content':q.message}],
|
||||
body={'model':os.environ.get('OCI_MODEL','xai.grok-4.3'),'input':[t.model_dump() for t in q.history]+[{'role':'user','content':q.message}],
|
||||
'instructions':f'Você é um assistente em português brasileiro. Hoje é {now.isoformat()}. Use a ferramenta web_search para verificar informações atuais. Cite fontes com URLs e datas. Diferencie data de publicação e data do fato. Nunca invente notícias; se não houver evidência de hoje, diga isso claramente.',
|
||||
'tools':[{'type':'web_search'}],'store':False,'max_output_tokens':2400}
|
||||
started=time.monotonic()
|
||||
|
||||
@@ -19,3 +19,7 @@ Fonte: [Agência Brasil — Confira a agenda dos presidenciáveis nesta quarta-f
|
||||
A ferramenta executou uma busca inicial que falhou, reformulou consultas e concluiu a pesquisa. As respostas brutas, identificadores OCI, capturas e cópia integral da fonte permanecem locais e não são versionados. O SHA-256 da fonte permite conferir o artefato local.
|
||||
|
||||
A chamada final consumiu 15.544 tokens. Incluindo os diagnósticos bem-sucedidos, foram 34.829 tokens e nove ferramentas contabilizadas pelo serviço. A escala financeira de cost_in_usd_ticks não foi verificada; o campo é preservado sem conversão para dólares.
|
||||
|
||||
## Matriz de versões Grok
|
||||
|
||||
Em 14/10/2026, sete modelos de texto passaram no teste da mesma aplicação via TestClient e inferência OCI real: Grok 4.3, 4.6, 4.7 e as quatro variantes 4.20. Veja a [matriz estruturada](grok-websearch-models.json) e a [metodologia no README](../README.md#modelos-com-web_search-confirmado-em-14102026). Cada confirmação exige busca concluída com fontes e citações; os tempos são observações isoladas. A matriz consumiu 76.374 tokens e 13 ferramentas contabilizadas pelo serviço, separadamente das validações originais acima.
|
||||
|
||||
414
evidence/grok-websearch-models.json
Normal file
414
evidence/grok-websearch-models.json
Normal file
@@ -0,0 +1,414 @@
|
||||
{
|
||||
"tested_at": "2026-10-14T11:27:10.525049-03:00",
|
||||
"region": "us-chicago-1",
|
||||
"endpoint": "/v1/responses",
|
||||
"method": "POST /api/chat via FastAPI TestClient; inferência OCI real; um processo isolado por modelo",
|
||||
"max_output_tokens": 2400,
|
||||
"concurrency": 2,
|
||||
"retries": 0,
|
||||
"confirmation_criteria": "HTTP 200, search completed, fontes não vazias e citações estruturadas",
|
||||
"results": [
|
||||
{
|
||||
"requested_model": "xai.grok-4.20-0309-non-reasoning",
|
||||
"returned_model": "xai.grok-4.20-0309-non-reasoning",
|
||||
"tested_at": "2026-10-14T11:26:10.793751-03:00",
|
||||
"app_http": 200,
|
||||
"oci_http": 200,
|
||||
"seconds": 3.62,
|
||||
"websearch_confirmed": true,
|
||||
"completed_searches": 2,
|
||||
"source_count": 15,
|
||||
"citation_count": 1,
|
||||
"tool_events": [
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 10036,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 1024
|
||||
},
|
||||
"output_tokens": 196,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 0
|
||||
},
|
||||
"total_tokens": 10232,
|
||||
"num_sources_used": 0,
|
||||
"num_server_side_tools_used": 2,
|
||||
"cost_in_usd_ticks": 219598000,
|
||||
"server_side_tool_usage_details": {
|
||||
"web_search_calls": 2,
|
||||
"x_search_calls": 0,
|
||||
"x_posts_fetched": 0,
|
||||
"x_users_fetched": 0,
|
||||
"code_interpreter_calls": 0,
|
||||
"file_search_calls": 0,
|
||||
"mcp_calls": 0,
|
||||
"document_search_calls": 0,
|
||||
"image_generation_calls": 0
|
||||
},
|
||||
"context_details": {
|
||||
"input_tokens": 9058,
|
||||
"output_tokens": 138
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"health_model": "xai.grok-4.20-0309-non-reasoning",
|
||||
"citation_urls": [
|
||||
"https://ndmais.com.br/tempo/tempestades-1-342-municipios-alerta-laranja-quarta-feira-14/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"requested_model": "xai.grok-4.20-0309-reasoning",
|
||||
"returned_model": "xai.grok-4.20-0309-reasoning",
|
||||
"tested_at": "2026-10-14T11:26:10.782598-03:00",
|
||||
"app_http": 200,
|
||||
"oci_http": 200,
|
||||
"seconds": 14.8,
|
||||
"websearch_confirmed": true,
|
||||
"completed_searches": 1,
|
||||
"source_count": 10,
|
||||
"citation_count": 2,
|
||||
"tool_events": [
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 7712,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 256
|
||||
},
|
||||
"output_tokens": 1491,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 1375
|
||||
},
|
||||
"total_tokens": 9203,
|
||||
"num_sources_used": 0,
|
||||
"num_server_side_tools_used": 1,
|
||||
"cost_in_usd_ticks": 180987000,
|
||||
"server_side_tool_usage_details": {
|
||||
"web_search_calls": 1,
|
||||
"x_search_calls": 0,
|
||||
"x_posts_fetched": 0,
|
||||
"x_users_fetched": 0,
|
||||
"code_interpreter_calls": 0,
|
||||
"file_search_calls": 0,
|
||||
"mcp_calls": 0,
|
||||
"document_search_calls": 0,
|
||||
"image_generation_calls": 0
|
||||
},
|
||||
"context_details": {
|
||||
"input_tokens": 6732,
|
||||
"output_tokens": 864
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"health_model": "xai.grok-4.20-0309-reasoning",
|
||||
"citation_urls": [
|
||||
"https://correiodobrasil.com.br/c/ultimas-noticias/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"requested_model": "xai.grok-4.20-non-reasoning",
|
||||
"returned_model": "xai.grok-4.20-non-reasoning",
|
||||
"tested_at": "2026-10-14T11:26:14.931513-03:00",
|
||||
"app_http": 200,
|
||||
"oci_http": 200,
|
||||
"seconds": 3.22,
|
||||
"websearch_confirmed": true,
|
||||
"completed_searches": 2,
|
||||
"source_count": 15,
|
||||
"citation_count": 1,
|
||||
"tool_events": [
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 10944,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 1024
|
||||
},
|
||||
"output_tokens": 224,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 0
|
||||
},
|
||||
"total_tokens": 11168,
|
||||
"num_sources_used": 0,
|
||||
"num_server_side_tools_used": 2,
|
||||
"cost_in_usd_ticks": 231648000,
|
||||
"server_side_tool_usage_details": {
|
||||
"web_search_calls": 2,
|
||||
"x_search_calls": 0,
|
||||
"x_posts_fetched": 0,
|
||||
"x_users_fetched": 0,
|
||||
"code_interpreter_calls": 0,
|
||||
"file_search_calls": 0,
|
||||
"mcp_calls": 0,
|
||||
"document_search_calls": 0,
|
||||
"image_generation_calls": 0
|
||||
},
|
||||
"context_details": {
|
||||
"input_tokens": 9966,
|
||||
"output_tokens": 153
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"health_model": "xai.grok-4.20-non-reasoning",
|
||||
"citation_urls": [
|
||||
"https://ndmais.com.br/tempo/tempestades-1-342-municipios-alerta-laranja-quarta-feira-14/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"requested_model": "xai.grok-4.20-reasoning",
|
||||
"returned_model": "xai.grok-4.20-reasoning",
|
||||
"tested_at": "2026-10-14T11:26:18.687154-03:00",
|
||||
"app_http": 200,
|
||||
"oci_http": 200,
|
||||
"seconds": 17.09,
|
||||
"websearch_confirmed": true,
|
||||
"completed_searches": 2,
|
||||
"source_count": 10,
|
||||
"citation_count": 1,
|
||||
"tool_events": [
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 7882,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 1024
|
||||
},
|
||||
"output_tokens": 1531,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 1329
|
||||
},
|
||||
"total_tokens": 9413,
|
||||
"num_sources_used": 0,
|
||||
"num_server_side_tools_used": 2,
|
||||
"cost_in_usd_ticks": 226048000,
|
||||
"server_side_tool_usage_details": {
|
||||
"web_search_calls": 2,
|
||||
"x_search_calls": 0,
|
||||
"x_posts_fetched": 0,
|
||||
"x_users_fetched": 0,
|
||||
"code_interpreter_calls": 0,
|
||||
"file_search_calls": 0,
|
||||
"mcp_calls": 0,
|
||||
"document_search_calls": 0,
|
||||
"image_generation_calls": 0
|
||||
},
|
||||
"context_details": {
|
||||
"input_tokens": 6902,
|
||||
"output_tokens": 732
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"health_model": "xai.grok-4.20-reasoning",
|
||||
"citation_urls": [
|
||||
"https://paraibaonline.com.br/brasil/2026/10/14/o-que-tem-nas-capas-dos-jornais-do-brasil-nesta-quarta-feira-14-10/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"requested_model": "xai.grok-4.3",
|
||||
"returned_model": "xai.grok-4.3",
|
||||
"tested_at": "2026-10-14T11:26:26.105038-03:00",
|
||||
"app_http": 200,
|
||||
"oci_http": 200,
|
||||
"seconds": 9.24,
|
||||
"websearch_confirmed": true,
|
||||
"completed_searches": 2,
|
||||
"source_count": 10,
|
||||
"citation_count": 1,
|
||||
"tool_events": [
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 8092,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 1280
|
||||
},
|
||||
"output_tokens": 898,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 734
|
||||
},
|
||||
"total_tokens": 8990,
|
||||
"num_sources_used": 0,
|
||||
"num_server_side_tools_used": 2,
|
||||
"cost_in_usd_ticks": 210160000,
|
||||
"server_side_tool_usage_details": {
|
||||
"web_search_calls": 2,
|
||||
"x_search_calls": 0,
|
||||
"x_posts_fetched": 0,
|
||||
"x_users_fetched": 0,
|
||||
"code_interpreter_calls": 0,
|
||||
"file_search_calls": 0,
|
||||
"mcp_calls": 0,
|
||||
"document_search_calls": 0,
|
||||
"image_generation_calls": 0
|
||||
},
|
||||
"context_details": {
|
||||
"input_tokens": 6926,
|
||||
"output_tokens": 456
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"health_model": "xai.grok-4.3",
|
||||
"citation_urls": [
|
||||
"https://www.cnnbrasil.com.br/nacional/brasil/temporais-e-chuva-forte-veja-como-fica-o-tempo-no-brasil-nesta-quarta-14/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"requested_model": "xai.grok-4.6",
|
||||
"returned_model": "xai.grok-4.6",
|
||||
"tested_at": "2026-10-14T11:26:35.843431-03:00",
|
||||
"app_http": 200,
|
||||
"oci_http": 200,
|
||||
"seconds": 35.17,
|
||||
"websearch_confirmed": true,
|
||||
"completed_searches": 1,
|
||||
"source_count": 10,
|
||||
"citation_count": 1,
|
||||
"tool_events": [
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 8712,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 1280
|
||||
},
|
||||
"output_tokens": 878,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 732
|
||||
},
|
||||
"total_tokens": 9590,
|
||||
"num_sources_used": 0,
|
||||
"num_server_side_tools_used": 1,
|
||||
"cost_in_usd_ticks": 257720000,
|
||||
"server_side_tool_usage_details": {
|
||||
"web_search_calls": 1,
|
||||
"x_search_calls": 0,
|
||||
"x_posts_fetched": 0,
|
||||
"x_users_fetched": 0,
|
||||
"code_interpreter_calls": 0,
|
||||
"file_search_calls": 0,
|
||||
"mcp_calls": 0,
|
||||
"document_search_calls": 0,
|
||||
"image_generation_calls": 0
|
||||
},
|
||||
"context_details": {
|
||||
"input_tokens": 7510,
|
||||
"output_tokens": 535
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"health_model": "xai.grok-4.6",
|
||||
"citation_urls": [
|
||||
"https://www.cnnbrasil.com.br/nacional/brasil/temporais-e-chuva-forte-veja-como-fica-o-tempo-no-brasil-nesta-quarta-14/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"requested_model": "xai.grok-4.7",
|
||||
"returned_model": "xai.grok-4.7",
|
||||
"tested_at": "2026-10-14T11:24:43.909978-03:00",
|
||||
"app_http": 200,
|
||||
"oci_http": 200,
|
||||
"seconds": 12.97,
|
||||
"websearch_confirmed": true,
|
||||
"completed_searches": 3,
|
||||
"source_count": 20,
|
||||
"citation_count": 1,
|
||||
"tool_events": [
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"action": "search"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"input_tokens": 16570,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 3456
|
||||
},
|
||||
"output_tokens": 1208,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 988
|
||||
},
|
||||
"total_tokens": 17778,
|
||||
"num_sources_used": 0,
|
||||
"num_server_side_tools_used": 3,
|
||||
"cost_in_usd_ticks": 502040000,
|
||||
"server_side_tool_usage_details": {
|
||||
"web_search_calls": 3,
|
||||
"x_search_calls": 0,
|
||||
"x_posts_fetched": 0,
|
||||
"x_users_fetched": 0,
|
||||
"code_interpreter_calls": 0,
|
||||
"file_search_calls": 0,
|
||||
"mcp_calls": 0,
|
||||
"document_search_calls": 0,
|
||||
"image_generation_calls": 0
|
||||
},
|
||||
"context_details": {
|
||||
"input_tokens": 14183,
|
||||
"output_tokens": 1022
|
||||
}
|
||||
},
|
||||
"error": null,
|
||||
"health_model": "xai.grok-4.7",
|
||||
"citation_urls": [
|
||||
"https://ndmais.com.br/tempo/tempestades-1-342-municipios-alerta-laranja-quarta-feira-14/"
|
||||
]
|
||||
}
|
||||
],
|
||||
"aggregate_usage": {
|
||||
"input_tokens": 69948,
|
||||
"output_tokens": 6426,
|
||||
"total_tokens": 76374,
|
||||
"num_server_side_tools_used": 13,
|
||||
"cost_in_usd_ticks": 1828201000
|
||||
},
|
||||
"test_runtime": {
|
||||
"python": "3.12.7",
|
||||
"fastapi": "0.143.1",
|
||||
"starlette": "1.8.0",
|
||||
"httpx": "0.28.1",
|
||||
"requests": "2.34.2"
|
||||
}
|
||||
}
|
||||
2
requirements-test.txt
Normal file
2
requirements-test.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
-r requirements.txt
|
||||
httpx==0.28.1
|
||||
@@ -2,3 +2,5 @@ const form=document.querySelector('#form'),box=document.querySelector('#messages
|
||||
function formatAnswer(div,text){div.textContent='';const pattern=/\*\*([^*]+)\*\*|\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g;let last=0;for(const m of text.matchAll(pattern)){div.append(document.createTextNode(text.slice(last,m.index)));const el=document.createElement(m[1]?'strong':'a');el.textContent=m[1]||m[2];if(!m[1]){el.href=m[3];el.target='_blank';el.rel='noopener noreferrer';}div.append(el);last=m.index+m[0].length;}div.append(document.createTextNode(text.slice(last)));}
|
||||
function turn(text,role){const div=document.createElement('div');div.className='turn '+role;div.textContent=text;box.append(div);return div;}
|
||||
form.addEventListener('submit',async e=>{e.preventDefault();const message=prompt.value.trim();if(!message)return;turn(message,'user');prompt.value='';send.disabled=true;status.textContent='Consultando Grok e pesquisando na web…';try{const r=await fetch('./api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message,history:history.slice(-12)})});if(!r.ok)throw Error(`Erro HTTP ${r.status}`);const d=await r.json();const div=turn(d.answer||'Sem texto na resposta.','assistant');formatAnswer(div,d.answer||'Sem texto na resposta.');const meta=document.createElement('div');meta.className='meta';meta.textContent=`${d.seconds}s · ${d.web_search_calls.length} chamadas de busca · ${d.citations.length} citações`;div.append(meta);for(const c of d.citations){try{const url=new URL(c.url);if(!['https:','http:'].includes(url.protocol))continue;const a=document.createElement('a');a.href=url.href;a.target='_blank';a.rel='noopener noreferrer';a.textContent=c.title && c.title!=='1'?c.title:url.hostname;div.append(document.createElement('br'),a);}catch{}}const details=document.createElement('details');const summary=document.createElement('summary');summary.textContent='Evidências da ferramenta';const pre=document.createElement('pre');pre.textContent=JSON.stringify({response_id:d.response_id,usage:d.usage,web_search_calls:d.web_search_calls},null,2);details.append(summary,pre);div.append(details);history.push({role:'user',content:message},{role:'assistant',content:d.answer});status.textContent='Pesquisa concluída';}catch(err){turn(err.message,'assistant');status.textContent='Não foi possível concluir';}finally{send.disabled=false;prompt.focus();}});
|
||||
|
||||
fetch('./health').then(r=>r.json()).then(d=>{document.querySelector('.badge').textContent='OCI · '+d.model.replace('xai.','');}).catch(()=>{});
|
||||
|
||||
112
test_models.py
Normal file
112
test_models.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""Teste pago da mesma aplicação ASGI com modelos Grok via OCI.
|
||||
|
||||
Executar com variáveis de .env exportadas. Concorrência máxima: 2.
|
||||
Uma chamada por modelo, sem retries; até 2400 tokens de saída e timeout de 150s.
|
||||
"""
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import importlib.metadata
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
MODELS = [
|
||||
'xai.grok-4.20-0309-non-reasoning', 'xai.grok-4.20-0309-reasoning',
|
||||
'xai.grok-4.20-non-reasoning', 'xai.grok-4.20-reasoning',
|
||||
'xai.grok-4.3', 'xai.grok-4.6', 'xai.grok-4.7',
|
||||
]
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
|
||||
def worker(model):
|
||||
os.environ['OCI_MODEL'] = model
|
||||
from fastapi.testclient import TestClient
|
||||
import app
|
||||
captured = {}
|
||||
original = app.requests.post
|
||||
def observe(*args, **kwargs):
|
||||
response = original(*args, **kwargs)
|
||||
captured['http'] = response.status_code
|
||||
captured['body'] = response.json()
|
||||
return response
|
||||
app.requests.post = observe
|
||||
now = datetime.now(ZoneInfo('America/Sao_Paulo'))
|
||||
message = (f'Use web_search para encontrar uma notícia publicada hoje ({now.date()}). '
|
||||
'Faça uma consulta curta e cite uma fonte com URL e data. Responda em até 100 palavras. '
|
||||
'Se não encontrar notícia de hoje, declare isso.')
|
||||
started = time.monotonic()
|
||||
with TestClient(app.app) as client:
|
||||
health = client.get('/health').json()
|
||||
response = client.post('/api/chat', auth=('poc', os.environ['APP_PASSWORD']), json={'message': message})
|
||||
data = response.json()
|
||||
calls = data.get('web_search_calls', [])
|
||||
completed = [c for c in calls if c.get('status') == 'completed']
|
||||
# Exigir ação real de pesquisa, fontes e citações: HTTP 200 sozinho não basta.
|
||||
searches = [c for c in completed if c.get('action', {}).get('type') == 'search']
|
||||
sources = [s for c in searches for s in c.get('action', {}).get('sources', [])]
|
||||
confirmed = response.status_code == 200 and bool(searches) and bool(sources) and bool(data.get('citations'))
|
||||
body = captured.get('body', {})
|
||||
error = body.get('error', {})
|
||||
error_text = str(error.get('message', ''))
|
||||
for key in ('OCI_API_KEY', 'OCI_PROJECT_ID', 'APP_PASSWORD'):
|
||||
value = os.environ.get(key)
|
||||
if value: error_text = error_text.replace(value, '[redacted]')
|
||||
error_text = re.sub(r'ocid1\.[\w.-]+', '[redacted]', error_text)
|
||||
row = {'requested_model': model, 'returned_model': data.get('model'), 'tested_at': now.isoformat(),
|
||||
'app_http': response.status_code, 'oci_http': captured.get('http'),
|
||||
'seconds': round(time.monotonic()-started, 2), 'websearch_confirmed': confirmed,
|
||||
'completed_searches': len(searches), 'source_count': len(sources),
|
||||
'citation_count': len(data.get('citations', [])),
|
||||
'tool_events': [{'status': c.get('status'), 'action': c.get('action', {}).get('type')} for c in calls],
|
||||
'usage': data.get('usage'),
|
||||
'citation_urls': sorted({c['url'] for c in data.get('citations', []) if c.get('url')}),
|
||||
'error': error_text or data.get('detail'), 'health_model': health['model']}
|
||||
folder = ROOT/'evidence'/'model-tests-raw'
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
(folder/(model+'.json')).write_text(json.dumps({'message': message, 'app_response': data, 'upstream': captured}, ensure_ascii=False, indent=2))
|
||||
print(json.dumps(row, ensure_ascii=False))
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--worker', choices=MODELS)
|
||||
parser.add_argument('--models', nargs='+', choices=MODELS, default=MODELS)
|
||||
args = parser.parse_args()
|
||||
if args.worker:
|
||||
worker(args.worker)
|
||||
return
|
||||
def run(model):
|
||||
result = subprocess.run([sys.executable, str(Path(__file__).resolve()), '--worker', model],
|
||||
capture_output=True, text=True, timeout=180, cwd=ROOT)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f'{model}: worker failed (exit {result.returncode})')
|
||||
row = json.loads(result.stdout)
|
||||
print(f"{model}: app={row['app_http']} OCI={row['oci_http']} search={row['websearch_confirmed']} {row['seconds']}s", flush=True)
|
||||
return row
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
|
||||
rows = list(pool.map(run, args.models))
|
||||
previous = ROOT/'evidence/grok-websearch-models.json'
|
||||
if previous.exists():
|
||||
old = json.loads(previous.read_text())
|
||||
today = datetime.now(ZoneInfo('America/Sao_Paulo')).date().isoformat()
|
||||
if old.get('tested_at', '').startswith(today):
|
||||
rows += [row for row in old['results'] if row['requested_model'] not in args.models]
|
||||
rows.sort(key=lambda row: MODELS.index(row['requested_model']))
|
||||
report = {'tested_at': datetime.now(ZoneInfo('America/Sao_Paulo')).isoformat(),
|
||||
'region': 'us-chicago-1', 'endpoint': '/v1/responses',
|
||||
'method': 'POST /api/chat via FastAPI TestClient; inferência OCI real; um processo isolado por modelo',
|
||||
'test_runtime': {'python': sys.version.split()[0], **{name: importlib.metadata.version(name) for name in ['fastapi', 'starlette', 'httpx', 'requests']}},
|
||||
'max_output_tokens': 2400, 'concurrency': 2, 'retries': 0,
|
||||
'confirmation_criteria': 'HTTP 200, search completed, fontes não vazias e citações estruturadas',
|
||||
'results': rows,
|
||||
'aggregate_usage': {key: sum((r.get('usage') or {}).get(key, 0) or 0 for r in rows)
|
||||
for key in ['input_tokens', 'output_tokens', 'total_tokens', 'num_server_side_tools_used', 'cost_in_usd_ticks']}}
|
||||
(ROOT/'evidence').mkdir(exist_ok=True)
|
||||
(ROOT/'evidence/grok-websearch-models.json').write_text(json.dumps(report, ensure_ascii=False, indent=2)+'\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user