| name | soccer-workshop-setup |
| description | 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. |
Soccer Workshop Setup
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.
Hybrid retrieval contract
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.
LangGraph OracleDB observability contract
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.
Required workshop-day OCI values
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.
Steps
-
Check the container engine (Docker or Podman)
- The setup script auto-detects Docker (preferred) or Podman, so either works. To confirm one is present:
- Run:
docker info >/dev/null 2>&1 && echo docker || (podman info >/dev/null 2>&1 && echo podman)
- If it prints neither: install Docker (https://docs.docker.com/get-docker/) or Podman (https://podman.io/). On Docker, make sure the daemon is running and the user is in the
docker group. Don't try to fix it from inside Claude Code — surface it.
-
Ensure .env exists and gate on required OCI values
- If
.env is missing at the repo root, copy from .env.example.
- Inspect
.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.
- Oracle values use the defaults that match
docker/docker-compose.yml.
- Never write real OCI values into tracked docs or examples; keep them in local ignored
.env or .env.workshop.local only.
- If it fails: stray BOM or wrong line endings in
.env will make python-dotenv silently load nothing. Save as plain UTF-8 LF.
-
Start the Oracle container
- Run:
bash .claude/skills/soccer-workshop-setup/scripts/01_start_oracle.sh
- The script auto-detects Docker or Podman, picks an Apple-Silicon-native image on arm64 Macs (and the official amd64 image everywhere else), then polls until the container is healthy (up to 7.5 min).
- If it fails on port
1525: 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 is also taken you need to edit and .
Flags (optional, when invoked with arguments)
--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/.
Pitfalls & lessons learned (read this first if you're building on top)
Why these are here
This workshop was built end-to-end; every item below corresponds to a failure mode that broke a real session. They are not theoretical.
Oracle AI Database
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.
- LangChain OracleVS table is additive and workshop-critical.
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.
- LangGraph OracleDB observability is per-store-instance.
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.
- Native HYBRID VECTOR INDEX is version-sensitive. When the database can create the hybrid index,
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.
In-DB ONNX embedding models
- Use
onnx2oracle (PyPI). Not optimum-cli directly. Not oml4py (PyPI stub). Not a hand-rolled DBMS_VECTOR.LOAD_ONNX_MODEL call against a HuggingFace export.
- Presets (
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).
- The Oracle model name is uppercase with underscores (e.g.
ALL_MINILM_L6_V2). It is NOT the HuggingFace repo path.
python-oracledb (3.x) sharp edges
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)).
- LOB locators die after connection close. If you build dataclasses inside a list comprehension AFTER the
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.
- Pass float32 vectors as
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").
OCI Generative AI Inference
- The
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.
- Compartment OCID is required in the request body (under
servingMode.compartmentId), even though authentication is by bearer key. Both must be set.
- Tool calling is NOT exposed through this endpoint with the bearer key. Including a
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).
- Prompt-protocol tool calling pattern: append tool schemas to the system message, instruct the model to emit a single JSON object
{"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.
Container networking
- Bind Oracle to
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.
- Healthcheck must use a sentinel value, not
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:]]*$'.