trongld's picture
Update LangGraphAgent to use meta-llama/Llama-3.3-70B-Instruct-Turbo model instead of openai/gpt-oss-20b
ab1278d
import os
from typing import Optional
from langchain_together import ChatTogether
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_google_genai import ChatGoogleGenerativeAI
from langgraph.prebuilt import ToolNode
from pydantic import Field, SecretStr # add
# Try to import tools from tools.py
try:
from .tools import get_tools as _get_tools # package-style
except Exception:
try:
from tools import get_tools as _get_tools # script-style
except Exception:
def _get_tools(): return [] # fallback
try:
# Optional, used when OPENAI_API_KEY is available
from langchain_openai import ChatOpenAI
except Exception: # pragma: no cover - optional dependency resolution
ChatOpenAI = None # type: ignore
class ChatOpenRouter(ChatOpenAI):
openai_api_key: Optional[SecretStr] = Field(
alias="api_key",
default_factory=os.getenv("OPENROUTER_API_KEY", None),
)
@property
def lc_secrets(self) -> dict[str, str]:
return {"openai_api_key": "OPENROUTER_API_KEY"}
def __init__(self,
openai_api_key: Optional[str] = None,
**kwargs):
openai_api_key = (
openai_api_key or os.getenv("OPENROUTER_API_KEY")
)
super().__init__(
base_url="https://openrouter.ai/api/v1",
openai_api_key=openai_api_key,
**kwargs
)
class _EchoModel:
"""Simple stub model used when no API key / model is configured."""
def __init__(self, prefix: str = "[stub]"):
self.prefix = prefix
def invoke(self, messages):
last = messages[-1]
content = getattr(last, "content", str(last))
# Ensure the contract: always emit FINAL ANSWER:
return AIMessage(content=f"{self.prefix} FINAL ANSWER: You asked: {content}")
class LangGraphAgent:
"""
Minimal LangGraph agent template.
Usage:
agent = LangGraphAgent()
answer = agent("What is the capital of France?")
"""
def __init__(self, *, model: Optional[object] = None, system_prompt: Optional[str] = None):
# Guide the model to use tools and to output a clear final answer.
self.system_prompt = system_prompt or "You are a helpful assistant. Keep answers concise."
# Choose an LLM if not provided
if model is None:
# model = ChatGoogleGenerativeAI(
# model="gemma-3-27b-it",
# )
model = ChatTogether(
model="meta-llama/Llama-3.3-70B-Instruct-Turbo",
api_key=os.getenv("TOGETHER_API_KEY"),
)
if model is None and ChatOpenAI is not None:
model = ChatOpenAI(
api_key=os.getenv("OPENROUTER_API_KEY"),
base_url=os.getenv("OPENROUTER_BASE_URL"),
model="openai/gpt-oss-20b:free",
)
if model is None:
model = _EchoModel()
self.model = model
# Load tools and bind to the model if supported
self.tools = _get_tools()
self.llm = getattr(self.model, "bind_tools",
lambda _: self.model)(self.tools)
# Build a tool-using LangGraph: agent -> (maybe) tools -> agent -> ... -> END
def call_agent(state: MessagesState):
msgs = [SystemMessage(content=self.system_prompt)
] + list(state["messages"])
ai = self.llm.invoke(msgs)
return {"messages": [ai]}
def should_call_tools(state: MessagesState):
# If the last AI message includes tool calls, route to tools; else end.
last = state["messages"][-1]
if isinstance(last, AIMessage) and getattr(last, "tool_calls", None):
print(
f"Detected tool calls in last AI message: {last.tool_calls}")
return "tools"
return "end"
builder = StateGraph(MessagesState)
builder.add_node("agent", call_agent)
builder.add_node("tools", ToolNode(self.tools))
builder.add_edge(START, "agent")
builder.add_edge("tools", "agent")
builder.add_conditional_edges("agent", should_call_tools, {
"tools": "tools", "end": END})
self.graph = builder.compile()
@staticmethod
def _extract_final_answer(text: str) -> str:
key = "FINAL ANSWER:"
idx = text.rfind(key)
return text[idx + len(key):].strip() if idx != -1 else text.strip()
def __call__(self, question: str) -> str:
state = {"messages": [HumanMessage(content=question)]}
result = self.graph.invoke(state, {'recursion_limit': 10})
messages = result.get("messages", [])
# Return only the content after "FINAL ANSWER:"
for msg in reversed(messages):
if isinstance(msg, AIMessage):
return self._extract_final_answer(msg.content)
return self._extract_final_answer(messages[-1].content) if messages else ""