Download app.py from Jarbas/jevasio: direct link, hf CLI and curl.
- Browser
- Download file 25.2 kB
-
https://huggingface.co/spaces/Jarbas/jevasio/resolve/main/app.py
- Command line
-
hf download hf://spaces/Jarbas/jevasio/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Jarbas/jevasio/resolve/main/app.py
25.2 kB
| """Jevásio on Hugging Face Spaces (Gradio, ZeroGPU). | |
| A state and a set of typed questions in, a probability per option out, in one forward pass. The model is loaded through | |
| Kev's own loader (kev.checkpoint), the same path `python -m kev.serve --run TigreGotico/jevasio-0.8b` takes, and | |
| requests go through kev.api and kev.model.admit exactly as /v1/systemone does. Both sizes load in bf16 at start-up, and | |
| each forward pass runs inside @spaces.GPU, so a request holds a GPU only for that pass. | |
| """ | |
| import html | |
| import json | |
| import os | |
| import time | |
| from dataclasses import replace | |
| import gradio as gr | |
| import spaces | |
| import torch | |
| from pydantic import ValidationError | |
| from kev.api import SystemOneRequest, output_tokens, render, to_answers, to_record | |
| from kev.checkpoint import Checkpoint, LoadOptions | |
| from kev.model import admit | |
| MODELS = {"Jevásio-4B-V2": "TigreGotico/jevasio-4b-v2", "Jevásio-0.8B-V2": "TigreGotico/jevasio-0.8b-v2", | |
| "Jevásio-4B": "TigreGotico/jevasio-4b", "Jevásio-0.8B": "TigreGotico/jevasio-0.8b"} | |
| # JEVASIO_MODELS="Jevásio-4B-V2,Jevásio-0.8B-V2" serves only the named models. | |
| if os.environ.get("JEVASIO_MODELS"): | |
| MODELS = {name: MODELS[name] for name in os.environ["JEVASIO_MODELS"].split(",")} | |
| DEFAULT = "Jevásio-4B-V2" | |
| LINK_08B = "https://huggingface.co/TigreGotico/jevasio-0.8b" | |
| LINK_4B = "https://huggingface.co/TigreGotico/jevasio-4b" | |
| LINK_08B_V2 = "https://huggingface.co/TigreGotico/jevasio-0.8b-v2" | |
| LINK_4B_V2 = "https://huggingface.co/TigreGotico/jevasio-4b-v2" | |
| LINK_DATA_V2 = "https://huggingface.co/datasets/TigreGotico/jevasio-decisions-v2" | |
| LINK_DATA = "https://huggingface.co/datasets/TigreGotico/jevasio-decisions" | |
| LOADED = {} | |
| for name, repo in MODELS.items(): | |
| t0 = time.perf_counter() | |
| checkpoint = Checkpoint(repo) | |
| opts = LoadOptions.from_env() | |
| if opts.dtype is None: | |
| opts = replace(opts, dtype=torch.bfloat16) | |
| # ZeroGPU has no GPU at start-up: load on the CPU, then move. ZeroGPU defers the move until a @spaces.GPU call, and | |
| # Kev builds its input tensors on model.device. Off ZeroGPU, JEVASIO_DEVICE_<SIZE> places a model, e.g. | |
| # JEVASIO_DEVICE_4B=cpu keeps the 4B in RAM on a laptop GPU too small for it (JEVASIO_DEVICE_4B_V2 for the 4B V2). | |
| key = name.split("-", 1)[1].replace(".", "").replace("-", "_").upper() | |
| device = "cuda" if os.environ.get("SPACES_ZERO_GPU") else os.environ.get( | |
| f"JEVASIO_DEVICE_{key}", "cuda" if torch.cuda.is_available() else "cpu") | |
| tok, model = checkpoint.load("cpu", opts) | |
| model.to(device) | |
| model.device = torch.device(device) | |
| LOADED[name] = tok, model | |
| print(f"[jevasio] {repo}: base={checkpoint.meta.base}@{checkpoint.meta.base_revision} dtype={model.dtype} " | |
| f"temperature={model.head.temperature:.2f} loaded in {time.perf_counter() - t0:.0f} s", flush=True) | |
| def forward(name, enc): | |
| return [p.float().cpu() for p in LOADED[name][1].probs(enc)] | |
| class RequestError(ValueError): | |
| """A request the model cannot answer: bad JSON, a bad question, or a state that is too long.""" | |
| def parse_request(payload): | |
| try: | |
| return SystemOneRequest.model_validate(payload) | |
| except ValidationError as e: | |
| first = e.errors()[0] | |
| where = ".".join(str(x) for x in first.get("loc", ())) | |
| raise RequestError(f"Pedido inválido em `{where}`: {first.get('msg')}") from None | |
| def systemone(req, name=DEFAULT): | |
| """The /v1/systemone response body kev.serve returns for this request, plus the latency of the forward pass.""" | |
| if name not in LOADED: | |
| raise RequestError(f"Modelo desconhecido: {name}. Opções: {', '.join(MODELS)}.") | |
| tokenizer, model = LOADED[name] | |
| rec, meta = to_record(req) | |
| try: | |
| enc = admit(model, tokenizer, rec) | |
| except ValueError as e: | |
| raise RequestError(str(e)) from None | |
| start = time.perf_counter() | |
| probs = forward(name, enc) | |
| ms = (time.perf_counter() - start) * 1000 | |
| answers = to_answers([p.tolist() for p in probs], meta) | |
| return {"model": MODELS[name], "answers": answers, | |
| "usage": {"input_tokens": len(enc["ids"]), "output_tokens": output_tokens(tokenizer, answers)}, | |
| "latency_ms": round(ms, 1)} | |
| # ------------------------------------------------------------------ rendering | |
| CARD = ("border:1px solid var(--border-color-primary);border-radius:10px;padding:12px 14px;margin-bottom:10px;" | |
| "background:var(--background-fill-secondary);") | |
| TRACK = "position:relative;display:block;height:8px;border-radius:999px;background:var(--border-color-primary);overflow:hidden;" | |
| TYPE_LABEL = {"choice": "escolha", "noul": "sim/não", "score": "escala"} | |
| def bar(label, p, top): | |
| fill = "var(--color-accent)" if top else "var(--body-text-color-subdued)" | |
| weight = "600" if top else "400" | |
| width = max(0.0, min(1.0, p)) * 100 | |
| return ('<div style="display:grid;grid-template-columns:minmax(0,12rem) minmax(0,1fr) 3.2rem;align-items:center;' | |
| 'gap:0 12px;font-size:13px;line-height:24px">' | |
| f'<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:{weight}" ' | |
| f'title="{html.escape(label)}">{html.escape(label)}</span>' | |
| f'<span style="{TRACK}"><span style="position:absolute;top:0;bottom:0;left:0;border-radius:999px;' | |
| f'background:{fill};width:{width:.1f}%"></span></span>' | |
| f'<span style="text-align:right;font-variant-numeric:tabular-nums;font-weight:{weight}">{p:.2f}</span></div>') | |
| def render_answers(req, resp): | |
| usage = resp["usage"] | |
| parts = ['<div style="font-size:12px;color:var(--body-text-color-subdued);margin:6px 0 10px">' | |
| f'{len(resp["answers"])} pergunta(s) · {usage["input_tokens"]} tokens de entrada · ' | |
| f'{resp["model"].split("/")[1]} · {resp["latency_ms"]:.0f} ms · nenhum texto gerado</div>'] | |
| for qid, a in resp["answers"].items(): | |
| instructions = render(req.questions[qid].instructions) | |
| if a["type"] == "noul": | |
| p = a["noul"] | |
| headline, detail = ("sim" if p >= 0.5 else "não"), f"p(sim) {p:.2f}" | |
| rows = bar("sim", p, p >= 0.5) + bar("não", 1 - p, p < 0.5) | |
| elif a["type"] == "choice": | |
| headline, detail = a["choice"], f"confiança {a['confidence']:.2f}" | |
| ranked = sorted(a["probabilities"].items(), key=lambda kv: -kv[1]) | |
| rows = "".join(bar(k, v, k == a["choice"]) for k, v in ranked) | |
| else: | |
| legend, dist = a["legend"], a["probabilities"] | |
| top = max(dist, key=dist.get) | |
| headline = f"{a['score']:.2f} de {len(legend) - 1}" | |
| detail = f"confiança {a['confidence']:.2f}" | |
| rows = "".join(bar(f"{i}. {legend[str(i)]}", dist[str(i)], str(i) == top) for i in range(len(legend))) | |
| parts.append(f'<div style="{CARD}"><div style="display:flex;align-items:baseline;gap:8px;flex-wrap:wrap">' | |
| f'<span style="font-family:var(--font-mono);font-size:13px;font-weight:600">{html.escape(qid)}</span>' | |
| f'<span style="font-size:11px;color:var(--body-text-color-subdued)">· {TYPE_LABEL[a["type"]]}</span></div>' | |
| f'<div style="font-size:13px;margin:2px 0 6px">{html.escape(instructions)}</div>' | |
| '<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:8px">' | |
| f'<span style="font-size:17px;font-weight:600">{html.escape(str(headline))}</span>' | |
| f'<span style="font-size:12px;color:var(--body-text-color-subdued)">{detail}</span></div>{rows}</div>') | |
| return "".join(parts) | |
| def error_html(message): | |
| return (f'<div style="{CARD}border-color:var(--error-border-color, #d33);">' | |
| f'<b>Erro:</b> {html.escape(message)}</div>') | |
| # ------------------------------------------------------------------ question builder | |
| def parse_state(text): | |
| """Plain text, or a JSON object or array.""" | |
| t = (text or "").strip() | |
| if t[:1] in "{[": | |
| try: | |
| return json.loads(t) | |
| except json.JSONDecodeError: | |
| pass | |
| return t | |
| def parse_options(text): | |
| """One option per line, `name: description` or just `name`.""" | |
| criteria = {} | |
| for line in (text or "").splitlines(): | |
| name, _, desc = line.partition(":") | |
| name, desc = name.strip(), desc.strip() | |
| if name: | |
| criteria[name] = desc or None | |
| return criteria | |
| def build_request(state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels): | |
| """The System One request the builder describes. A question with an empty id is left out.""" | |
| ids = [i.strip() for i in (choice_id, noul_id, score_id) if i and i.strip()] | |
| if len(ids) != len(set(ids)): | |
| raise RequestError("Cada pergunta precisa de um id diferente.") | |
| questions = {} | |
| if choice_id.strip(): | |
| questions[choice_id.strip()] = {"type": "choice", "instructions": choice_instr, "criteria": parse_options(choice_opts)} | |
| if noul_id.strip(): | |
| questions[noul_id.strip()] = {"type": "noul", "instructions": noul_instr} | |
| if score_id.strip(): | |
| levels = [line.strip() for line in (score_levels or "").splitlines() if line.strip()] | |
| questions[score_id.strip()] = {"type": "score", "instructions": score_instr, "criteria": levels} | |
| if not (state or "").strip(): | |
| raise RequestError("Escreva um estado: o texto ou o objeto JSON que o modelo lê.") | |
| if not questions: | |
| raise RequestError("Defina pelo menos uma pergunta (um id não vazio).") | |
| return {"state": parse_state(state), "model": "kev-latest", "questions": questions} | |
| def decide(name, state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels): | |
| """Answer the builder's questions about the state. Returns the rendered answers, the request and the response.""" | |
| try: | |
| payload = build_request(state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels) | |
| req = parse_request(payload) | |
| resp = systemone(req, name) | |
| except RequestError as e: | |
| return error_html(str(e)), {}, {"error": str(e)} | |
| return render_answers(req, resp), payload, resp | |
| def decide_raw(name, request_json): | |
| """Answer a raw System One request (JSON text). Returns a status line and the response or an error object.""" | |
| try: | |
| payload = json.loads(request_json or "") | |
| except json.JSONDecodeError as e: | |
| message = f"JSON inválido: {e}" | |
| return f"**Erro:** {message}", {"error": message} | |
| if not isinstance(payload, dict): | |
| message = 'O pedido tem de ser um objeto JSON com "state" e "questions".' | |
| return f"**Erro:** {message}", {"error": message} | |
| try: | |
| resp = systemone(parse_request(payload), name) | |
| except RequestError as e: | |
| return f"**Erro:** {e}", {"error": str(e)} | |
| return f"Respondido em {resp['latency_ms']:.0f} ms.", resp | |
| # ------------------------------------------------------------------ examples | |
| EXAMPLES = [ | |
| { | |
| "name": "Apoio ao cliente", | |
| "state": "Bom dia. Fui cobrado duas vezes pela encomenda 1182 e o dinheiro já saiu da minha conta. " | |
| "Preciso que devolvam uma das cobranças ainda hoje, porque tenho a renda para pagar amanhã.", | |
| "choice": ("team", "Que equipa deve tratar deste pedido?", | |
| "billing: Cobranças, faturas e reembolsos\nshipping: Entregas e atrasos\n" | |
| "returns: Trocas e devoluções de artigos\ntechnical: Problemas no site ou na aplicação"), | |
| "noul": ("urgent", "Este pedido precisa de resposta hoje?"), | |
| "score": ("frustration", "Quão frustrado está o cliente?", "Calmo\nIncomodado\nMuito zangado"), | |
| }, | |
| { | |
| "name": "Sentimento", | |
| "state": "Fomos jantar ao restaurante novo da Baixa. A comida chegou fria, o empregado esqueceu-se das bebidas " | |
| "e no fim ainda nos cobraram o couvert que não pedimos. Não voltamos.", | |
| "choice": ("sentiment", "Qual é o sentimento geral do comentário?", | |
| "positive: Positivo\nneutral: Neutro ou misto\nnegative: Negativo"), | |
| "noul": ("recommends", "O autor recomenda o restaurante?"), | |
| "score": ("intensity", "Com que intensidade o autor exprime a opinião?", "Fraca\nModerada\nForte"), | |
| }, | |
| { | |
| "name": "Variedade do português", | |
| "state": "Estou a escrever-te do autocarro porque o meu telemóvel ficou sem rede no comboio. " | |
| "Logo à noite passo aí para irmos tomar um pequeno-almoço tardio, está bem?", | |
| "choice": ("variety", "Em que variedade do português está escrito este texto?", | |
| "pt_pt: Português europeu\npt_br: Português do Brasil"), | |
| "noul": ("informal", "O texto usa um tom informal?"), | |
| "score": ("register", "Qual é o registo do texto?", "Muito informal\nInformal\nNeutro\nFormal\nMuito formal"), | |
| }, | |
| { | |
| "name": "Registo", | |
| "state": "Exmo. Senhor Diretor, venho por este meio solicitar a V. Ex.ª a marcação de uma reunião, " | |
| "com a maior brevidade possível, para tratar da renovação do contrato de prestação de serviços.", | |
| "choice": ("text_type", "Que tipo de texto é este?", | |
| "letter: Carta ou correio formal\nchat: Mensagem entre amigos\nnews: Notícia\nreview: Crítica ou opinião"), | |
| "noul": ("asks_action", "O texto pede ao destinatário que faça alguma coisa?"), | |
| "score": ("register", "Qual é o registo do texto?", "Muito informal\nInformal\nNeutro\nFormal\nMuito formal"), | |
| }, | |
| { | |
| "name": "Mensagem a um serviço (V2)", | |
| "state": '{"message": "Boa tarde. Recebi a fatura de março com um valor de 89 euros, quando o meu plano é de 35. ' | |
| 'Já liguei duas vezes e ninguém resolve. Se não corrigirem até amanhã, cancelo o contrato."}', | |
| "choice": ("department", "Que departamento deve tratar desta mensagem?", | |
| "delivery: Entregas e envios\ncancellation: Cancelamentos e rescisões\nsales: Vendas e novos serviços\n" | |
| "technical: Assistência técnica\ncomplaints: Reclamações\nbilling: Faturação e pagamentos"), | |
| "noul": ("", ""), | |
| "score": ("urgency", "Quão urgente é esta mensagem?", | |
| "Pode esperar dias\nResponder nos próximos dias\nResponder hoje\nImediato: há risco ou dano"), | |
| }, | |
| { | |
| "name": "Comando de voz (V2)", | |
| "state": '{"request": "acorda-me amanhã às sete e meia que tenho de apanhar o comboio"}', | |
| "choice": ("scenario", "A que domínio pertence este comando?", | |
| "alarm: Alarmes\ncalendar: Agenda\ntransport: Transportes\nweather: Meteorologia\n" | |
| "music: Preferências de música\niot: Casa inteligente"), | |
| "noul": ("", ""), | |
| "score": ("", "", ""), | |
| }, | |
| { | |
| "name": "Secção do jornal (V2)", | |
| "state": '{"title": "Câmara do Porto aprova novas regras para o alojamento local na Baixa", ' | |
| '"text": "A Câmara Municipal do Porto aprovou esta terça-feira um regulamento que suspende novos registos de ' | |
| 'alojamento local em seis freguesias do centro histórico durante dois anos."}', | |
| "choice": ("section", "Em que secção do jornal saiu este artigo?", | |
| "sports: Desporto\nlocal: Notícias locais\ntechnology: Tecnologia\neconomy: Economia\n" | |
| "culture: Cultura\npolitics: Política nacional\ntravel: Viagens e lazer\nsociety: Sociedade"), | |
| "noul": ("", ""), | |
| "score": ("", "", ""), | |
| }, | |
| { | |
| "name": "Área do direito (V2)", | |
| "state": '{"text": "Acordam no Supremo Tribunal de Justiça: a autora pede a condenação da ré no pagamento das ' | |
| 'retribuições em dívida e da indemnização pelo despedimento, que considera ilícito por falta de processo ' | |
| 'disciplinar."}', | |
| "choice": ("legal_area", "Em que área do direito se insere este acórdão?", | |
| "labour: Direito do trabalho\ndisputes: Contencioso\ncivil: Direito civil\ncriminal: Direito penal"), | |
| "noul": ("", ""), | |
| "score": ("", "", ""), | |
| }, | |
| { | |
| "name": "Relação entre frases (V2)", | |
| "state": '{"premise": "O concerto da banda no Coliseu de Lisboa esgotou em menos de uma hora.", ' | |
| '"hypothesis": "Os bilhetes para o concerto no Coliseu acabaram muito depressa."}', | |
| "choice": ("relation", "Que relação há entre as duas frases?", | |
| "entailment: A primeira implica a segunda\nnone: Nenhuma: a segunda não decorre da primeira\n" | |
| "paraphrase: As duas dizem o mesmo"), | |
| "noul": ("", ""), | |
| "score": ("similarity", "Quão parecido é o sentido das duas frases?", | |
| "sem relação\npouca relação\nalguma relação\nmuito parecidas\npraticamente iguais"), | |
| }, | |
| { | |
| "name": "Tradução com um facto trocado (V2)", | |
| "state": '{"original": "The museum opens at 9 a.m. on weekdays and is closed on Mondays.", ' | |
| '"translation": "O museu abre às 10 horas nos dias úteis e está fechado às segundas-feiras."}', | |
| "choice": ("", "", ""), | |
| "noul": ("faithful", "As duas versões dizem o mesmo?"), | |
| "score": ("", "", ""), | |
| }, | |
| { | |
| "name": "Linguagem ofensiva (V2)", | |
| "state": "Este árbitro é um palhaço, devia ter vergonha de aparecer em campo. Corja de incompetentes!", | |
| "choice": ("", "", ""), | |
| "noul": ("offensive", "A mensagem contém insultos ou linguagem ofensiva?"), | |
| "score": ("", "", ""), | |
| }, | |
| { | |
| "name": "Trocadilho (V2)", | |
| "state": "Qual é o animal que anda com uma pata? O pato.", | |
| "choice": ("", "", ""), | |
| "noul": ("pun", "A piada joga com um duplo sentido ou com o som das palavras?"), | |
| "score": ("", "", ""), | |
| }, | |
| ] | |
| def example_row(ex): | |
| return [ex["state"], *ex["choice"], *ex["noul"], *ex["score"]] | |
| RAW_EXAMPLE = json.dumps({ | |
| "state": {"request": "A encomenda 4471 chegou com o ecrã partido. Quero trocar o artigo, não quero o dinheiro de volta.", | |
| "channel": "email"}, | |
| "model": "kev-latest", | |
| "questions": { | |
| "team": {"type": "choice", "instructions": "Que equipa deve tratar deste pedido?", | |
| "criteria": {"billing": "Cobranças e reembolsos", "shipping": "Entregas", "returns": "Trocas"}}, | |
| "refund_requested": {"type": "noul", "instructions": "O cliente pede o reembolso do dinheiro?"}, | |
| "frustration": {"type": "score", "instructions": "Quão frustrado está o cliente?", | |
| "criteria": ["Calmo", "Incomodado", "Muito zangado"]}, | |
| }, | |
| }, ensure_ascii=False, indent=2) | |
| # ------------------------------------------------------------------ UI | |
| INTRO = f"""# Jevásio | |
| Modelo de decisão para português europeu. Recebe um **estado** (um texto ou um objeto JSON) e perguntas tipadas, e | |
| devolve uma probabilidade para cada opção de cada pergunta, numa só passagem pelo modelo. Não gera texto. | |
| Escolha o modelo: os 4B são mais exatos, os 0.8B são mais pequenos e rápidos. O [Jevásio-4B-V2]({LINK_4B_V2}) e o | |
| [Jevásio-0.8B-V2]({LINK_08B_V2}) são o [Jevásio-4B]({LINK_4B}) e o [Jevásio-0.8B]({LINK_08B}) treinados com mais tarefas: | |
| variedade do português por domínio, comandos de voz, secções de jornal, áreas do direito, relação entre frases, | |
| linguagem ofensiva, trocadilhos e traduções ou resumos com um facto trocado. Os exemplos marcados com (V2) são dessas | |
| tarefas. | |
| """ | |
| TEMPERATURES = ", ".join(f"T = {m.head.temperature:.2f} no {n}" for n, (_, m) in LOADED.items()) | |
| ABOUT = f"""Há três tipos de pergunta: `choice` (escolher uma opção com nome), `noul` (sim ou não) e `score` (um nível numa | |
| escala ordenada). Os nomes das chaves (campos do estado, ids das perguntas, nomes das opções) estão em inglês e os | |
| valores em português, como nos dados de treino. | |
| O Jevásio é o [Kev](https://github.com/jaredpalmer/kev) afinado com o conjunto | |
| [TigreGotico/jevasio-decisions]({LINK_DATA}), e o V2 com [TigreGotico/jevasio-decisions-v2]({LINK_DATA_V2}). A | |
| probabilidade usa a temperatura de calibração guardada no modelo ({TEMPERATURES}). | |
| Antes de definir um limiar, meça o modelo com os seus próprios dados. | |
| Licença CC BY-NC-SA 4.0: só para investigação não comercial. Não use o modelo em decisões com consequências legais, | |
| médicas, financeiras ou laborais sem revisão humana. | |
| """ | |
| PLACEHOLDER = ('<div style="border:1px dashed var(--border-color-primary);border-radius:10px;padding:28px 16px;' | |
| 'text-align:center;color:var(--body-text-color-subdued);font-size:13px">' | |
| 'As respostas aparecem aqui: um cartão por pergunta, com uma barra por opção.</div>') | |
| CSS = """ | |
| #col-container { max-width: 1180px; margin: 0 auto; } | |
| #decide-btn { min-height: 44px; } | |
| """ | |
| first = EXAMPLES[0] | |
| with gr.Blocks(title="Jevásio") as demo: | |
| with gr.Column(elem_id="col-container"): | |
| gr.Markdown(INTRO) | |
| model_choice = gr.Radio(list(MODELS), value=DEFAULT, label="Modelo") | |
| with gr.Tabs(): | |
| with gr.Tab("Perguntas"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| state_box = gr.Textbox(label="Estado (o texto que o modelo lê)", lines=5, max_lines=14, | |
| value=first["state"], | |
| placeholder="Uma mensagem, uma crítica, um artigo ou um objeto JSON.") | |
| with gr.Group(): | |
| gr.Markdown("**Pergunta de escolha** (`choice`)") | |
| with gr.Row(): | |
| choice_id = gr.Textbox(label="Id", value=first["choice"][0], scale=1) | |
| choice_instr = gr.Textbox(label="Instrução", value=first["choice"][1], scale=3) | |
| choice_opts = gr.Textbox(label="Opções (uma por linha, nome: descrição)", lines=4, | |
| value=first["choice"][2]) | |
| with gr.Group(): | |
| gr.Markdown("**Pergunta de sim/não** (`noul`)") | |
| with gr.Row(): | |
| noul_id = gr.Textbox(label="Id", value=first["noul"][0], scale=1) | |
| noul_instr = gr.Textbox(label="Instrução", value=first["noul"][1], scale=3) | |
| with gr.Group(): | |
| gr.Markdown("**Pergunta de escala** (`score`)") | |
| with gr.Row(): | |
| score_id = gr.Textbox(label="Id", value=first["score"][0], scale=1) | |
| score_instr = gr.Textbox(label="Instrução", value=first["score"][1], scale=3) | |
| score_levels = gr.Textbox(label="Níveis, do mais baixo ao mais alto (um por linha)", lines=3, | |
| value=first["score"][2]) | |
| gr.Markdown("Deixe o id vazio para omitir uma pergunta.") | |
| run_btn = gr.Button("Decidir", variant="primary", elem_id="decide-btn") | |
| with gr.Column(scale=1): | |
| answers_html = gr.HTML(value=PLACEHOLDER) | |
| with gr.Accordion("Pedido enviado (formato System One)", open=False): | |
| request_json = gr.JSON() | |
| with gr.Accordion("Resposta em bruto", open=False): | |
| response_json = gr.JSON() | |
| inputs = [state_box, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels] | |
| gr.Examples(examples=[example_row(ex) for ex in EXAMPLES], example_labels=[ex["name"] for ex in EXAMPLES], | |
| inputs=inputs, label="Exemplos") | |
| run_btn.click(fn=decide, inputs=[model_choice, *inputs], outputs=[answers_html, request_json, response_json], api_name="decide") | |
| with gr.Tab("Avançado"): | |
| gr.Markdown("Cole um pedido em JSON no formato System One (`POST /v1/systemone`) e veja a resposta em JSON.") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| raw_in = gr.Code(label="Pedido (JSON)", language="json", lines=18, value=RAW_EXAMPLE) | |
| raw_btn = gr.Button("Enviar", variant="primary") | |
| with gr.Column(scale=1): | |
| raw_status = gr.Markdown() | |
| raw_out = gr.JSON(label="Resposta (JSON)") | |
| raw_btn.click(fn=decide_raw, inputs=[model_choice, raw_in], outputs=[raw_status, raw_out], api_name="systemone") | |
| with gr.Tab("Sobre"): | |
| gr.Markdown(ABOUT) | |
| if __name__ == "__main__": | |
| demo.queue(default_concurrency_limit=1).launch(css=CSS) | |