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_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_API_KEY=replace-with-oci-generative-ai-api-key
|
||||||
OCI_PROJECT_ID=replace-with-active-chicago-project-id
|
OCI_PROJECT_ID=replace-with-active-chicago-project-id
|
||||||
APP_PASSWORD=replace-with-a-strong-password
|
APP_PASSWORD=replace-with-a-strong-password
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ render_check.py
|
|||||||
evidence/*
|
evidence/*
|
||||||
!evidence/RELATORIO.md
|
!evidence/RELATORIO.md
|
||||||
!evidence/validation-summary.json
|
!evidence/validation-summary.json
|
||||||
|
!evidence/grok-websearch-models.json
|
||||||
|
|||||||
51
README.md
51
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Grok Web Search via OCI
|
# 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
|
## Estrutura
|
||||||
|
|
||||||
@@ -11,14 +11,17 @@ POC de chatbot com interface JavaScript e backend Python/FastAPI. Usa `xai.grok-
|
|||||||
│ ├── index.html # Interface e estilos
|
│ ├── index.html # Interface e estilos
|
||||||
│ └── app.js # Chat, histórico e citações
|
│ └── app.js # Chat, histórico e citações
|
||||||
├── requirements.txt
|
├── requirements.txt
|
||||||
|
├── requirements-test.txt # TestClient para matriz de modelos
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
├── .dockerignore
|
├── .dockerignore
|
||||||
├── .gitignore
|
├── .gitignore
|
||||||
├── .env.example # Variáveis sem credenciais
|
├── .env.example # Variáveis sem credenciais
|
||||||
├── validate.py # Validação REST com inferência real
|
├── validate.py # Validação REST com inferência real
|
||||||
|
├── test_models.py # Matriz de modelos pela mesma aplicação
|
||||||
└── evidence/
|
└── evidence/
|
||||||
├── RELATORIO.md # Resultados da POC
|
├── 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
|
## Configuração
|
||||||
@@ -37,6 +40,7 @@ Preencha `.env`:
|
|||||||
| Variável | Finalidade |
|
| Variável | Finalidade |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `OCI_BASE_URL` | `https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/v1` |
|
| `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_API_KEY` | Chave OCI Generative AI |
|
||||||
| `OCI_PROJECT_ID` | Identificador de projeto ativo em Chicago |
|
| `OCI_PROJECT_ID` | Identificador de projeto ativo em Chicago |
|
||||||
| `APP_PASSWORD` | Senha forte para o usuário `poc` |
|
| `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`.
|
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
|
## API REST
|
||||||
|
|
||||||
`POST /api/chat` com HTTP Basic. Na publicação com prefixo, use `POST /grok-poc/api/chat`.
|
`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)
|
message:str=Field(min_length=1,max_length=8000)
|
||||||
history:list[Turn]=Field(default_factory=list,max_length=12)
|
history:list[Turn]=Field(default_factory=list,max_length=12)
|
||||||
@app.get('/health')
|
@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)])
|
@app.get('/',dependencies=[Depends(authorize)])
|
||||||
def index():return FileResponse('static/index.html')
|
def index():return FileResponse('static/index.html')
|
||||||
@app.get('/app.js',dependencies=[Depends(authorize)])
|
@app.get('/app.js',dependencies=[Depends(authorize)])
|
||||||
@@ -32,7 +32,7 @@ async def chat(q:Chat):
|
|||||||
base=os.environ['OCI_BASE_URL'].rstrip('/')
|
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')
|
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'))
|
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.',
|
'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}
|
'tools':[{'type':'web_search'}],'store':False,'max_output_tokens':2400}
|
||||||
started=time.monotonic()
|
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 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.
|
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 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;}
|
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();}});
|
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