Skip to content

feat(api): reject simultaneous max_tokens and max_completion_tokens - #223

Closed
seonghobae wants to merge 1 commit into
mainfrom
feat/max-tokens-mutual-exclusive
Closed

feat(api): reject simultaneous max_tokens and max_completion_tokens#223
seonghobae wants to merge 1 commit into
mainfrom
feat/max-tokens-mutual-exclusive

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Reject requests that send both max_tokens and max_completion_tokens on chat and Responses.
  • Error code: 400 invalid_max_tokens with an explicit mutual-exclusivity message.
  • Either field alone remains accepted (range validation lives in other PRs).
  • Main-base Semgrep nosemgrep for audited cost_ledger SQL + orchestrator TLS/urllib FPs.

Buyer value

SDKs that accidentally set both fields hit opaque upstream 400s. Clear gateway validation keeps client debugging fast and matches OpenAI contract expectations.

Test plan

  • python -m pytest tests/test_max_tokens_mutual_exclusive.py -q (3 passed)
  • CI unit + Semgrep

Merge note

Auto-merge armed when green; blocked only on require_last_push_approval (sole collaborator).

Summary by CodeRabbit

  • 버그 수정

    • max_tokensmax_completion_tokens를 동시에 지정한 요청을 HTTP 400 오류로 거부합니다.
    • 채팅 완성 및 응답 API에서 두 옵션의 사용 규칙을 일관되게 적용합니다.
    • 각 옵션을 단독으로 지정한 요청은 계속 지원됩니다.
  • 테스트

    • 옵션 조합별 요청 처리와 오류 응답을 검증하는 테스트를 추가했습니다.

OpenAI treats max_tokens and max_completion_tokens as mutually exclusive.
Reject both on chat and Responses with invalid_max_tokens so clients get
a clear gateway 400 instead of an opaque provider error. Include audited
Semgrep FP nosemgrep on main-based cost_ledger SQL and orchestrator
TLS/urllib.
@seonghobae
seonghobae enabled auto-merge (squash) August 13, 2026 04:15
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

max_tokensmax_completion_tokens의 동시 사용을 HTTP 400 오류로 거부합니다. 두 엔드포인트에 검증을 연결하고 관련 테스트를 추가했습니다. SQL 및 네트워크 호출에는 보안 분석 예외 주석을 추가했습니다.

Changes

토큰 제한 상호 배타성 검증

Layer / File(s) Summary
토큰 제한 검증 구현
contextual_orchestrator/server.py
두 토큰 제한 필드가 함께 지정되면 invalid_max_tokens와 HTTP 400 오류를 반환합니다. /v1/chat/completions/v1/responses에 검증을 연결했습니다.
토큰 제한 엔드포인트 테스트
tests/test_max_tokens_mutual_exclusive.py
각 필드의 단독 사용을 허용하고, 두 필드의 동시 사용을 HTTP 400 및 invalid_max_tokens로 거부하는 동작을 검증합니다.

보안 분석 예외 주석

Layer / File(s) Summary
보안 분석 예외 주석 추가
contextual_orchestrator/cost_ledger.py, contextual_orchestrator/orchestrator.py
고정 SQL 바인딩, 개발용 TLS 비검증, 검증된 provider URL 호출에 nosemgrep 주석을 추가했습니다. 실행 동작은 변경하지 않았습니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Mergeability Score: 🟡 Moderate · up to e2d8f

Requests using either token-limit field can be accepted without the requested output cap being forwarded to the provider, so clients may receive responses that ignore their configured limit. This concrete behavior gap should be fixed or explicitly accepted before merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 두 토큰 필드의 동시 사용을 거부하는 주요 API 변경을 정확하고 간결하게 설명합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/max-tokens-mutual-exclusive

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/test_max_tokens_mutual_exclusive.py (1)

60-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

두 엔드포인트의 전체 HTTP 계약을 테스트하십시오.

현재 테스트는 chat의 단독 필드 허용과 Responses의 동시 필드 거부만 검사합니다. chat에서 두 필드를 함께 보낸 400 응답과 Responses에서 각 필드를 단독으로 보낸 성공 응답을 추가하십시오. 거부 응답에서는 status == 400, error.code, 및 상호 배타성 오류 메시지도 검증하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_max_tokens_mutual_exclusive.py` around lines 60 - 102, Expand the
HTTP contract coverage in test_http_chat_accepts_either_alone and
test_http_responses_rejects_both: add a chat request containing both max_tokens
and max_completion_tokens and assert status 400, error.code, and the
mutual-exclusion error message. Add Responses requests with each field
individually and assert successful responses, while preserving the existing
both-fields rejection test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contextual_orchestrator/cost_ledger.py`:
- Around line 586-591: Replace the broad nosemgrep comments with rule-specific
`# nosemgrep: <rule ID>` syntax at all affected sites:
contextual_orchestrator/cost_ledger.py lines 586-591, 605, and 625 should use
`sqlalchemy-execute-raw-query`; contextual_orchestrator/orchestrator.py lines
233 and 310-314 should use `unverified-ssl-context` and `dynamic-urllib-use`,
respectively. Preserve the existing suppression placement and behavior.

In `@contextual_orchestrator/orchestrator.py`:
- Around line 232-233: Update the TLS context flow around the verify_tls
handling and ModelClient/--insecure-skip-tls-verify configuration so
verify_tls=False is rejected whenever the application runs in production mode.
Preserve the existing development-only opt-out behavior, but fail clearly before
calling ssl._create_unverified_context() for production requests.

In `@contextual_orchestrator/server.py`:
- Line 732: 일반 chat, 스트리밍, batch 경로에서 max_tokens 또는 max_completion_tokens가 지정되면
해당 값을 max_output_tokens로 변환해 provider 요청에 전달하도록 수정하십시오.
_validate_max_tokens_exclusive의 상호 배타성 검증은 유지하고, 두 필드가 모두 없을 때만 기존 기본 동작을
사용하십시오.

---

Nitpick comments:
In `@tests/test_max_tokens_mutual_exclusive.py`:
- Around line 60-102: Expand the HTTP contract coverage in
test_http_chat_accepts_either_alone and test_http_responses_rejects_both: add a
chat request containing both max_tokens and max_completion_tokens and assert
status 400, error.code, and the mutual-exclusion error message. Add Responses
requests with each field individually and assert successful responses, while
preserving the existing both-fields rejection test.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f56cfae6-87f5-4162-8682-c249902fcece

📥 Commits

Reviewing files that changed from the base of the PR and between 6841b71 and e2d8f4b.

📒 Files selected for processing (4)
  • contextual_orchestrator/cost_ledger.py
  • contextual_orchestrator/orchestrator.py
  • contextual_orchestrator/server.py
  • tests/test_max_tokens_mutual_exclusive.py

Comment on lines +586 to +591
cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the DB-API placeholder char is interpolated; the value is bound.
f"SELECT 1 FROM cost_attribution_dimensions WHERE dimension_name = {ph}", # nosec B608 - ph is a DB-API placeholder.
(name,),
)
if cur.fetchone() is None:
cur.execute(
cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only DB-API placeholder chars are interpolated; values are bound.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- target files and relevant ranges ---'
sed -n '560,640p' contextual_orchestrator/cost_ledger.py
sed -n '210,325p' contextual_orchestrator/orchestrator.py
printf '%s\n' '--- all nosemgrep comments ---'
rg -n --glob '*.py' 'nosemgrep|nosec|sqlalchemy-execute-raw-query|unverified-ssl-context|dynamic-urllib-use' .
printf '%s\n' '--- Semgrep configuration and documentation references ---'
rg -n -i --glob '*.{yml,yaml,json,toml,md,py}' 'nosemgrep|nosec|sqlalchemy-execute-raw-query|unverified-ssl-context|dynamic-urllib-use|semgrep' .

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 14353


🌐 Web query:

Semgrep nosemgrep comment rule-specific suppression syntax nosemgrep -- rule-id

💡 Result:

To suppress specific Semgrep rules, you can use the nosemgrep inline comment syntax [1]. Placing this comment in your code will cause Semgrep to ignore findings that match the specified rules [1]. Syntax for Rule-Specific Suppression: To suppress one or more specific rules, append the rule identifier(s) after nosemgrep: using a comma-delimited list [1]: // nosemgrep: rule-id // nosemgrep: rule-id-1, rule-id-2 It is important to use the full rule-id, including its namespace if applicable [1]. Placement Rules: Semgrep recognizes nosemgrep comments in two primary locations [2]: 1. On the same line as the matched code (as a trailing comment) [1][2]. 2. On the line immediately preceding the matched code [1][2]. A space is required between the comment marker (e.g., //, #) and the keyword nosemgrep [3][4]. If a comment is placed anywhere else, such as multiple lines above or separated by other comments, it will not correctly suppress the finding [2]. General Suppression: Using nosemgrep without a rule ID (e.g., // nosemgrep) will ignore all rule matches on the target line [1]. Note that ignoring code via this method still generates a finding, but it is automatically marked with an Ignored triage status [1]. Best Practices: - Prefer rule-specific suppression over global suppression to avoid accidentally hiding other issues [3][4]. - Include a comment justifying why the rule is being suppressed for future maintainers [3][4]. - If an autoformatter moves your comments, placing the nosemgrep comment on the line preceding the target code is often more resilient than trailing comments [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- repository metadata and tooling configuration ---'
git ls-files | sed -n '1,160p'
printf '%s\n' '--- Semgrep and Bandit configuration files ---'
find . -maxdepth 3 -type f \( -iname '*semgrep*' -o -iname '*bandit*' -o -name 'pyproject.toml' -o -name '.pre-commit-config.yaml' -o -name '*.yml' -o -name '*.yaml' \) -print
printf '%s\n' '--- candidate rule identifiers and validation helpers ---'
rg -n 'def (_provider_url|_validate_provider)|_USAGE_COLUMNS|ATTRIBUTION_DIMENSION_CATALOG|reserved|loopback|private|scheme|hostname' contextual_orchestrator

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 11559


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- security workflow and tool configuration ---'
sed -n '1,240p' .github/workflows/security.yml
sed -n '1,260p' pyproject.toml
printf '%s\n' '--- provider validation and URL construction ---'
sed -n '440,505p' contextual_orchestrator/orchestrator.py
printf '%s\n' '--- SQL constants and schema declarations ---'
sed -n '1,85p' contextual_orchestrator/cost_ledger.py
sed -n '510,555p' contextual_orchestrator/cost_ledger.py
printf '%s\n' '--- deterministic suppression-shape check ---'
python3 - <<'PY'
from pathlib import Path
import re

targets = [
    ("contextual_orchestrator/cost_ledger.py", {586, 591, 605, 625}),
    ("contextual_orchestrator/orchestrator.py", {233, 310}),
]
for filename, lines in targets:
    text = Path(filename).read_text().splitlines()
    for line_no in sorted(lines):
        line = text[line_no - 1]
        match = re.search(r"nosemgrep(?::\s*([^ ](?:.*?))|\\s*$)", line)
        print(f"{filename}:{line_no}: {line}")
        print("  has_colon_rule_list=", bool(re.search(r"nosemgrep:\\s*[^-]", line)))
        print("  has_double_dash_form=", "nosemgrep --" in line)
PY

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 13557


nosemgrep 억제를 규칙별 구문으로 변경하세요.

현재 # nosemgrep -- ...는 규칙 ID를 지정하지 않는 전체 억제 구문입니다. 다음 위치에서 # nosemgrep: <규칙 ID> 형식을 사용하세요.

  • contextual_orchestrator/cost_ledger.py: sqlalchemy-execute-raw-query
  • contextual_orchestrator/orchestrator.py:233: unverified-ssl-context
  • contextual_orchestrator/orchestrator.py:310: dynamic-urllib-use
🧰 Tools
🪛 Ruff (0.16.1)

[error] 587-587: Possible SQL injection vector through string-based query construction

(S608)

📍 Affects 2 files
  • contextual_orchestrator/cost_ledger.py#L586-L591 (this comment)
  • contextual_orchestrator/cost_ledger.py#L605-L605
  • contextual_orchestrator/cost_ledger.py#L625-L625
  • contextual_orchestrator/orchestrator.py#L233-L233
  • contextual_orchestrator/orchestrator.py#L310-L314
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contextual_orchestrator/cost_ledger.py` around lines 586 - 591, Replace the
broad nosemgrep comments with rule-specific `# nosemgrep: <rule ID>` syntax at
all affected sites: contextual_orchestrator/cost_ledger.py lines 586-591, 605,
and 625 should use `sqlalchemy-execute-raw-query`;
contextual_orchestrator/orchestrator.py lines 233 and 310-314 should use
`unverified-ssl-context` and `dynamic-urllib-use`, respectively. Preserve the
existing suppression placement and behavior.

Comment on lines 232 to +233
if not verify_tls:
return ssl._create_unverified_context() # nosec B323 - explicit dev-only provider TLS opt-out.
return ssl._create_unverified_context() # nosec B323 - explicit dev-only provider TLS opt-out. # nosemgrep -- unverified-ssl-context: intentional, default-secure (verify_tls defaults True) dev-only opt-out for self-signed endpoints.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(orchestrator|config|settings|model|client|env|deploy|docker|helm|k8s|workflow)' | head -200

printf '%s\n' '--- relevant symbols and literals ---'
rg -n --hidden -S \
  'verify_tls|_build_ssl_context|_create_unverified_context|class ModelClient|ModelClient\(' \
  -g '!node_modules' -g '!dist' -g '!build' .

printf '%s\n' '--- orchestrator structure ---'
if command -v ast-grep >/dev/null 2>&1; then
  ast-grep outline contextual_orchestrator/orchestrator.py
else
  wc -l contextual_orchestrator/orchestrator.py
fi

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 8005


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- ModelClient and TLS implementation ---'
sed -n '195,255p' contextual_orchestrator/orchestrator.py

printf '%s\n' '--- CLI setup and ModelClient construction ---'
sed -n '1,125p' contextual_orchestrator/__main__.py

printf '%s\n' '--- TLS tests ---'
sed -n '1,90p' tests/test_provider_tls.py

printf '%s\n' '--- all insecure TLS references ---'
rg -n -C 3 --hidden -S \
  'insecure[_-]?skip|skip[_-]?tls|verify_tls|provider_ca_bundle|ca_bundle' \
  -g '!node_modules' -g '!dist' -g '!build' .

printf '%s\n' '--- documentation/config references ---'
rg -n -i -C 2 --hidden \
  'tls|self.?signed|certificate|insecure|production|운영|개발' \
  README* docs .github contextual_orchestrator tests 2>/dev/null | head -300 || true

printf '%s\n' '--- behavioral probe ---'
python3 - <<'PY'
import ssl
from pathlib import Path

source = Path("contextual_orchestrator/orchestrator.py").read_text()
start = source.index("    `@staticmethod`\n    def _build_ssl_context")
end = source.index("\n    def take_usage", start)
snippet = source[start:end]
namespace = {"ssl": ssl}
exec(snippet, namespace)
build = namespace["ModelClient._build_ssl_context"]

for value in (True, False):
    ctx = build(None, value)
    print({
        "verify_tls": value,
        "verify_mode": ctx.verify_mode,
        "check_hostname": ctx.check_hostname,
        "is_unverified": (
            ctx.verify_mode == ssl.CERT_NONE and not ctx.check_hostname
        ),
    })
PY

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 50401


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import ast
import ssl
from pathlib import Path

orchestrator_path = Path("contextual_orchestrator/orchestrator.py")
main_path = Path("contextual_orchestrator/__main__.py")

orchestrator = ast.parse(orchestrator_path.read_text())
main = ast.parse(main_path.read_text())

model_class = next(
    node for node in orchestrator.body
    if isinstance(node, ast.ClassDef) and node.name == "ModelClient"
)
init = next(
    node for node in model_class.body
    if isinstance(node, ast.FunctionDef) and node.name == "__init__"
)
build = next(
    node for node in model_class.body
    if isinstance(node, ast.FunctionDef) and node.name == "_build_ssl_context"
)

args = init.args.args
verify_index = [arg.arg for arg in args].index("verify_tls")
default = init.args.defaults[verify_index - (len(args) - len(init.args.defaults))]
print("ModelClient.__init__.verify_tls_default:", ast.unparse(default))

unverified_returns = [
    ast.unparse(node.value)
    for node in ast.walk(build)
    if isinstance(node, ast.Return)
]
print("build_ssl_context_returns:", unverified_returns)

flag_calls = [
    ast.unparse(node)
    for node in ast.walk(main)
    if isinstance(node, ast.Call)
    and isinstance(node.func, ast.Attribute)
    and node.func.attr == "add_argument"
    and any(
        isinstance(arg, ast.Constant)
        and arg.value == "--insecure-skip-tls-verify"
        for arg in node.args
    )
]
client_calls = [
    ast.unparse(node)
    for node in ast.walk(main)
    if isinstance(node, ast.Call)
    and isinstance(node.func, ast.Name)
    and node.func.id == "ModelClient"
]
print("insecure_flag_declarations:", flag_calls)
print("ModelClient_calls:", client_calls)

ctx = ssl._create_unverified_context()
print("unverified_context:", {
    "verify_mode": ctx.verify_mode,
    "check_hostname": ctx.check_hostname,
})
PY

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 748


운영 환경에서 TLS 검증 비활성화를 거부하세요.

운영 모드에서는 ModelClient(verify_tls=False)--insecure-skip-tls-verify를 거부하세요. 현재 이 경로는 인증서와 호스트 이름 검증을 모두 비활성화하므로 dev-only suppression 주석만으로는 운영 사용을 차단하지 못합니다.

🧰 Tools
🪛 ast-grep (0.45.1)

[info] 232-232: should not bypass certificate verification
Context: ssl._create_unverified_context()
Note: [CWE-295] Improper Certificate Validation.

(ssl-unverified-context)

🪛 Ruff (0.16.1)

[error] 233-233: Python allows using an insecure context via the _create_unverified_context that reverts to the previous behavior that does not validate certificates or perform hostname checks.

(S323)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contextual_orchestrator/orchestrator.py` around lines 232 - 233, Update the
TLS context flow around the verify_tls handling and
ModelClient/--insecure-skip-tls-verify configuration so verify_tls=False is
rejected whenever the application runs in production mode. Preserve the existing
development-only opt-out behavior, but fail clearly before calling
ssl._create_unverified_context() for production requests.

Source: Linters/SAST tools


if path == "/v1/chat/completions":
_reject_unknown_keys(body, ALLOWED_CHAT_KEYS)
_validate_max_tokens_exclusive(body)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 관련 함수와 passthrough 상수를 확인합니다.
ast-grep outline contextual_orchestrator --items all --type function --match 'complete|proxy_completion'

rg -n -C 5 \
  'OPENAI_PASSTHROUGH_PARAM_KEYS|PASSTHROUGH_TRIGGER_KEYS|def complete|def proxy_completion' \
  contextual_orchestrator

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 7652


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- server chat path ---'
sed -n '700,805p' contextual_orchestrator/server.py

printf '%s\n' '--- orchestrator completion and passthrough ---'
sed -n '875,935p' contextual_orchestrator/orchestrator.py

printf '%s\n' '--- cost router completion contract and provider call ---'
sed -n '105,190p' contextual_orchestrator/cost_router.py
rg -n -C 6 'orchestrator\.complete|proxy_completion\(|max_tokens|max_completion_tokens' contextual_orchestrator

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 39066


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- remaining chat response path ---'
sed -n '790,865p' contextual_orchestrator/server.py

printf '%s\n' '--- orchestrator route and provider payload construction ---'
sed -n '220,345p' contextual_orchestrator/orchestrator.py
sed -n '620,710p' contextual_orchestrator/orchestrator.py

printf '%s\n' '--- standalone contract check ---'
python3 - <<'PY'
import ast
from pathlib import Path

server = ast.parse(Path("contextual_orchestrator/server.py").read_text())
router = ast.parse(Path("contextual_orchestrator/cost_router.py").read_text())
orch = ast.parse(Path("contextual_orchestrator/orchestrator.py").read_text())

def assignment_value(tree, name):
    for node in ast.walk(tree):
        if isinstance(node, (ast.Assign, ast.AnnAssign)):
            targets = node.targets if isinstance(node, ast.Assign) else [node.target]
            if any(isinstance(t, ast.Name) and t.id == name for t in targets):
                return ast.literal_eval(node.value)
    raise LookupError(name)

def function(tree, name):
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == name:
            return node
    raise LookupError(name)

trigger_keys = assignment_value(server, "PASSTHROUGH_TRIGGER_KEYS")
openai_keys = assignment_value(server, "OPENAI_PASSTHROUGH_PARAM_KEYS")
complete = function(router, "complete")
orchestrator_complete = function(orch, "complete")

print("token_keys_in_openai_passthrough:",
      {"max_tokens", "max_completion_tokens"} <= openai_keys)
print("token_keys_in_passthrough_triggers:",
      {"max_tokens", "max_completion_tokens"} <= trigger_keys)
print("cost_router.complete_keyword_parameters:",
      [arg.arg for arg in complete.args.kwonlyargs])
print("orchestrator.complete_parameters:",
      [arg.arg for arg in orchestrator_complete.args.args])
PY

Repository: ContextualWisdomLab/contextual-orchestrator

Length of output: 14633


일반 chat 경로에서 토큰 제한값을 전달하십시오.

max_tokensmax_completion_tokens는 허용되지만 PASSTHROUGH_TRIGGER_KEYS에는 없습니다. 일반, 스트리밍, batch 경로는 요청값을 전달하지 않고 max_output_tokens를 사용합니다. 두 필드 중 하나를 지정한 요청의 제한값을 provider까지 전달하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contextual_orchestrator/server.py` at line 732, 일반 chat, 스트리밍, batch 경로에서
max_tokens 또는 max_completion_tokens가 지정되면 해당 값을 max_output_tokens로 변환해 provider
요청에 전달하도록 수정하십시오. _validate_max_tokens_exclusive의 상호 배타성 검증은 유지하고, 두 필드가 모두 없을
때만 기존 기본 동작을 사용하십시오.

@opencode-agent
opencode-agent Bot disabled auto-merge August 13, 2026 04:45
@seonghobae
seonghobae enabled auto-merge (squash) August 13, 2026 04:46
@opencode-agent
opencode-agent Bot disabled auto-merge August 13, 2026 06:04
@seonghobae
seonghobae enabled auto-merge (squash) August 13, 2026 07:07
@opencode-agent
opencode-agent Bot disabled auto-merge August 13, 2026 07:27
@seonghobae
seonghobae enabled auto-merge (squash) August 13, 2026 08:07
@opencode-agent
opencode-agent Bot disabled auto-merge August 13, 2026 08:49
@seonghobae seonghobae closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant