mirror of
https://github.com/THU-MAIC/OpenMAIC.git
synced 2026-10-29 00:46:40 +08:00
* refactor(chat): move Pi's shared parser, action schemas and store-state type out of the legacy chat owners Pi classroom chat still imports three pieces from the legacy director-graph chat, which #1814 retires. Give each a Pi or neutral owner first, so the legacy files can later be deleted without touching Pi: - The structured-output parser (createParserState, parseStructuredChunk, finalizeParser, looksLikeStructuredFragment, ParseResult) moves verbatim from lib/orchestration/stateless-generate.ts to lib/chat/structured-output.ts. Only two comments change: they no longer name director-graph as the reader of the per-call ordered indices. This also stops the Pi route from loading director-graph and LangGraph through the parser's module. - lib/orchestration/tool-schemas.ts moves unchanged to lib/chat/pi/tool-schemas.ts. - AgentLoopStoreState moves unchanged from lib/chat/agent-loop.ts to lib/chat/store-state.ts; the Pi request path in use-chat-sessions uses it. The remaining legacy consumers import from the new locations until they are removed. No behavior change. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(chat): cover classroom model precedence on /api/chat/pi with the real resolver The user-route, lock and body precedence for classroom interaction is covered with the real resolveModel only through the legacy /api/chat route, which #1814 removes. Route-level Pi tests mock resolveModel. Add the same four cases against /api/chat/pi, asserting the model handed to the Pi runtime: - a chat-adapter user route wins over the body model; - a locked classroom slot wins over the user route; - with no matching route, the body model is used; - a locked classroom slot is kept despite unlisted body and route models. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(eval): run the whiteboard layout eval against Pi classroom chat The whiteboard eval drove the legacy /api/chat through runAgentLoop. Point it at /api/chat/pi, which #1814 keeps as the only classroom chat: - one Pi request per scripted user turn, with whiteboard tools enabled as the app does for in-class requests; - text and actions grouped by each agent's messageId and sealed into their own history message at agent_end, since one Pi request can hand the turn to several agents; - the previous turn's done.directorState carried into the next request within a scenario run, as the app does within a session; - an error event, a non-200 response, or a stream without done fails the scenario through the existing error path, which skips its remaining turns (previously an error event was swallowed and the turn still captured); - a native whiteboard event fails the run, because the harness cannot see the native child's server-side board. Not seeing one does not prove the server uses the legacy child; run the eval on a server configured for it. Scores are a new baseline and are not comparable with runs of the legacy runner. Scenarios, capture, scoring and reporting are unchanged. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(eval): settle queued actions and cancel the request when a whiteboard turn fails Review of the Pi whiteboard runner (#1913) found two lifecycle gaps on the failure paths it added: - A turn that failed (an SSE error, a native whiteboard event, or EOF without done) returned and disposed the scenario without waiting for its queued actions. The state manager drives shared store singletons, so a pending action could finish inside the next scenario and land in its screenshot. Every turn exit now settles the action queue first. A queued rejection is handled while the stream is still open and is logged at settle time, as on the success path, without replacing the turn's own error. - Stopping early only released the reader lock, so the request kept running. Each turn's request now has its own AbortController, aborted on exit, and an unfinished response body is cancelled before the lock is released. runScenario is exported with its CLI settings passed in, and main() runs only when the file is the entry point, so an offline Vitest suite can exercise the real function: failed turns settle before returning, the native early exit aborts and cancels, a concurrent action rejection is not unhandled, and the success path keeps one Pi request per turn, messageId grouping, directorState carry-over and score:null on scorer failure. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(eval): run the answering and answer-content evals on Pi; retire premature-end Both evals drove the legacy single-step director prompt and agent prompt, which #1814 retires. Run them on the real Pi director in process instead (eval/orchestration/pi-harness.ts): the Pi config helpers supply agent configs and budgets, whiteboard tools are on as for classroom requests, thinking is off, and the legacy child runtime is pinned. The harness skips the HTTP route, so route authorization and tool-capability checks are not exercised. - answering classifies by the first recorded call_agent attempt in the director tool trace: TEACHER, OTHER_AGENT (another or a non-existent agent), NO_DISPATCH (none recorded; wrong, kept in the denominator), or ERROR (a failed run, excluded as before). A later recorded attempt never rescues the first. - answer-content judges the full reply of the first recorded teacher attempt. A failed run, no recorded teacher attempt, or a first one that is skipped, marked isError or empty scores 0. The judge, opening window, threshold and all-samples denominator are unchanged. Pi's short teacher replies make the two metrics nearly coincide, and the score includes routing. - Both score only what the trace records. A schema-rejected attempt is not recorded, so a later one can score, and a legacy child's partial text after a provider failure is judged. Scores can be overstated. Real-chain tests (real Pi loop and agent SDK, scripted StreamFn) pin both limits. - The old prompt A/B variants are removed; results are a Pi baseline. - premature-end is retired: its single END decision has no Pi counterpart, and the close gate does not show a question was answered. - The three remaining eval scripts load with tsx --conditions=import, so ESM-only dependencies resolve. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(chat): always run classroom chat on Pi and fail builds that still opt out The classroom chat hook, the /api/chat/pi route and PlaybackChromeRoot no longer read NEXT_PUBLIC_PI_CHAT_ENABLED. The hook sends every turn through the existing Pi single-request path; the frontend no longer calls the legacy /api/chat loop (fetchStatelessChat and the runAgentLoop tail are removed). The Pi branch itself is unchanged apart from indentation. isPiChatEnabled and its readDefaultOnBoolean helper are removed. The independent courseware-reference gate stays default-off. next.config.ts now exports a phase function. In the production-build phase it fails when NEXT_PUBLIC_PI_CHAT_ENABLED is set to anything other than '', 'true' or '1' (exact match), which covers every value that used to select the legacy runtime. The error names the value and points to the Classroom model setting, the google native preset for Gemini, and the administrator for managed configurations. Other phases do not check the flag. The fixed env pin for the flag is removed. This is an intermediate step of #1814 PR 3: the legacy route, director graph and agent loop are still present and are removed in a follow-up commit, and Docker/Compose wiring and docs still describe the old rollback. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(chat): remove the legacy director-graph classroom chat backend The frontend has run every classroom turn on /api/chat/pi since the previous commit, so nothing reaches the legacy stack any more. Remove it: - app/api/chat/route.ts (no stub, redirect or 410; /api/chat/pi stays) - lib/orchestration/{stateless-generate,director-graph,ai-sdk-adapter, director-prompt,prompt-builder}.ts and lib/chat/agent-loop.ts - summarizers/{conversation-summary,message-converter}.ts - the director, agent-system and agent-system-wb-* templates, the speech-guidelines and whiteboard-reference snippets, and their prompt and snippet IDs Pi's default legacy child, the shared structured-output parser, the Pi tool schemas, the shared summarizers, the registry and the protocol types are unchanged. Dependency declarations are left for a follow-up commit. Tests: - Drop the legacy chat route from the model-authorization tests and the coverage matrix. The mixed compatibility cases keep their six and eight stage assertions, and the exhaustive resolver call-site scan is unchanged. The locked-classroom case now goes through /api/chat/pi. - Retire chat-route-user-routes (its precedence cases are rebuilt on the Pi route in route-model-precedence), the agent-loop async-state test, and the conversation-summary test. - Keep the #511 intent as a history input contract on Pi: toHistoryMessages keeps the human challenge as a user turn in order, and buildUserPrompt still reports it after a later agent reply. - Move the role, peer-context and language-directive prompt checks onto Pi's buildChildPrompt. Keep the PBL and placeholder-lint template tests and the getActionDescriptions LaTeX guard. - Add middleware cases for POST /api/chat (401 without access, pass-through otherwise) and a Playwright spec that expects a plain 404 from the running app. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(deps): drop the unused @langchain/langgraph dependency The legacy director graph was its only consumer and was removed in the previous commit. Remove the root declaration and let pnpm update the lockfile: @langchain/langgraph and @langchain/langgraph-checkpoint are no longer installed. @langchain/langgraph-sdk stays (an optional CopilotKit peer and a direct dependency of @ag-ui/langgraph). @langchain/core is kept: CopilotKit requires it as a peer and pnpm would install it anyway. partial-json and jsonrepair are kept for the Pi parser. The re-resolution also moved vite 8.0.0's postcss from 8.5.15 to 8.5.23 (both were already in the lockfile; Next's postcss is unchanged) and shortened four eslint-import snapshot keys without changing versions. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(docker): keep the Pi chat flag only as an input to the build check NEXT_PUBLIC_PI_CHAT_ENABLED no longer selects a chat runtime; next.config.ts only reads it to fail a production build that still opts out of Pi. Keep the builder ARG and the Compose build-arg forwarding so an explicit false still reaches that check, and drop the ENV line: build args are already visible to the builder's RUN steps, and the value has nothing to configure in the image. Remove the legacy-chat rollback comment and command from docker-compose.yml and replace the rollback section of .env.example with a short note that the legacy runtime is gone and which values now fail the build. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: describe classroom chat after the legacy runtime removal - Configuration docs (all six locales): drop the Pi rollback example, the advice to rebuild with the flag set to false and the Compose rollback command. Say instead that classroom chat always runs on Pi and needs a tool-calling Classroom model (google preset for Gemini), that the flag should be removed, which exact values a production build still accepts, and that the Compose service env_file supplies runtime values only. - READMEs: drop the LangGraph badge and describe lib/orchestration and the Pi director loop as they now are. - CHANGELOG: add the breaking change (model requirement, the flag, POST /api/chat answering 404 after the access gate) and the dependency removal. - lib/prompts/README.md: point the role and length guidelines at lib/chat/pi/prompts.ts, correct which tests check placeholders, use a missing snippet in the error example, and start the whiteboard eval server with the native child runtime off. - Remove the two comments that told CI and Playwright to leave the flag unset. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(chat): pin the server key on a user-route model; document answering eval limits - route-model-precedence: when the user route switches the classroom model to another provider without a key of its own, assert that the model is built with the server key rather than the body's key. - answering eval: state in the header comment and the generated report that a run which throws, is aborted or ends without done is ERROR even after it dispatched an agent, that ERROR samples stay out of the correct-rate denominator, and what the rate therefore does and does not measure. The scoring rules are unchanged. Refs #1814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: wyuc <wang-yc24@mails.tsinghua.edu.cn>
175 lines
9.1 KiB
YAML
175 lines
9.1 KiB
YAML
services:
|
|
openmaic:
|
|
build:
|
|
context: .
|
|
args:
|
|
- ALPINE_MIRROR=${ALPINE_MIRROR:-}
|
|
- NPM_REGISTRY=${NPM_REGISTRY:-}
|
|
# NEXT_PUBLIC_* values are compiled into the browser bundle. Empty values
|
|
# follow each feature's documented default; explicit build-time overrides
|
|
# can be supplied on the command line.
|
|
# env_file below does not feed build args.
|
|
- ALLOWED_FRAME_ANCESTORS=${ALLOWED_FRAME_ANCESTORS:-}
|
|
- NEXT_PUBLIC_MAIC_EDITOR_ENABLED=${NEXT_PUBLIC_MAIC_EDITOR_ENABLED:-}
|
|
- NEXT_PUBLIC_MAIC_EDITOR_RENDERER_ENABLED=${NEXT_PUBLIC_MAIC_EDITOR_RENDERER_ENABLED:-}
|
|
- NEXT_PUBLIC_MAIC_PLAYBACK_RENDERER_ENABLED=${NEXT_PUBLIC_MAIC_PLAYBACK_RENDERER_ENABLED:-}
|
|
- NEXT_PUBLIC_PI_CHAT_ENABLED=${NEXT_PUBLIC_PI_CHAT_ENABLED:-}
|
|
- NEXT_PUBLIC_COURSEWARE_REFERENCE_ENABLED=${NEXT_PUBLIC_COURSEWARE_REFERENCE_ENABLED:-}
|
|
- NEXT_PUBLIC_SHOW_VOCATIONAL_TEST_UI=${NEXT_PUBLIC_SHOW_VOCATIONAL_TEST_UI:-}
|
|
- NEXT_PUBLIC_ENABLE_VIDEO_EXPORT=${NEXT_PUBLIC_ENABLE_VIDEO_EXPORT:-}
|
|
- NEXT_PUBLIC_VIDEO_EXPORT_CTA_DESTINATION=${NEXT_PUBLIC_VIDEO_EXPORT_CTA_DESTINATION:-}
|
|
- NEXT_PUBLIC_ENABLE_PPTX_IMPORT=${NEXT_PUBLIC_ENABLE_PPTX_IMPORT:-}
|
|
- NEXT_PUBLIC_PRO_WORKBENCH_ENABLED=${NEXT_PUBLIC_PRO_WORKBENCH_ENABLED:-}
|
|
# Published on loopback by default: the single-user owner (see
|
|
# docker-compose.defaults.env) serves one course library to every request,
|
|
# so only this machine should reach it. To publish on the network, set
|
|
# ACCESS_CODE in .env.local (strongly recommended: without it anyone who
|
|
# reaches the port shares, edits and can delete the library) and start with
|
|
# OPENMAIC_PUBLISH_ADDRESS=0.0.0.0 (or a specific interface address).
|
|
ports:
|
|
- '${OPENMAIC_PUBLISH_ADDRESS:-127.0.0.1}:${OPENMAIC_PORT:-3000}:3000'
|
|
env_file:
|
|
# Compose's defaults for this deployment (the bundled DATABASE_URL, the
|
|
# single-user owner); .env.local is read after it, so a key set there
|
|
# (for example an external DATABASE_URL) overrides the default.
|
|
- docker-compose.defaults.env
|
|
- .env.local
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
# The address the port above is published on, passed in so the app can
|
|
# warn when it is published beyond loopback with the default PostgreSQL
|
|
# password below.
|
|
- OPENMAIC_PUBLISH_ADDRESS=${OPENMAIC_PUBLISH_ADDRESS:-127.0.0.1}
|
|
# Point the app at the render service when it's running. This wins over any
|
|
# RENDER_SERVICE_URL in .env.local; the service only starts under the
|
|
# "video-export" profile. The app's capability check probes the service's
|
|
# /health, so if the profile is off (service absent) the app reports the
|
|
# MP4 export disabled and degrades to the ZIP-download path — this URL
|
|
# being set does not, by itself, advertise a working render.
|
|
- RENDER_SERVICE_URL=http://render-service:9000
|
|
networks:
|
|
- default # outbound internet (LLM/media provider APIs, etc.)
|
|
- render # reach the render service over the isolated network
|
|
volumes:
|
|
# Optional: the model configuration (copy openmaic.example.yml to
|
|
# openmaic.yml first; a missing file would be mounted as a directory).
|
|
# - ./openmaic.yml:/app/openmaic.yml:ro
|
|
# Legacy, read only when there is no openmaic.yml:
|
|
# - ./server-providers.yml:/app/server-providers.yml:ro
|
|
- openmaic-data:/app/data
|
|
restart: unless-stopped
|
|
|
|
# PostgreSQL for the app-embedded persistence API. Started by default; the
|
|
# app waits for its health check. Its port is not published to the host here;
|
|
# `pnpm db:up` runs this service definition as a separate development
|
|
# database (its own project and volume, see docker-compose.db.yml).
|
|
postgres:
|
|
image: postgres:16
|
|
environment:
|
|
- POSTGRES_DB=openmaic
|
|
- POSTGRES_USER=openmaic
|
|
# Development default. It initializes the role only when the volume is
|
|
# empty; the default DATABASE_URL in docker-compose.defaults.env is built
|
|
# from the same variable, so use letters and digits only (or set an
|
|
# encoded DATABASE_URL in .env.local). Set PERSISTENCE_POSTGRES_PASSWORD before
|
|
# publishing the app beyond loopback; the app warns at startup if it is
|
|
# published elsewhere with this default.
|
|
- POSTGRES_PASSWORD=${PERSISTENCE_POSTGRES_PASSWORD:-openmaic-dev}
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pg_isready -U openmaic -d openmaic']
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 5s
|
|
volumes:
|
|
- openmaic-postgres:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
|
|
# Isolated MP4 render service (Node 22 + Chromium + FFmpeg) for classroom video
|
|
# export (issue #866). Opt-in: only starts with `--profile video-export`, since
|
|
# the image is heavy (bundles Chromium + FFmpeg) and the capability is optional.
|
|
#
|
|
# docker compose --profile video-export up --build
|
|
#
|
|
# Sits on the `render` network (`internal: true`, no host/internet gateway).
|
|
# It shares that network with `openmaic` only so the app can reach it; to stop
|
|
# the untrusted Chromium from initiating connections *back* to the app over
|
|
# that shared network, the container's entrypoint installs an iptables egress
|
|
# lockdown (needs CAP_NET_ADMIN) that drops all outbound except loopback and
|
|
# replies on app-initiated connections. The export ZIP is self-contained, so
|
|
# the render needs no outbound at all.
|
|
render-service:
|
|
build: ./render-service
|
|
profiles:
|
|
- video-export
|
|
expose:
|
|
- '9000'
|
|
# Required for the entrypoint's iptables egress lockdown. Without it the
|
|
# service still boots, but logs a warning and does NOT block Chromium egress.
|
|
cap_add:
|
|
- NET_ADMIN
|
|
environment:
|
|
- PORT=9000
|
|
# The standard CPU profile prefers BeginFrame but permits producer's
|
|
# compatibility fallback (for example iframe GenUI). It still fixes one
|
|
# producer worker, one render, and one extraction. Select low-memory to
|
|
# force screenshot instead of relying on producer auto-selection.
|
|
- RENDER_RESOURCE_PROFILE=${RENDER_RESOURCE_PROFILE:-standard}
|
|
- RENDER_CHUNK_EXECUTION=${RENDER_CHUNK_EXECUTION:-false}
|
|
- RENDER_CHUNK_COUNT=${RENDER_CHUNK_COUNT:-1}
|
|
- RENDER_CHUNK_WORKERS=${RENDER_CHUNK_WORKERS:-1}
|
|
- RENDER_MAX_PARALLEL_CHUNKS=${RENDER_MAX_PARALLEL_CHUNKS:-1}
|
|
- RENDER_CHUNK_SIZE_FRAMES=${RENDER_CHUNK_SIZE_FRAMES:-0}
|
|
- RENDER_TARGET_CHUNK_FRAMES=${RENDER_TARGET_CHUNK_FRAMES:-0}
|
|
- PRODUCER_HEADLESS_SHELL_PATH=/usr/bin/chromium-headless-shell
|
|
- RENDER_MAX_CONCURRENCY=1
|
|
- RENDER_MAX_CONCURRENT_EXTRACTIONS=1
|
|
# Long compositions still need CDP headroom even with one explicit worker.
|
|
- PRODUCER_PUPPETEER_PROTOCOL_TIMEOUT_MS=900000
|
|
# OpenMAIC's long slide exports currently exhaust producer's 15s static
|
|
# verification budget and disable dedup anyway. Skip that guaranteed-lost
|
|
# startup cost until the verifier can share/scale its plan across workers.
|
|
- HF_STATIC_DEDUP=false
|
|
# In this default topology the app is exposed directly and does NOT set
|
|
# TRUST_PROXY_HEADERS, so every caller collapses to one identity. A
|
|
# per-identity limit would then throttle the whole deployment to a single
|
|
# render, so disable it here and rely on RENDER_MAX_CONCURRENCY + the global
|
|
# RENDER_MAX_QUEUE cap. Set this >0 only with a trusted proxy supplying
|
|
# per-user identity (see render-service/README.md).
|
|
- RENDER_MAX_JOBS_PER_USER=0
|
|
- RENDER_PREVIEW_TIMEOUT_MS=${RENDER_PREVIEW_TIMEOUT_MS:-20000}
|
|
# Separate Chromium capacity: previews can run alongside a video export.
|
|
- RENDER_PREVIEW_MAX_CONCURRENCY=${RENDER_PREVIEW_MAX_CONCURRENCY:-1}
|
|
- RENDER_PREVIEW_MAX_IN_FLIGHT=${RENDER_PREVIEW_MAX_IN_FLIGHT:-8}
|
|
# Preview callers send a durable owner identity in x-openmaic-client, so
|
|
# keep this per-owner guard enabled. Set it to 0 only in deployments that
|
|
# call /preview without an owner identity.
|
|
- RENDER_PREVIEW_MAX_PER_USER=${RENDER_PREVIEW_MAX_PER_USER:-2}
|
|
# Bound RAM for the export (Chromium + FFmpeg + archive expansion) and the
|
|
# independent preview browsers. Provision more before raising preview concurrency.
|
|
# The standard profile requires 8 GiB. For the explicit low-memory profile,
|
|
# set RENDER_RESOURCE_PROFILE=low-memory and RENDER_SERVICE_MEMORY_LIMIT=4g.
|
|
mem_limit: ${RENDER_SERVICE_MEMORY_LIMIT:-8g}
|
|
# Chromium media/frame work can exceed Docker's 64 MiB default shared-memory
|
|
# mount. This is a ceiling inside the selected profile cgroup, not eager allocation.
|
|
shm_size: 2gb
|
|
networks:
|
|
- render
|
|
restart: unless-stopped
|
|
|
|
networks:
|
|
default: {}
|
|
# Isolated network for app <-> render-service traffic. `internal: true` means
|
|
# no route to the host or internet. Both services share it so the app can
|
|
# reach the render service; the render container additionally blocks its own
|
|
# egress (iptables, see the service's entrypoint) so the untrusted Chromium
|
|
# can't use this shared network to reach the app.
|
|
render:
|
|
internal: true
|
|
|
|
volumes:
|
|
openmaic-data:
|
|
openmaic-postgres:
|