Bootstrap the soccer analytics agent workshop. Starts the Oracle AI Database Free container, applies schema, loads the FIFA dataset, optionally trains models, populates LangChain OracleVS hybrid retrieval plus semantic memory, applies LangGraph OracleDB observability, and verifies OCI GenAI access. Use when starting the workshop or resetting a stale environment.
67
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
You are bootstrapping the soccer-analytics-agent workshop. Follow these steps strictly in order. Stop and surface the error on any failure.
Each step has a "If it fails:" hint pointing at the most common root cause we hit while building this. Skim them once before running so you know what to watch for.
This workshop is hybrid-first after ML inference. Coding agents must build and verify the LangChain OracleVS vector store (SOCCER_LANGCHAIN_DOCS) after PREDICCIONES_FINAL is loaded, and the final Grok 4 chat must ground explanatory answers with hybrid_retrieve or the startup hybrid_search(...) context before falling back to semantic-only memory. vector_search remains in the workshop as the baseline contrast: semantic similarity over semantic_memory only, without cached prediction documents or keyword/text scoring.
Every workshop build must also initialize langgraph-oracledb OracleStore tables and prove the agent stores individual execution steps in Oracle. The current agent remains a small Grok prompt-protocol loop, but each turn writes ordered step records (turn_start, grounding_retrieved, model_response, tool_call, tool_result, final_response) into the LangGraph OracleDB store under namespace ("soccer-agent", "agent-steps", session_id). The API exposes these rows at GET /observability/{session_id} for demo/debugging.
The Grok 4 final chat is a required workshop capability, so setup must not silently proceed with placeholder OCI values. When building the workshop, inspect .env after it exists. If any of these three values are missing or still contain REPLACE_ME, ask the user to provide them before running scripts/verify.py, scripts/smoke_test.py, or declaring the workshop ready:
OCI_GENAI_ENDPOINT — regional OCI GenAI Inference endpoint.OCI_GENAI_API_KEY — bearer API key beginning with sk-.OCI_COMPARTMENT_ID — compartment OCID used in the GenAI request body.If the user says they will provide these on the day of the workshop, leave placeholders in .env/.env.workshop.local, complete only the local Oracle/data/model setup, and explicitly report that Grok verification, smoke testing, and final public readiness remain blocked until those three values are pasted locally. Never write real OCI values into tracked files.
Check the container engine (Docker or Podman)
docker info >/dev/null 2>&1 && echo docker || (podman info >/dev/null 2>&1 && echo podman)docker group. Don't try to fix it from inside Claude Code — surface it.Ensure .env exists and gate on required OCI values
.env is missing at the repo root, copy from .env.example..env for OCI_GENAI_ENDPOINT, OCI_GENAI_API_KEY, and OCI_COMPARTMENT_ID. If any are missing or still contain REPLACE_ME, ask the user for those exact three values. The instructor may say they will provide them on workshop day; in that case, keep placeholders and continue only through local setup, but do not claim Grok/final readiness until they are supplied.docker/docker-compose.yml..env or .env.workshop.local only..env will make python-dotenv silently load nothing. Save as plain UTF-8 LF.Start the Oracle container
bash .claude/skills/soccer-workshop-setup/scripts/01_start_oracle.sh1525: another container is bound to host port 1525. Check docker ps (or podman ps). The compose file deliberately picks 1525 because 1521-1524 are commonly taken; if 1525 is also taken you need to edit docker-compose.yml and ORACLE_DSN.gvenzl/oracle-free:latest. Confirm uname -m reports arm64; if you are forcing the official image via ORACLE_IMAGE, unset it and re-run.Create the soccer user with workshop grants
bash .claude/skills/soccer-workshop-setup/scripts/setup.shCREATE MINING MODEL is also granted here — without it, step 8 below fails with ORA-01031: insufficient privileges deep inside DBMS_VECTOR.LOAD_ONNX_MODEL. Do NOT remove that grant.Load match data
uv run python scripts/setup_db.pyresults.csv, goalscorers.csv, shootouts.csv) in data/.kaggle datasets download -d martj42/international-football-results-from-1872-to-2017 -p data/ --unzip (needs ~/.kaggle/kaggle.json), or fetch the ZIP from the URL and unzip into data/.results.csv is < 1 KB the workshop will still run, but later steps (embed_match_facts.py, ML feature engineering, bulk predictions) will produce mostly empty output. Do not "fix" zero-row counts in stub mode — load the real Kaggle dataset instead.Prepare model artifacts
uv run python scripts/prepare_artifacts.pymodels/. This step validates those files and falls back to training from the loaded Oracle data only if the artifacts are missing or invalid.MATCH_RESULTS/GOALSCORERS were not loaded, dependencies are missing, or the local machine cannot train the XGBoost model. Re-run step 5 and inspect the training error.Apply memory schema and LangGraph observability store
uv run python scripts/init_memory.pylanggraph-oracledb OracleStore.setup() so the Oracle-backed step-observability tables exist before the app starts.langgraph-oracledb, confirm the uv environment installed the package from pyproject.toml/uv.lock.Load the ONNX embedding model (~30-90s)
uv run python scripts/load_onnx_model.pyVECTOR_EMBEDDING(...) SQL function expects an ONNX model with the tokenizer baked into the graph, NOT a vanilla transformer export. We learned this the hard way:
optimum-cli export onnx produces a model with input_ids: [batch_size, sequence_length] — both variable. Oracle rejects it with ORA-54426: Tensor "input_ids" contains multiple dimensions (2) of variable size.oml4py package on PyPI is a 4-file stub with no EmbeddingModel helper. The real oml4py ships with Oracle Database client, not pip.onnx2oracle (this is what scripts/load_onnx_model.py uses). It builds the right augmented ONNX (tokenizer + transformer + pooling + normalize).ORA-01031: step 4 was skipped or the soccer user lacks CREATE MINING MODEL.ORA-54426: something rewrote load_onnx_model.py to skip onnx2oracle. Don't go back to raw optimum-cli ONNX — it will not work.Load precomputed predictions
uv run python scripts/load_predictions.pyPopulate the LangChain OracleVS hybrid retrieval store
uv run python scripts/load_langchain_vectors.py --reset --demo-query "Spain Brazil World Cup prediction evidence"PREDICCIONES_FINAL, VW_TEAM_STATISTICS, and World Cup team/decade aggregates into LangChain Document rows in SOCCER_LANGCHAIN_DOCS using the langchain-oracledb PyPI package and the in-DB ONNX model.skipped because the agent still retrieves with the best available native hybrid or Oracle Text + vector reciprocal-rank-fusion path.langchain-oracledb is missing, or the ONNX model name in ORACLE_EMBED_MODEL is wrong.Embed match facts into semantic memory
uv run python scripts/embed_match_facts.pyHAVING COUNT(*) >= 3, so if you've only loaded a 3-row stub CSV (no real FIFA data), this inserts zero facts and reports Inserted 0 semantic facts. That's expected, not a failure.Optionally showcase Oracle Agent Memory SDK
uv run python scripts/showcase_oracle_agent_memory.pyoracleagentmemory PyPI package in the same database with user/agent/thread-scoped durable memory. If the alpha SDK changes its API, surface the error but do not block the core workshop; the custom memory tables remain the primary path.Run the verifier, observability check, and hybrid-vs-semantic contrast check
uv run python scripts/verify.pyPREDICCIONES_FINAL means the real prediction parquet was not prepared or loaded; re-run steps 6 and 9. A red on SOCCER_LANGCHAIN_DOCS means step 10 did not populate the hybrid retrieval store. A red on langgraph-oracledb OracleStore observability setup means step 7 did not initialize the LangGraph OracleDB store.uv run python .claude/skills/soccer-agent-toolbelt/tools/run_tool.py hybrid_retrieve '{"query":"Spain Brazil World Cup prediction evidence","limit":3}'uv run python .claude/skills/soccer-agent-toolbelt/tools/run_tool.py vector_search '{"query":"Spain Brazil World Cup prediction evidence","limit":3}'hybrid_retrieve returns LangChain OracleVS documents and can surface cached ML prediction rows with retrieval_mode; vector_search returns semantic-only facts from semantic_memory.uv run python scripts/smoke_test.py. In addition to a real predict_match call, this must print LangGraph OracleDB observability events including turn_start, tool_call, tool_result, and final_response.Build and polish the React front-end with the taste-skill (automatic for every attendee — do not skip unless --skip-ui-polish was passed)
frontend/. FastAPI serves the built frontend/dist/ if it exists, and falls back to the bundled soccer_agent/api/static/index.html otherwise.cd frontend && npm ci && npm run buildfrontend/dist/index.html + dist/assets/index-*.js,*.css, which soccer_agent/api/main.py mounts at /.node --version you got and that Node 18+ is required) and fall back to the committed default UI — main.py automatically serves soccer_agent/api/static/index.html when frontend/dist/ is absent. Do NOT fail the whole setup over a missing Node; the API still runs against the bundled static page.taste-skill (vendored in this repo at .claude/skills/taste-skill/, so it is available in the repo's Claude Code session) via the Skill tool, then refine the React front-end source in frontend/src/ against it (single accent, off-black base, suggestion chips, per-tool trace cards, typing skeleton, live status pill, tactile feedback, no emoji). Rebuild after refining: cd frontend && npm run build.POST /chat with body {session_id, message} → renders {session_id, reply, tool_trace[]}, where each trace item is {name, args, result}.GET /health → {oracle, grok_configured} (used to drive the status indicator).GET /observability/{sessionId} for ordered LangGraph OracleDB step records.DELETE /memory/{sessionId} for reset./chat, /predict, /health, /memory/...), so it is port-agnostic — keep it that way (do not hardcode a host/port).http://localhost:8000/, send "Predict Spain vs Brazil at a neutral venue.", and confirm the probability bar renders with the predict_match tool trace showing features_used: 92. Copy the returned session_id and call GET /observability/{session_id}; it must show ordered LangGraph OracleDB step records for the turn. Then send "Use hybrid retrieval to explain the evidence for Spain vs Brazil, and contrast it with semantic-only memory." Expand the trace and confirm hybrid_retrieve appears or the reply cites the startup hybrid context from SOCCER_LANGCHAIN_DOCS; vector_search should appear only for the requested semantic-only contrast. Then stop the server.git checkout -- frontend/src/) and rebuild, or remove frontend/dist/ to fall back to the committed default soccer_agent/api/static/index.html — a polished-but-broken UI is worse than the shipped default. The workshop must remain runnable.Done
uv run uvicorn soccer_agent.api.main:app --reload and open http://localhost:8000/"--retrain: Pass --force-retrain to scripts/prepare_artifacts.py in step 6.--skip-embeddings: Skip step 8 and the vector-store population in step 10. This disables the hybrid showcase; do not use it for the final workshop demo.--skip-ui-polish: Skip the taste-skill refinement of the React front-end in step 14. The React app is still built (frontend/dist/) so FastAPI serves it; this only skips re-applying the latest taste-skill standards to frontend/src/.This workshop was built end-to-end; every item below corresponds to a failure mode that broke a real session. They are not theoretical.
CREATE MINING MODEL is required for DBMS_VECTOR.LOAD_ONNX_MODEL. It's not implied by CONNECT, RESOURCE. The default workshop user gets it via setup.sh (step 4).DBMS_VECTOR is already EXECUTE to PUBLIC on Oracle AI Database Free. You do NOT need to grant EXECUTE ON SYS.DBMS_VECTOR; in fact SYSTEM cannot grant on SYS-owned objects without GRANT ANY OBJECT PRIVILEGE, so trying will give you ORA-01031.VECTOR(384, FLOAT32) is the type to use for embeddings. Pick the dim that matches your loaded ONNX model; the workshop uses 384 because all-MiniLM-L6-v2 outputs 384.VECTOR_DISTANCE(a, :q, COSINE) returns lower-is-better. Order ascending. The query embedding (:q) must be passed as a Python array.array('f', ...) of the right length; passing a numpy ndarray directly raises a type error.SOCCER_LANGCHAIN_DOCS is managed by langchain-oracledb; use scripts/load_langchain_vectors.py --reset after retraining so cached prediction documents match the latest model. The final Grok 4 chat should use this table via hybrid_retrieve/startup grounding for evidence, not plain semantic memory alone.OracleStore.setup() must be called on each fresh OracleStore(conn) object before put()/search() so the package initializes its internal table-name map, even after the schema tables already exist.OracleHybridSearchRetriever is used directly. If an image cannot create it, the workshop still showcases hybrid retrieval by fusing Oracle Text results with vector similarity in Python, while all data and indexes remain in Oracle.onnx2oracle (PyPI). Not optimum-cli directly. Not oml4py (PyPI stub). Not a hand-rolled DBMS_VECTOR.LOAD_ONNX_MODEL call against a HuggingFace export.onnx2oracle presets): all-MiniLM-L6-v2 (384, ~90MB), all-MiniLM-L12-v2 (384, ~130MB), all-mpnet-base-v2 (768, ~420MB), bge-small-en-v1.5 (384, ~130MB), nomic-embed-text-v1 (768, ~540MB).ALL_MINILM_L6_V2). It is NOT the HuggingFace repo path.IS JSON CLOBs auto-decode to Python dict/list. If you wrote json.loads(value) you'll get TypeError: the JSON object must be str, bytes or bytearray, not dict. Guard with isinstance(val, (str, bytes, bytearray)).with get_connection() block exits, .read() on any returned CLOB raises DPY-1001: not connected to database. Fix: materialize all CLOBs (val.read() if hasattr(val, 'read') else val) INSIDE the with block.array.array('f', list). numpy ndarrays don't bind to VECTOR columns directly.load_dotenv() with no args needs a stack frame. If you pipe Python to stdin (uv run python - <<EOF), find_dotenv() raises AssertionError. Pass an explicit path: load_dotenv(Path.cwd() / ".env").sk-... bearer key authenticates against the inference endpoint only, not the control plane. You can call /20231130/actions/chat and /actions/embedText, but you can't LIST models with that key. To learn what model IDs are valid, look in the OCI Console under Generative AI → Models.servingMode.compartmentId), even though authentication is by bearer key. Both must be set.tools array (in either GENERIC or COHERE apiFormat, with or without the OpenAI-style {"type":"FUNCTION","function":{...}} wrapper) returns 400: Please pass in correct format of request. on every model we tested — xai.grok-4, xai.grok-3, cohere.command-r-plus-08-2024. The agent loop works around this with a prompt protocol (see next item).{"tool": "...", "args": {...}} when it wants to call one, and parse JSON tool calls out of the response text. See soccer_agent/agent/grok_client.py for the working implementation.role: "tool" messages get rejected without toolCallId. Since we never receive a toolCallId (tool calling isn't native), surface tool results back to the model as role: "system" messages instead. Also: skip persisted role: "tool" turns when rebuilding the message list for the next iteration.127.0.0.1:1525:1521, not 0.0.0.0. The workshop image's system password is well-known; never expose port 1521/1525 to a public interface.1. Searching for 1 in sqlplus output matches the release banner (Release 23.x.x) and connection failures (ORA-01017), giving false positives. Use SELECT 424242 and grep -Eq '^[[:space:]]*424242[[:space:]]*$'.22d9a38
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.