Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 98 additions & 19 deletions scripts/langchain/issue_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def _load_apply_prompt() -> str:
return APPLY_SUGGESTIONS_PROMPT


def _get_llm_client() -> tuple[object, str] | None:
def _get_llm_client(force_openai: bool = False) -> tuple[object, str] | None:
try:
from langchain_openai import ChatOpenAI
except ImportError:
Expand All @@ -242,6 +242,17 @@ def _get_llm_client() -> tuple[object, str] | None:

from tools.llm_provider import DEFAULT_MODEL, GITHUB_MODELS_BASE_URL

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

The import of '_is_token_limit_error' is unused in the '_get_llm_client' function. This import is only needed in the 'analyze_issue' function. Consider moving this import to where it's actually used, or if it's needed for type checking purposes, add a comment explaining why it's imported here.

Copilot uses AI. Check for mistakes.

# If force_openai is True, skip GitHub Models and use OpenAI directly
if force_openai and openai_token:
return (
ChatOpenAI(
model=DEFAULT_MODEL,
api_key=openai_token,
temperature=0.1,
),
"openai",
)

if github_token:
return (
ChatOpenAI(
Expand Down Expand Up @@ -538,11 +549,33 @@ def _coerce_list(value: Any) -> list[Any]:
)


def _process_llm_response(
response: Any, provider: str, use_llm: bool
) -> IssueOptimizationResult | None:
"""Process LLM response and return normalized result, or None if processing fails."""
content = getattr(response, "content", None) or str(response)
payload = _extract_json_payload(content)
if payload:
try:
data = json.loads(payload)
except json.JSONDecodeError:
return None
if isinstance(data, dict):
result = _normalize_result(data, provider)
result.task_splitting = _ensure_task_decomposition(
result.task_splitting, use_llm=use_llm
)
return result
return None


def analyze_issue(issue_body: str, *, use_llm: bool = True) -> IssueOptimizationResult:
if not issue_body:
issue_body = ""

if use_llm:
from tools.llm_provider import _is_token_limit_error

client_info = _get_llm_client()
if client_info:
client, provider = client_info
Expand All @@ -554,25 +587,71 @@ def analyze_issue(issue_body: str, *, use_llm: bool = True) -> IssueOptimization
prompt = _load_prompt()
template = ChatPromptTemplate.from_template(prompt)
chain = template | client
response = chain.invoke(
{
"issue_body": issue_body,
"agent_limitations": "\n".join(f"- {item}" for item in AGENT_LIMITATIONS),
}
)
content = getattr(response, "content", None) or str(response)
payload = _extract_json_payload(content)
if payload:
try:
data = json.loads(payload)
except json.JSONDecodeError:
data = None
if isinstance(data, dict):
result = _normalize_result(data, provider)
result.task_splitting = _ensure_task_decomposition(
result.task_splitting, use_llm=use_llm
)
try:
response = chain.invoke(
{
"issue_body": issue_body,
"agent_limitations": "\n".join(
f"- {item}" for item in AGENT_LIMITATIONS
),
}
)
result = _process_llm_response(response, provider, use_llm)
if result:
return result
except Exception as e:
# If GitHub Models hit token limit, retry with OpenAI API
Comment thread
stranske marked this conversation as resolved.
if _is_token_limit_error(e) and provider == "github-models":
print(
"GitHub Models token limit hit, retrying with OpenAI API...",
file=sys.stderr,
)
openai_client_info = _get_llm_client(force_openai=True)
if openai_client_info:
openai_client, openai_provider = openai_client_info
openai_chain = template | openai_client
try:
response = openai_chain.invoke(
{
"issue_body": issue_body,
"agent_limitations": "\n".join(
f"- {item}" for item in AGENT_LIMITATIONS
),
}
)
content = getattr(response, "content", None) or str(response)
payload = _extract_json_payload(content)
if payload:
try:
data = json.loads(payload)
except json.JSONDecodeError:
data = None
if isinstance(data, dict):
result = _normalize_result(data, openai_provider)
result.task_splitting = _ensure_task_decomposition(
result.task_splitting, use_llm=use_llm
)
print(
"Successfully analyzed with OpenAI API",
Comment thread
stranske marked this conversation as resolved.
file=sys.stderr,
)
return result
except Exception as openai_error:
print(
f"OpenAI API also failed ({type(openai_error).__name__}: {openai_error}), using fallback",
file=sys.stderr,
)
else:
print(
"OPENAI_API_KEY not available, using fallback",
file=sys.stderr,
)
Comment thread
stranske marked this conversation as resolved.
else:
# Other error types - fall back immediately
print(
Comment thread
stranske marked this conversation as resolved.
f"LLM analysis failed ({type(e).__name__}: {e}), using fallback",
file=sys.stderr,
)

result = _fallback_analysis(issue_body)
result.task_splitting = _ensure_task_decomposition(result.task_splitting, use_llm=False)
Expand Down
14 changes: 14 additions & 0 deletions tools/llm_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ def _setup_langsmith_tracing() -> bool:
LANGSMITH_ENABLED = _setup_langsmith_tracing()


def _is_token_limit_error(error: Exception) -> bool:
"""Check if error is a token limit (413) error from GitHub Models."""
error_str = str(error).lower()
# Check for 413 status code in common formats: "413", "code: 413", "status code 413"
has_413 = (
"error code: 413" in error_str
or "status code: 413" in error_str
or "error code 413" in error_str
or "status code 413" in error_str
)
has_token_message = "tokens_limit_reached" in error_str or "request body too large" in error_str
return has_413 and has_token_message


@dataclass
class CompletionAnalysis:
"""Result of task completion analysis."""
Expand Down
Loading