TradingAgents/Docs/tradingagents_codex_bridge_...

19 KiB
Raw Blame History

TradingAgents × Codex 브리지 구현 설계서

1. 목표

TradingAgents가 요구하는 LLM 호출을 OpenAI API key 대신 로컬 Codex app-server + ChatGPT/Codex 인증으로 처리한다.
핵심 목표는 다음과 같다.

  1. TradingAgents의 기존 LangGraph / ToolNode 구조를 유지한다.
  2. OpenAI 호환 프록시를 억지로 에뮬레이션하지 않고, 새 provider(codex)를 추가한다.
  3. ChatGPT Pro 사용자는 **Codex 로그인(codex login / codex login --device-auth) 또는 Codex의 managed auth cache(~/.codex/auth.json)**를 통해 인증한다.
  4. bind_tools()가 필요한 analyst 노드와 plain invoke()만 필요한 debate / manager / trader 노드가 모두 동작해야 한다.
  5. Codex의 자체 shell/web/tool 생태계에 의존하지 않고, TradingAgents가 이미 가진 도구 실행 루프를 그대로 사용한다.

2. 왜 이 방식이 최선인가

채택할 방식

권장안: codex app-server를 로컬에 띄우고, Python에서 stdio(JSONL)로 통신하는 Custom LangChain ChatModel을 만든다.

채택하지 않을 방식

A. OpenAI-compatible /v1/responses 프록시

비추천. TradingAgents는 현재 openai provider에서 langchain_openai.ChatOpenAI를 사용하고 native OpenAI일 때 use_responses_api=True를 켠다.
/v1/responses와 tool-calling semantics를 꽤 정확히 흉내 내야 한다. 구현 난도가 높고 유지보수 비용이 크다.

B. Codex dynamic tools 직접 사용

비추천. app-server의 dynamicToolsitem/tool/callexperimental 이다.
TradingAgents는 이미 ToolNode로 툴 실행을 잘 처리하므로, 여기까지 Codex에 넘길 이유가 없다.

C. Codex SDK 직접 내장

부분적으로 가능하지만 비권장. SDK는 TypeScript 중심이다. Python 프로젝트인 TradingAgents에선 app-server stdio 브리지가 더 단순하다.

설계 핵심

Codex는 모델 추론만 담당하고, 실제 툴 실행은 여전히 TradingAgents/LangGraph가 담당한다.
따라서 Codex 쪽에는 tool schema를 설명하고, 응답은 엄격한 JSON schema로만 받는다.

  • 툴이 필요하면: {"mode":"tool_calls", ...}
  • 툴이 더 이상 필요 없으면: {"mode":"final", ...}

이렇게 하면 analyst 노드의 bind_tools() 요구사항을 만족시키면서도 Codex의 experimental dynamic tool API를 피할 수 있다.


3. 구현 아키텍처

3.1 새 provider 추가

수정 파일

  • tradingagents/llm_clients/factory.py
  • tradingagents/default_config.py
  • tradingagents/llm_clients/__init__.py
  • CLI/UI 관련 파일(선택 사항이 아니라 사실상 권장)

추가 파일

  • tradingagents/llm_clients/codex_client.py
  • tradingagents/llm_clients/codex_chat_model.py
  • tradingagents/llm_clients/codex_app_server.py
  • tradingagents/llm_clients/codex_schema.py
  • tradingagents/llm_clients/codex_message_codec.py
  • tradingagents/llm_clients/codex_preflight.py
  • tests/llm_clients/test_codex_chat_model.py
  • tests/llm_clients/test_codex_app_server.py
  • tests/integration/test_codex_provider_smoke.py

3.2 런타임 구성

TradingAgents 측

TradingAgentsGraph.__init__()는 deep/quick 두 개 LLM을 한 번 생성해 재사용한다.
따라서 CodexChatModel모델 인스턴스당 app-server 세션 1개를 유지하는 것이 적절하다.

  • quick_thinking_llm → Codex app-server session A
  • deep_thinking_llm → Codex app-server session B

중요 원칙

  • 세션은 재사용
  • thread는 per-invoke 새로 생성
  • 이유: 여러 analyst / debate agent가 같은 LLM 인스턴스를 공유하므로 thread까지 재사용하면 문맥 오염이 발생한다.

즉:

  • app-server process: 재사용
  • Codex thread: 매 호출마다 새로 생성 후 thread/unsubscribe

3.3 인증 전략

기본/권장

사용자가 먼저 로컬에서:

codex login

브라우저 callback이 막히거나 headless면:

codex login --device-auth

headless / container / 원격 머신

  • cli_auth_credentials_store = "file" 로 설정해서 ~/.codex/auth.json을 사용
  • 신뢰 가능한 머신에서 생성한 auth.json을 복사
  • refresh는 직접 구현하지 말고 Codex가 하게 둔다
  • auth.json은 절대 커밋 금지

고급 옵션: OAuth URL helper

원한다면 Python helper에서 app-server로 아래를 호출해 브라우저 login URL을 직접 받아 출력할 수 있다.

  • account/read
  • account/login/start with type="chatgpt"

하지만 v1 구현은 이 helper 없이도 충분하다. 실제 운영에서는 codex login이 더 단순하고 안정적이다.


3.4 보안 / 하드닝

Codex를 “코딩 에이전트”가 아니라 “모델 백엔드”로만 쓰기 위해 다음을 권장한다.

.codex/config.toml 예시

model = "gpt-5.4"
model_reasoning_effort = "medium"
approval_policy = "never"
sandbox_mode = "read-only"
web_search = "disabled"
personality = "none"
log_dir = ".codex-log"
cli_auth_credentials_store = "file"

선택적 하드닝

[features]
apps = false
shell_tool = false
multi_agent = false

추가 권장

cwd를 프로젝트 루트가 아니라 비어 있는 전용 workspace로 준다.

예:

  • ~/.cache/tradingagents/codex_workspace
  • 또는 repo 내 ./.tradingagents_codex_workspace

이렇게 해야 Codex가 리포지토리를 뒤지거나 파일을 읽는 쪽으로 샐 가능성을 낮출 수 있다.


4. 메시지/툴 호출 설계

4.1 입력 정규화

CodexChatModel은 아래 입력을 모두 받아야 한다.

  1. str
  2. list[BaseMessage]
  3. list[dict(role=..., content=...)]

이유:

  • analyst 체인은 prompt pipeline 때문에 BaseMessage 시퀀스를 넘길 가능성이 높다
  • trader / manager 쪽은 OpenAI-style dict list를 직접 llm.invoke(messages)로 넘긴다

내부 정규화 포맷 예시

[SYSTEM]
...

[USER]
...

[ASSISTANT]
...

[ASSISTANT_TOOL_CALL]
name=get_news
args={"query":"AAPL",...}

[TOOL_RESULT]
name=get_news
call_id=call_xxx
content=...

4.2 bind_tools 처리

TradingAgents analyst 노드는 다음 패턴을 사용한다.

chain = prompt | llm.bind_tools(tools)
result = chain.invoke(state["messages"])

따라서 CodexChatModel.bind_tools()는 반드시 구현해야 한다.

구현 방식

  • LangChain tool 객체를 OpenAI-style tool schema로 변환
  • 내부적으로 self.bind(tools=formatted_tools, tool_choice=...) 형태로 바인딩
  • _generate(..., tools=..., tool_choice=...)에서 그 schema를 읽어 사용

tool schema 변환

가능한 한 LangChain의 표준 helper(convert_to_openai_tool 계열)를 사용한다.
각 tool에 대해 다음 정보를 확보한다.

  • name
  • description
  • parameters JSON schema

4.3 output schema 설계

plain invoke용

{
  "type": "object",
  "properties": {
    "answer": { "type": "string" }
  },
  "required": ["answer"],
  "additionalProperties": false
}

tool-capable invoke용

루트는 final 또는 tool_calls 중 하나가 되도록 강제한다.

{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "mode": { "const": "final" },
        "content": { "type": "string" },
        "tool_calls": {
          "type": "array",
          "maxItems": 0
        }
      },
      "required": ["mode", "content", "tool_calls"],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "mode": { "const": "tool_calls" },
        "content": { "type": "string" },
        "tool_calls": {
          "type": "array",
          "minItems": 1,
          "items": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "name": { "const": "get_news" },
                  "arguments": { "...": "get_news parameters schema" }
                },
                "required": ["name", "arguments"],
                "additionalProperties": false
              }
            ]
          }
        }
      },
      "required": ["mode", "content", "tool_calls"],
      "additionalProperties": false
    }
  ]
}

중요한 포인트

tool_calls.items.oneOf 안에 툴별 arguments schema를 넣는다.
그래야 Codex가 tool 이름과 인자를 아무렇게나 생성하지 못한다.


4.4 tool-call 정책

Codex에게 항상 다음 규칙을 준다.

  1. 지금 당장 필요한 다음 단계 툴 호출만 요청할 것
  2. speculative call 금지
  3. tool result를 아직 보지 않은 상태에서 downstream tool을 미리 호출하지 말 것
  4. 툴이 필요 없으면 final로 답할 것
  5. 응답은 output schema에 맞는 JSON만 낼 것

왜 필요한가

예를 들어 market analyst는 get_stock_data 이후에 get_indicators가 자연스럽다.
하지만 CSV 생성/캐시 같은 간접 의존성이 있으므로 한 번에 여러 단계를 추측 호출하게 두는 것보다 최소 다음 호출만 받는 편이 안전하다.


5. Codex app-server 통신 계층 설계

5.1 CodexAppServerConnection

책임:

  • codex app-server subprocess 시작/종료
  • initialize / initialized
  • request/response correlation (id)
  • stdout JSONL reader thread
  • notifications 수집
  • timeout / error propagation
  • graceful shutdown

핵심 메서드

  • start()
  • close()
  • request(method, params, timeout)
  • wait_for_turn_completion(thread_id, turn_id, timeout)
  • read_account()
  • read_models()
  • read_rate_limits()

transport

  • stdio(JSONL) 사용
  • websocket transport는 실익이 적으므로 v1에서 제외

5.2 초기 handshake

시작 직후:

  1. subprocess spawn: codex app-server
  2. initialize
  3. initialized
  4. account/read
  5. 필요 시 model/list

initialize 예시

{
  "method": "initialize",
  "id": 1,
  "params": {
    "clientInfo": {
      "name": "tradingagents_codex_bridge",
      "title": "TradingAgents Codex Bridge",
      "version": "0.1.0"
    }
  }
}

5.3 preflight 체크

codex_preflight.py 또는 helper 함수에서:

  1. codex binary 존재 여부 확인
  2. app-server 시작 가능 여부 확인
  3. account/read(refreshToken=false) 실행
  4. account.type == "chatgpt" 또는 "apiKey"인지 확인
  5. 가능하면 planType == "pro" 확인
  6. model/list에서 deep_think_llm, quick_think_llm 가용성 확인
  7. account/rateLimits/read 가능하면 출력

실패 시 메시지 예시

  • Codex not installed. Install with npm i -g @openai/codex
  • No ChatGPT/API auth found. Run codex login
  • Requested model gpt-5.4-mini is not available under current Codex account

6. LangChain 커스텀 모델 설계

6.1 CodexChatModel

상속:

  • langchain_core.language_models.chat_models.BaseChatModel

필수 구현:

  • _generate(...)
  • _llm_type
  • bind_tools(...)

권장 추가:

  • _identifying_params
  • invoke(...) 입력 정규화 보강
  • 에러 래핑

내부 필드 예시

  • model
  • reasoning_effort
  • summary
  • personality
  • request_timeout
  • max_retries
  • server: CodexAppServerConnection
  • workspace_dir
  • cleanup_threads
  • service_name

6.2 _generate() 동작

tools 없는 경우

  1. 입력 messages 정규화
  2. plain schema 생성 (answer)
  3. thread/start
  4. turn/start with outputSchema
  5. 최종 agent message JSON 파싱
  6. AIMessage(content=answer) 반환

tools 있는 경우

  1. 입력 messages 정규화
  2. tool schema 생성
  3. root oneOf output schema 생성
  4. thread/start
  5. turn/start with outputSchema
  6. 최종 agent message JSON 파싱
  7. mode == "tool_calls" 면:
    • 각 call에 id = "call_" + uuid
    • AIMessage(content=content or "", tool_calls=[...])
  8. mode == "final" 면:
    • AIMessage(content=content, tool_calls=[])

종료 처리

  • thread/unsubscribe
  • reader queue cleanup
  • 필요 시 thread archive는 선택 옵션

6.3 app-server 호출 파라미터

thread/start

{
  "method": "thread/start",
  "params": {
    "model": "gpt-5.4",
    "cwd": "/abs/path/to/.tradingagents_codex_workspace",
    "approvalPolicy": "never",
    "serviceName": "tradingagents_codex_bridge"
  }
}

turn/start

{
  "method": "turn/start",
  "params": {
    "threadId": "...",
    "input": [
      { "type": "text", "text": "<serialized prompt>" }
    ],
    "model": "gpt-5.4",
    "effort": "medium",
    "summary": "concise",
    "personality": "none",
    "sandboxPolicy": {
      "type": "readOnly",
      "access": { "type": "fullAccess" }
    },
    "outputSchema": { ... }
  }
}

6.4 프롬프트 래퍼 템플릿

plain invoke wrapper

You are the language model backend for a LangGraph-based financial multi-agent system.

Rules:
1. Answer only from the provided conversation transcript.
2. Do not inspect files.
3. Do not run commands.
4. Do not use web search.
5. Return ONLY JSON that matches the provided schema.

Conversation transcript:
<...serialized messages...>

tool-capable wrapper

You are the language model backend for a LangGraph-based financial multi-agent system.

You may either:
- request the next necessary tool call(s), or
- provide the final assistant response.

Hard rules:
1. Use only the allowed tools listed below.
2. Arguments must conform exactly to the JSON schema for that tool.
3. Request only the next required tool call batch.
4. Do not speculate past missing tool results.
5. Do not inspect files.
6. Do not run commands.
7. Do not use web search.
8. Return ONLY JSON that matches the provided schema.

Allowed tools:
<tool schemas pretty-printed>

Conversation transcript:
<...serialized messages...>

안정화 팁

  • tool schema를 pretty JSON으로 포함
  • 1~2개의 few-shot example을 포함할 수 있음
  • 단, prompt를 너무 길게 만들어 토큰 낭비하지 않도록 주의

7. TradingAgents 코드 변경 체크리스트

7.1 default_config.py

추가 권장 key:

"llm_provider": "openai",
"codex_binary": "codex",
"codex_reasoning_effort": "medium",
"codex_summary": "concise",
"codex_personality": "none",
"codex_workspace_dir": os.getenv("TRADINGAGENTS_CODEX_WORKSPACE", "./.tradingagents_codex_workspace"),
"codex_request_timeout": 120,
"codex_max_retries": 2,
"codex_cleanup_threads": True,

호환성 위해:

  • openai_reasoning_effort가 설정돼 있고 codex_reasoning_effort가 비어 있으면 fallback 하도록 해도 좋다.

7.2 factory.py

대략:

if provider_lower == "codex":
    return CodexClient(model, base_url, **kwargs)

7.3 codex_client.py

책임:

  • BaseLLMClient 구현
  • kwargs를 CodexChatModel 생성자에 전달
  • validate_model()에서 preflight/model list 확인

7.4 CLI / UI

반드시 추가할 항목:

  • provider 목록에 codex
  • backend_url 입력은 codex일 때 숨기거나 무시
  • advanced options:
    • codex_reasoning_effort
    • codex_summary
    • codex_personality
    • codex_workspace_dir

7.5 README / docs

반드시 문서화:

  1. ChatGPT Pro/Codex auth와 API key의 차이
  2. codex login
  3. headless auth cache 사용법
  4. .codex/config.toml 예시
  5. provider 선택 방법
  6. known limitations

8. 테스트 전략

8.1 단위 테스트

test_codex_message_codec.py

  • str 입력 정규화
  • BaseMessage 시퀀스 정규화
  • dict message 시퀀스 정규화
  • ToolMessage 직렬화

test_codex_schema.py

  • plain schema 생성
  • tool oneOf schema 생성
  • tool args const / required / additionalProperties 검증

test_codex_chat_model.py

mock app-server 응답으로:

  • plain final answer
  • tool_calls answer
  • malformed JSON retry
  • timeout
  • unsupported model error

test_codex_app_server.py

  • initialize handshake
  • request/response correlation
  • notification draining
  • turn completed / failed 처리

8.2 통합 테스트

smoke

  • provider=codex
  • analyst=news 한 개만 선택
  • ticker=AAPL
  • research depth=1
  • 최종 리포트 파일 생성 확인

tool loop

  • market analyst만 실행
  • 첫 응답이 get_stock_data tool call
  • tool result 후 다음 응답이 get_indicators 또는 final

multi-agent

  • market + news
  • graph 전체 완주
  • final_trade_decision 비어 있지 않음

auth preflight

  • 로그인 안 된 환경 → 친절한 실패
  • 로그인 된 환경 → account/read 성공

8.3 운영 검증

실제 실행 전 아래 순서 권장:

codex login
python -m tradingagents.llm_clients.codex_preflight
python main.py

또는 CLI/UI에서 provider를 codex로 선택.


9. 장애 대응

9.1 malformed JSON

대응:

  • 1회 재시도
  • 재시도 prompt:
    • “Your previous output was invalid JSON. Return valid JSON matching the schema only.”
  • 그래도 실패하면 예외 raise

9.2 app-server 시작 실패

대응:

  • binary path 재확인
  • codex --version 확인
  • PATH 문제면 codex_binary 절대경로 사용

9.3 로그인/권한 문제

대응:

  • codex login
  • headless면 codex login --device-auth
  • cli_auth_credentials_store="file" 설정
  • ~/.codex/auth.json 존재 여부 확인

9.4 rate limit

대응:

  • account/rateLimits/read 노출
  • 재시도(backoff)
  • 긴 배치 작업은 serialized run
  • 필요 시 Codex credits 사용 고려

9.5 thread log 과다 생성

대응:

  • thread/unsubscribe 기본 수행
  • .codex-log 별도 디렉터리 사용
  • 오래된 로그 cleanup script 추가

10. 권장 구현 순서

Phase 1

  • provider 추가
  • app-server connection 추가
  • plain invoke만 먼저 연결
  • preflight 추가

Phase 2

  • bind_tools() + tool schema oneOf 구현
  • analyst nodes smoke test

Phase 3

  • CLI/UI 옵션 추가
  • README/docs 작성
  • 통합 테스트 보강

Phase 4

  • malformed JSON retry
  • rate limit/backoff
  • log cleanup / diagnostics

11. 최종 권장안 요약

가장 좋은 구현 방식

TradingAgents에 codex provider를 새로 추가하고, 내부에서 codex app-server와 stdio(JSONL)로 통신하는 LangChain 커스텀 ChatModel을 구현한다.
tool calling은 Codex dynamicTools를 쓰지 말고, outputSchema + JSON oneOf 방식으로 모델 응답을 final 또는 tool_calls 형태로 강제한다.

이 방식의 장점

  • OpenAI API key 불필요
  • ChatGPT Pro / Codex 로그인 재사용 가능
  • TradingAgents의 기존 ToolNode / graph 구조 유지
  • Python 프로젝트에 자연스럽게 통합 가능
  • dynamicTools 실험 API 의존 최소화
  • 추후 유지보수 포인트가 명확함

반드시 지켜야 할 운영 원칙

  • 직접 OAuth refresh 구현 금지
  • auth.json은 비밀 취급
  • codex login 또는 device-auth 우선
  • one auth cache per trusted runner / serialized workflow
  • Codex를 모델 백엔드로만 쓰고 shell/web 기능은 최대한 비활성화

12. 최소 수용 기준(Acceptance Criteria)

아래가 모두 충족되면 구현 성공으로 간주한다.

  1. llm_provider="codex" 설정으로 TradingAgents가 실행된다.
  2. API key 없이 codex login 상태에서 동작한다.
  3. analyst 노드가 bind_tools()를 통해 tool call을 생성하고 ToolNode가 이를 실행한다.
  4. manager/trader/risk nodes가 plain invoke()로 정상 응답한다.
  5. AAPL 또는 SPY에 대해 최소 1개 analyst + 전체 graph smoke run이 성공한다.
  6. malformed JSON, auth missing, binary missing, model missing에 대한 에러 메시지가 명확하다.
  7. README와 preflight가 포함된다.