Repository navigation
feat(cli): add coach ask answering through a one-node LangGraph graph - #90
Merged
Merged
Conversation
Implements issue #5: a coach CLI whose ask subcommand sends one question through a one-node LangGraph StateGraph that calls GLM-5.3 on the GLM Coding Plan's OpenAI-compatible endpoint, built by one small factory (config in git-ignored .env / environment variables, readable error on a missing key, disabled_params per ADR-0001). Tests run the graph end to end with LangChain's fake chat model, no network.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
This change delivers issue #5, the walking skeleton: the smallest working slice of the interview coach, kept deliberately small so every step stays easy to follow and review on its own.
coach ask "What is consistent hashing?"prints an answer produced by GLM-5.3. The question passes through a LangGraph graph with a single node, and the model comes from one small factory function that reads the endpoint, model name and API key from local, git-ignored configuration (the GLM Coding Plan's OpenAI-compatible endpoint on open.bigmodel.cn). There is no search, memory or mode selection yet; those capabilities arrive as later, individually reviewable units built on this base.The acceptance criteria: the answer comes back through the one-node graph; a missing key produces a readable error instead of a stack trace; the factory sets
disabled_paramsfortool_choiceandparallel_tool_calls(ADR-0001); tests run the graph end to end with LangChain's fake chat model without touching the network, and check the base URL, model name and disabled params the factory builds; the project's single check command passes locally and in CI; and comments link the technical references used so the change can be studied while it is reviewed.Closes #5
What Changed
coach ask "<question>"command (pyproject[project.scripts]entry point): the question flows through a one-node LangGraphStateGraphto GLM-5.3 on the GLM Coding Plan's OpenAI-compatible endpoint and the answer is printed; a missing API key exits non-zero with a readable error instead of a stack trace.build_modelfactory buildsChatOpenAIfromSettingsloaded from environment variables plus an optional git-ignored.envfile (template.env.example, real environment variables taking precedence), and setsdisabled_paramsto never sendtool_choiceorparallel_tool_calls(ADR-0001).Risk Assessment
✅ Low: A small, well-bounded walking skeleton that satisfies every stated acceptance criterion (one-node graph, factory with disabled_params, git-ignored config, readable missing-key error, behavioral fake-model tests, referenced comments); the only finding is an informational docstring nuance with no reachable defect.
Testing
Drove the walking skeleton as an end user against a disposable OpenAI-compatible endpoint: the CLI prints the model's answer through the one-node graph (wire-captured), configuration loads from
.envwith real env vars winning, a missing key exits 1 with a readable one-line error and no traceback, and the ADR-0001disabled_paramsguard keepstool_choice/parallel_tool_callsoff the wire on the structured-output path where LangChain would otherwise force them; all 13 offline tests including the fake-chat-model graph e2e pass. The real GLM-5.3 answer itself could not be exercised (no API key available to this run), mitigated by proving the default endpoint is reachable and the full stack works against an equivalent OpenAI-compatible endpoint.git check-ignore .envconfirms the ignore ruleuv run pytest -q(13 passed), which is an offline unit-test run against LangChain's fake chat model with no network, not a drive…Evidence: Live CLI happy path: coach ask end-to-end with captured wire request
Evidence: Live .env file loading and env-var precedence with wire Authorization comparison
Evidence: Live missing-key error: readable one-liner, exit 1, no traceback
Evidence: Live ADR-0001 guard on the wire: factory vs control structured-output requests
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
src/interview_coach/model.py:20- The docstring claimsdisabled_params"drops request parameters before they are sent" and that therefore "neither may ever reach the endpoint", naming agent runtimes that addparallel_tool_callswhen binding tools. In the pinned langchain-openai 1.6.6,disabled_paramsis only consulted insidewith_structured_output(BaseChatOpenAI._filter_disabled_params, base.py:2781); kwargs passed throughbind_tools(...)(base.py:2538-2587) or directly atinvoke(...)flow unfiltered into the request payload, and the field's own docstring carries exactly this caveat ("this does not prevent a user from directly passing in the parameter during invocation"). No reachable defect exists in this change — nothing here binds tools or passes those params, so the acceptance criterion (factory setsdisabled_params) is satisfied — but this repo's comments are the owner's stated learning material, and this one asserts an absolute safety property of the ADR-0001 mechanism that the pinned library version does not provide on the very bind_tools path it cites. Remedy: reword to the library's actual guarantee (filters params LangChain adds itself, e.g. inwith_structured_output; explicitly passed bind/invoke params still go through), so the future agent-runtime PR does not inherit a false sense of safety. No other site in the change repeats this claim.src/interview_coach/model.py:25- Scope boundary of the ADR-0001 guard, verified on the wire: in langchain-openai 1.6.6disabled_paramsfilters the kwargswith_structured_outputpasses tobind_tools(so the factory model keepstool_choice/parallel_tool_callsoff the wire there, while a plain ChatOpenAI control sendstool_choice: {function: {name: ...}}andparallel_tool_calls: false), but it does NOT strip params a caller supplies directly tobind_tools(..., tool_choice='auto', parallel_tool_calls=True)— those still reach the wire even on factory-built models. Not a defect against issue Walking skeleton: ask the coach one question from the terminal #5 (nothing binds tools yet, and GLM acceptstool_choice: 'auto'per ADR-0001), but worth knowing when the agent iterations start binding tools.git check-ignore .envconfirms the ignore ruleuv run pytest -q(13 passed), which is an offline unit-test run against LangChain's fake chat model with no network, not a drive…uv run pytest -q— all 13 tests pass, including the graph run end-to-end with LangChain's fake chat model, factory base-URL/model/disabled-params checks, config loader, and CLI testsuv run coach ask "What is consistent hashing?"from an isolated dir with env-var config against a disposable local OpenAI-compatible endpoint — exit 0, answer printed, wire request captured (POST /v1/chat/completions, Bearer key, model glm-5.3, user message, no tool_choice/parallel_tool_calls).env-file run ofuv run coach ask(template copied from the repo's.env.example, key and base URL filled) then an env-var-precedence run — wire Authorization switched fromBearer file-key-456toBearer os-key-789missing-key run ofuv run coach askin a dir with no.envand no env vars — exit 1, one readable stderr line naming INTERVIEW_COACH_API_KEY and.env.example, empty stdout, zero Traceback occurrenceswith_structured_output(method="function_calling")wire check: factory model (disabled_params) vs plain ChatOpenAI control — factory sends tools unforced with both params absent, control sends forcedtool_choiceandparallel_tool_callsprobed the default endpointhttps://open.bigmodel.cn/api/coding/paas/v4with a dummy key — reachable, returns a proper OpenAI-style 401 (no credential available for a real answer)git check-ignore .env—.envis git-ignored per ADR-0002teardown: killed the disposable endpoint, removedtmp_live/, verified a clean worktree✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.