Merge c9408db34a into f047f26df0
This commit is contained in:
commit
be69a6212b
|
|
@ -1,3 +1,4 @@
|
||||||
|
# Default provider is `codex_oauth` and does not require OPENAI_API_KEY.
|
||||||
# LLM Providers (set the one you use)
|
# LLM Providers (set the one you use)
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
GOOGLE_API_KEY=
|
GOOGLE_API_KEY=
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
reports
|
||||||
|
results
|
||||||
|
tests
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[codz]
|
*.py[codz]
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -116,9 +116,32 @@ Install dependencies:
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### Required APIs
|
### Authentication / APIs
|
||||||
|
|
||||||
TradingAgents supports multiple LLM providers. Set the API key for your chosen provider:
|
TradingAgents supports multiple LLM providers.
|
||||||
|
|
||||||
|
#### Option A (Default): ChatGPT OAuth for Codex models (no `OPENAI_API_KEY`)
|
||||||
|
|
||||||
|
If you have a ChatGPT Plus/Pro account, login once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tradingagents auth login
|
||||||
|
# If local callback port 1455 is busy:
|
||||||
|
tradingagents auth login --manual
|
||||||
|
```
|
||||||
|
|
||||||
|
Check status / logout:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tradingagents auth status
|
||||||
|
tradingagents auth logout
|
||||||
|
```
|
||||||
|
|
||||||
|
`codex_oauth` currently uses a Codex model whitelist:
|
||||||
|
- `gpt-5.2-codex`
|
||||||
|
- `gpt-5.2`
|
||||||
|
|
||||||
|
#### Option B: API keys for other providers
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export OPENAI_API_KEY=... # OpenAI (GPT)
|
export OPENAI_API_KEY=... # OpenAI (GPT)
|
||||||
|
|
@ -162,7 +185,7 @@ An interface will appear showing results as they load, letting you track the age
|
||||||
|
|
||||||
### Implementation Details
|
### Implementation Details
|
||||||
|
|
||||||
We built TradingAgents with LangGraph to ensure flexibility and modularity. The framework supports multiple LLM providers: OpenAI, Google, Anthropic, xAI, OpenRouter, and Ollama.
|
We built TradingAgents with LangGraph to ensure flexibility and modularity. The framework supports multiple LLM providers: Codex OAuth (ChatGPT Plus/Pro), OpenAI, Google, Anthropic, xAI, OpenRouter, and Ollama.
|
||||||
|
|
||||||
### Python Usage
|
### Python Usage
|
||||||
|
|
||||||
|
|
@ -186,9 +209,9 @@ from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||||
from tradingagents.default_config import DEFAULT_CONFIG
|
from tradingagents.default_config import DEFAULT_CONFIG
|
||||||
|
|
||||||
config = DEFAULT_CONFIG.copy()
|
config = DEFAULT_CONFIG.copy()
|
||||||
config["llm_provider"] = "openai" # openai, google, anthropic, xai, openrouter, ollama
|
config["llm_provider"] = "codex_oauth" # codex_oauth, openai, google, anthropic, xai, openrouter, ollama
|
||||||
config["deep_think_llm"] = "gpt-5.2" # Model for complex reasoning
|
config["deep_think_llm"] = "gpt-5.4"
|
||||||
config["quick_think_llm"] = "gpt-5-mini" # Model for quick tasks
|
config["quick_think_llm"] = "gpt-5.2"
|
||||||
config["max_debate_rounds"] = 2
|
config["max_debate_rounds"] = 2
|
||||||
|
|
||||||
ta = TradingAgentsGraph(debug=True, config=config)
|
ta = TradingAgentsGraph(debug=True, config=config)
|
||||||
|
|
|
||||||
119
cli/main.py
119
cli/main.py
|
|
@ -23,8 +23,6 @@ from rich import box
|
||||||
from rich.align import Align
|
from rich.align import Align
|
||||||
from rich.rule import Rule
|
from rich.rule import Rule
|
||||||
|
|
||||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
|
||||||
from tradingagents.default_config import DEFAULT_CONFIG
|
|
||||||
from cli.models import AnalystType
|
from cli.models import AnalystType
|
||||||
from cli.utils import *
|
from cli.utils import *
|
||||||
from cli.announcements import fetch_announcements, display_announcements
|
from cli.announcements import fetch_announcements, display_announcements
|
||||||
|
|
@ -37,6 +35,8 @@ app = typer.Typer(
|
||||||
help="TradingAgents CLI: Multi-Agents LLM Financial Trading Framework",
|
help="TradingAgents CLI: Multi-Agents LLM Financial Trading Framework",
|
||||||
add_completion=True, # Enable shell completion
|
add_completion=True, # Enable shell completion
|
||||||
)
|
)
|
||||||
|
auth_app = typer.Typer(help="Manage ChatGPT OAuth credentials for codex_oauth.")
|
||||||
|
app.add_typer(auth_app, name="auth")
|
||||||
|
|
||||||
|
|
||||||
# Create a deque to store recent messages with a maximum length
|
# Create a deque to store recent messages with a maximum length
|
||||||
|
|
@ -536,10 +536,10 @@ def get_user_selections():
|
||||||
)
|
)
|
||||||
selected_research_depth = select_research_depth()
|
selected_research_depth = select_research_depth()
|
||||||
|
|
||||||
# Step 5: OpenAI backend
|
# Step 5: LLM backend
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 5: OpenAI backend", "Select which service to talk to"
|
"Step 5: LLM backend", "Select which service to talk to"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
selected_llm_provider, backend_url = select_llm_provider()
|
selected_llm_provider, backend_url = select_llm_provider()
|
||||||
|
|
@ -566,11 +566,11 @@ def get_user_selections():
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
thinking_level = ask_gemini_thinking_config()
|
thinking_level = ask_gemini_thinking_config()
|
||||||
elif provider_lower == "openai":
|
elif provider_lower in ("openai", "codex_oauth"):
|
||||||
console.print(
|
console.print(
|
||||||
create_question_box(
|
create_question_box(
|
||||||
"Step 7: Reasoning Effort",
|
"Step 7: Reasoning Effort",
|
||||||
"Configure OpenAI reasoning effort level"
|
"Configure reasoning effort level"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reasoning_effort = ask_openai_reasoning_effort()
|
reasoning_effort = ask_openai_reasoning_effort()
|
||||||
|
|
@ -897,6 +897,9 @@ def format_tool_args(args, max_length=80) -> str:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def run_analysis():
|
def run_analysis():
|
||||||
|
from tradingagents.default_config import DEFAULT_CONFIG
|
||||||
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||||
|
|
||||||
# First get all user selections
|
# First get all user selections
|
||||||
selections = get_user_selections()
|
selections = get_user_selections()
|
||||||
|
|
||||||
|
|
@ -1167,6 +1170,110 @@ def run_analysis():
|
||||||
display_complete_report(final_state)
|
display_complete_report(final_state)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_ms(ms: int) -> str:
|
||||||
|
if not ms:
|
||||||
|
return "unknown"
|
||||||
|
return datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def _load_codex_oauth_modules():
|
||||||
|
try:
|
||||||
|
from codex_oauth.auth import (
|
||||||
|
decode_jwt_payload,
|
||||||
|
exchange_authorization_code,
|
||||||
|
extract_chatgpt_account_id,
|
||||||
|
login_manual,
|
||||||
|
login_via_browser,
|
||||||
|
)
|
||||||
|
from codex_oauth.exceptions import NotAuthenticatedError, OAuthFlowError
|
||||||
|
from codex_oauth.store import AuthStore, OAuthCredentials
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
console.print(
|
||||||
|
"[red]Missing dependency `langchain-codex-oauth`. "
|
||||||
|
"Install project dependencies first.[/red]"
|
||||||
|
)
|
||||||
|
raise typer.Exit(code=1) from exc
|
||||||
|
|
||||||
|
return {
|
||||||
|
"decode_jwt_payload": decode_jwt_payload,
|
||||||
|
"exchange_authorization_code": exchange_authorization_code,
|
||||||
|
"extract_chatgpt_account_id": extract_chatgpt_account_id,
|
||||||
|
"login_manual": login_manual,
|
||||||
|
"login_via_browser": login_via_browser,
|
||||||
|
"NotAuthenticatedError": NotAuthenticatedError,
|
||||||
|
"OAuthFlowError": OAuthFlowError,
|
||||||
|
"AuthStore": AuthStore,
|
||||||
|
"OAuthCredentials": OAuthCredentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@auth_app.command("login")
|
||||||
|
def auth_login(
|
||||||
|
manual: bool = typer.Option(False, "--manual", help="Paste redirect URL/code manually."),
|
||||||
|
timeout_s: int = typer.Option(180, "--timeout-s", min=30, help="Browser callback timeout in seconds."),
|
||||||
|
):
|
||||||
|
"""Login via ChatGPT OAuth and save local credentials."""
|
||||||
|
modules = _load_codex_oauth_modules()
|
||||||
|
try:
|
||||||
|
if manual:
|
||||||
|
code, verifier = modules["login_manual"]()
|
||||||
|
else:
|
||||||
|
result = modules["login_via_browser"](timeout_s=timeout_s)
|
||||||
|
if not result:
|
||||||
|
raise modules["OAuthFlowError"](
|
||||||
|
"OAuth callback timed out. Retry with --manual or try again."
|
||||||
|
)
|
||||||
|
code, verifier = result
|
||||||
|
|
||||||
|
token = modules["exchange_authorization_code"](code=code, verifier=verifier)
|
||||||
|
payload = modules["decode_jwt_payload"](token.access)
|
||||||
|
if not payload:
|
||||||
|
raise modules["OAuthFlowError"]("Received invalid access token.")
|
||||||
|
account_id = modules["extract_chatgpt_account_id"](payload)
|
||||||
|
if not account_id:
|
||||||
|
raise modules["OAuthFlowError"](
|
||||||
|
"Failed to extract chatgpt_account_id from access token."
|
||||||
|
)
|
||||||
|
|
||||||
|
modules["AuthStore"]().save(
|
||||||
|
modules["OAuthCredentials"](
|
||||||
|
access=token.access,
|
||||||
|
refresh=token.refresh,
|
||||||
|
expires=token.expires_at_ms,
|
||||||
|
account_id=account_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except modules["OAuthFlowError"] as exc:
|
||||||
|
console.print(f"[red]{exc}[/red]")
|
||||||
|
raise typer.Exit(code=2)
|
||||||
|
|
||||||
|
console.print("[green]Login successful. OAuth credentials saved.[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
@auth_app.command("status")
|
||||||
|
def auth_status():
|
||||||
|
"""Show current OAuth credential status."""
|
||||||
|
modules = _load_codex_oauth_modules()
|
||||||
|
store = modules["AuthStore"]()
|
||||||
|
try:
|
||||||
|
creds = store.load()
|
||||||
|
except modules["NotAuthenticatedError"] as exc:
|
||||||
|
console.print(f"[red]{exc}[/red]")
|
||||||
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
|
console.print("[green]Logged in: yes[/green]")
|
||||||
|
console.print(f"Account id: {creds.account_id}")
|
||||||
|
console.print(f"Expires (UTC): {_format_ms(creds.expires)}")
|
||||||
|
|
||||||
|
|
||||||
|
@auth_app.command("logout")
|
||||||
|
def auth_logout():
|
||||||
|
"""Delete local OAuth credentials."""
|
||||||
|
modules = _load_codex_oauth_modules()
|
||||||
|
modules["AuthStore"]().delete()
|
||||||
|
console.print("[green]Logged out.[/green]")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def analyze():
|
def analyze():
|
||||||
run_analysis()
|
run_analysis()
|
||||||
|
|
|
||||||
38
cli/utils.py
38
cli/utils.py
|
|
@ -127,6 +127,12 @@ def select_shallow_thinking_agent(provider) -> str:
|
||||||
|
|
||||||
# Define shallow thinking llm engine options with their corresponding model names
|
# Define shallow thinking llm engine options with their corresponding model names
|
||||||
SHALLOW_AGENT_OPTIONS = {
|
SHALLOW_AGENT_OPTIONS = {
|
||||||
|
"codex_oauth": [
|
||||||
|
("GPT-5.4", "gpt-5.4"),
|
||||||
|
("GPT-5.2", "gpt-5.2"),
|
||||||
|
("GPT-5.3-Codex", "gpt-5.3-codex"),
|
||||||
|
("GPT-5.2-Codex", "gpt-5.2-codex"),
|
||||||
|
],
|
||||||
"openai": [
|
"openai": [
|
||||||
("GPT-5 Mini - Cost-optimized reasoning", "gpt-5-mini"),
|
("GPT-5 Mini - Cost-optimized reasoning", "gpt-5-mini"),
|
||||||
("GPT-5 Nano - Ultra-fast, high-throughput", "gpt-5-nano"),
|
("GPT-5 Nano - Ultra-fast, high-throughput", "gpt-5-nano"),
|
||||||
|
|
@ -192,6 +198,12 @@ def select_deep_thinking_agent(provider) -> str:
|
||||||
|
|
||||||
# Define deep thinking llm engine options with their corresponding model names
|
# Define deep thinking llm engine options with their corresponding model names
|
||||||
DEEP_AGENT_OPTIONS = {
|
DEEP_AGENT_OPTIONS = {
|
||||||
|
"codex_oauth": [
|
||||||
|
("GPT-5.4", "gpt-5.4"),
|
||||||
|
("GPT-5.2", "gpt-5.2"),
|
||||||
|
("GPT-5.3-Codex", "gpt-5.3-codex"),
|
||||||
|
("GPT-5.2-Codex", "gpt-5.2-codex"),
|
||||||
|
],
|
||||||
"openai": [
|
"openai": [
|
||||||
("GPT-5.2 - Latest flagship", "gpt-5.2"),
|
("GPT-5.2 - Latest flagship", "gpt-5.2"),
|
||||||
("GPT-5.1 - Flexible reasoning", "gpt-5.1"),
|
("GPT-5.1 - Flexible reasoning", "gpt-5.1"),
|
||||||
|
|
@ -253,22 +265,22 @@ def select_deep_thinking_agent(provider) -> str:
|
||||||
return choice
|
return choice
|
||||||
|
|
||||||
def select_llm_provider() -> tuple[str, str]:
|
def select_llm_provider() -> tuple[str, str]:
|
||||||
"""Select the OpenAI api url using interactive selection."""
|
"""Select LLM provider and backend URL using interactive selection."""
|
||||||
# Define OpenAI api options with their corresponding endpoints
|
|
||||||
BASE_URLS = [
|
BASE_URLS = [
|
||||||
("OpenAI", "https://api.openai.com/v1"),
|
("Codex OAuth (ChatGPT Plus/Pro)", "codex_oauth", "https://chatgpt.com/backend-api"),
|
||||||
("Google", "https://generativelanguage.googleapis.com/v1"),
|
("OpenAI", "openai", "https://api.openai.com/v1"),
|
||||||
("Anthropic", "https://api.anthropic.com/"),
|
("Google", "google", "https://generativelanguage.googleapis.com/v1"),
|
||||||
("xAI", "https://api.x.ai/v1"),
|
("Anthropic", "anthropic", "https://api.anthropic.com/"),
|
||||||
("Openrouter", "https://openrouter.ai/api/v1"),
|
("xAI", "xai", "https://api.x.ai/v1"),
|
||||||
("Ollama", "http://localhost:11434/v1"),
|
("Openrouter", "openrouter", "https://openrouter.ai/api/v1"),
|
||||||
|
("Ollama", "ollama", "http://localhost:11434/v1"),
|
||||||
]
|
]
|
||||||
|
|
||||||
choice = questionary.select(
|
choice = questionary.select(
|
||||||
"Select your LLM Provider:",
|
"Select your LLM Provider:",
|
||||||
choices=[
|
choices=[
|
||||||
questionary.Choice(display, value=(display, value))
|
questionary.Choice(display, value=(display, provider, value))
|
||||||
for display, value in BASE_URLS
|
for display, provider, value in BASE_URLS
|
||||||
],
|
],
|
||||||
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
|
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
|
||||||
style=questionary.Style(
|
style=questionary.Style(
|
||||||
|
|
@ -281,13 +293,13 @@ def select_llm_provider() -> tuple[str, str]:
|
||||||
).ask()
|
).ask()
|
||||||
|
|
||||||
if choice is None:
|
if choice is None:
|
||||||
console.print("\n[red]no OpenAI backend selected. Exiting...[/red]")
|
console.print("\n[red]No LLM backend selected. Exiting...[/red]")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
display_name, url = choice
|
display_name, provider_name, url = choice
|
||||||
print(f"You selected: {display_name}\tURL: {url}")
|
print(f"You selected: {display_name}\tURL: {url}")
|
||||||
|
|
||||||
return display_name, url
|
return provider_name, url
|
||||||
|
|
||||||
|
|
||||||
def ask_openai_reasoning_effort() -> str:
|
def ask_openai_reasoning_effort() -> str:
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ description = "TradingAgents: Multi-Agents LLM Financial Trading Framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"langchain-core>=0.3.81",
|
"langchain-core>=1.0.0,<2.0.0",
|
||||||
"backtrader>=1.9.78.123",
|
"backtrader>=1.9.78.123",
|
||||||
"chainlit>=2.5.5",
|
"chainlit>=2.5.5",
|
||||||
"langchain-anthropic>=0.3.15",
|
"langchain-anthropic>=1.0.0,<2.0.0",
|
||||||
"langchain-experimental>=0.3.4",
|
|
||||||
"langchain-google-genai>=2.1.5",
|
"langchain-google-genai>=2.1.5",
|
||||||
"langchain-openai>=0.3.23",
|
"langchain-openai>=1.0.0,<2.0.0",
|
||||||
|
"langchain-codex-oauth>=1.0,<1.1",
|
||||||
"langgraph>=0.4.8",
|
"langgraph>=0.4.8",
|
||||||
"pandas>=2.3.0",
|
"pandas>=2.3.0",
|
||||||
"parsel>=1.10.0",
|
"parsel>=1.10.0",
|
||||||
|
|
@ -38,3 +38,7 @@ tradingagents = "cli.main:app"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
include = ["tradingagents*", "cli*"]
|
include = ["tradingagents*", "cli*"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = ["."]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
typing-extensions
|
typing-extensions
|
||||||
langchain-core
|
langchain-core
|
||||||
langchain-openai
|
langchain-openai
|
||||||
langchain-experimental
|
langchain-codex-oauth
|
||||||
pandas
|
pandas
|
||||||
yfinance
|
yfinance
|
||||||
stockstats
|
stockstats
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ DEFAULT_CONFIG = {
|
||||||
"dataflows/data_cache",
|
"dataflows/data_cache",
|
||||||
),
|
),
|
||||||
# LLM settings
|
# LLM settings
|
||||||
"llm_provider": "openai",
|
"llm_provider": "codex_oauth",
|
||||||
"deep_think_llm": "gpt-5.2",
|
"deep_think_llm": "gpt-5.4",
|
||||||
"quick_think_llm": "gpt-5-mini",
|
"quick_think_llm": "gpt-5.2",
|
||||||
"backend_url": "https://api.openai.com/v1",
|
"backend_url": "https://chatgpt.com/backend-api",
|
||||||
# Provider-specific thinking configuration
|
# Provider-specific thinking configuration
|
||||||
"google_thinking_level": None, # "high", "minimal", etc.
|
"google_thinking_level": None, # "high", "minimal", etc.
|
||||||
"openai_reasoning_effort": None, # "medium", "high", "low"
|
"openai_reasoning_effort": None, # "medium", "high", "low"
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ class TradingAgentsGraph:
|
||||||
if thinking_level:
|
if thinking_level:
|
||||||
kwargs["thinking_level"] = thinking_level
|
kwargs["thinking_level"] = thinking_level
|
||||||
|
|
||||||
elif provider == "openai":
|
elif provider in ("openai", "codex_oauth"):
|
||||||
reasoning_effort = self.config.get("openai_reasoning_effort")
|
reasoning_effort = self.config.get("openai_reasoning_effort")
|
||||||
if reasoning_effort:
|
if reasoning_effort:
|
||||||
kwargs["reasoning_effort"] = reasoning_effort
|
kwargs["reasoning_effort"] = reasoning_effort
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from .base_client import BaseLLMClient
|
||||||
|
from .validators import validate_model
|
||||||
|
|
||||||
|
|
||||||
|
class CodexOAuthClient(BaseLLMClient):
|
||||||
|
"""Client for ChatGPT OAuth Codex models."""
|
||||||
|
|
||||||
|
def __init__(self, model: str, base_url: Optional[str] = None, **kwargs):
|
||||||
|
super().__init__(model, base_url, **kwargs)
|
||||||
|
|
||||||
|
def get_llm(self) -> Any:
|
||||||
|
"""Return configured ChatCodexOAuth instance."""
|
||||||
|
try:
|
||||||
|
from langchain_codex_oauth import ChatCodexOAuth
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
"langchain-codex-oauth is required for llm_provider='codex_oauth'. "
|
||||||
|
"Install dependencies and run `tradingagents auth login`."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
llm_kwargs = {"model": self.model}
|
||||||
|
|
||||||
|
if self.base_url:
|
||||||
|
llm_kwargs["base_url"] = self.base_url
|
||||||
|
|
||||||
|
for key in (
|
||||||
|
"timeout",
|
||||||
|
"max_retries",
|
||||||
|
"reasoning_effort",
|
||||||
|
"max_tokens",
|
||||||
|
"temperature",
|
||||||
|
"callbacks",
|
||||||
|
):
|
||||||
|
if key in self.kwargs:
|
||||||
|
llm_kwargs[key] = self.kwargs[key]
|
||||||
|
|
||||||
|
return ChatCodexOAuth(**llm_kwargs)
|
||||||
|
|
||||||
|
def validate_model(self) -> bool:
|
||||||
|
"""Validate model for codex_oauth."""
|
||||||
|
return validate_model("codex_oauth", self.model)
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .base_client import BaseLLMClient
|
from .base_client import BaseLLMClient
|
||||||
from .openai_client import OpenAIClient
|
|
||||||
from .anthropic_client import AnthropicClient
|
|
||||||
from .google_client import GoogleClient
|
|
||||||
|
|
||||||
|
|
||||||
def create_llm_client(
|
def create_llm_client(
|
||||||
|
|
@ -15,7 +12,7 @@ def create_llm_client(
|
||||||
"""Create an LLM client for the specified provider.
|
"""Create an LLM client for the specified provider.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
provider: LLM provider (openai, anthropic, google, xai, ollama, openrouter)
|
provider: LLM provider (openai, codex_oauth, anthropic, google, xai, ollama, openrouter)
|
||||||
model: Model name/identifier
|
model: Model name/identifier
|
||||||
base_url: Optional base URL for API endpoint
|
base_url: Optional base URL for API endpoint
|
||||||
**kwargs: Additional provider-specific arguments
|
**kwargs: Additional provider-specific arguments
|
||||||
|
|
@ -29,15 +26,57 @@ def create_llm_client(
|
||||||
provider_lower = provider.lower()
|
provider_lower = provider.lower()
|
||||||
|
|
||||||
if provider_lower in ("openai", "ollama", "openrouter"):
|
if provider_lower in ("openai", "ollama", "openrouter"):
|
||||||
|
try:
|
||||||
|
from .openai_client import OpenAIClient
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
"Missing dependency for OpenAI-compatible providers. "
|
||||||
|
"Install `langchain-openai`."
|
||||||
|
) from exc
|
||||||
|
|
||||||
return OpenAIClient(model, base_url, provider=provider_lower, **kwargs)
|
return OpenAIClient(model, base_url, provider=provider_lower, **kwargs)
|
||||||
|
|
||||||
|
if provider_lower == "codex_oauth":
|
||||||
|
try:
|
||||||
|
from .codex_oauth_client import CodexOAuthClient
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
"Missing dependency for codex_oauth provider. "
|
||||||
|
"Install `langchain-codex-oauth`."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
return CodexOAuthClient(model, base_url, **kwargs)
|
||||||
|
|
||||||
if provider_lower == "xai":
|
if provider_lower == "xai":
|
||||||
|
try:
|
||||||
|
from .openai_client import OpenAIClient
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
"Missing dependency for xAI provider. Install `langchain-openai`."
|
||||||
|
) from exc
|
||||||
|
|
||||||
return OpenAIClient(model, base_url, provider="xai", **kwargs)
|
return OpenAIClient(model, base_url, provider="xai", **kwargs)
|
||||||
|
|
||||||
if provider_lower == "anthropic":
|
if provider_lower == "anthropic":
|
||||||
|
try:
|
||||||
|
from .anthropic_client import AnthropicClient
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
"Missing dependency for Anthropic provider. "
|
||||||
|
"Install `langchain-anthropic`."
|
||||||
|
) from exc
|
||||||
|
|
||||||
return AnthropicClient(model, base_url, **kwargs)
|
return AnthropicClient(model, base_url, **kwargs)
|
||||||
|
|
||||||
if provider_lower == "google":
|
if provider_lower == "google":
|
||||||
|
try:
|
||||||
|
from .google_client import GoogleClient
|
||||||
|
except ModuleNotFoundError as exc:
|
||||||
|
raise ModuleNotFoundError(
|
||||||
|
"Missing dependency for Google provider. "
|
||||||
|
"Install `langchain-google-genai`."
|
||||||
|
) from exc
|
||||||
|
|
||||||
return GoogleClient(model, base_url, **kwargs)
|
return GoogleClient(model, base_url, **kwargs)
|
||||||
|
|
||||||
raise ValueError(f"Unsupported LLM provider: {provider}")
|
raise ValueError(f"Unsupported LLM provider: {provider}")
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ VALID_MODELS = {
|
||||||
"gpt-4o",
|
"gpt-4o",
|
||||||
"gpt-4o-mini",
|
"gpt-4o-mini",
|
||||||
],
|
],
|
||||||
|
"codex_oauth": [
|
||||||
|
# Codex chat models via ChatGPT OAuth backend
|
||||||
|
"gpt-5.4",
|
||||||
|
"gpt-5.2",
|
||||||
|
"gpt-5.3-codex",
|
||||||
|
"gpt-5.2-codex",
|
||||||
|
],
|
||||||
"anthropic": [
|
"anthropic": [
|
||||||
# Claude 4.5 series (2025)
|
# Claude 4.5 series (2025)
|
||||||
"claude-opus-4-5",
|
"claude-opus-4-5",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue