Update stream_agent.py
This commit is contained in:
+57
-54
@@ -2,75 +2,78 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import time
|
from typing import Any
|
||||||
from collections.abc import Iterable
|
|
||||||
|
from langchain_core.messages import AIMessage
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from langgraph.prebuilt import create_react_agent
|
||||||
|
|
||||||
|
|
||||||
class StreamingAIAgent:
|
@tool
|
||||||
"""Small AI-agent with a streaming interface and an offline fallback."""
|
def get_demo_price(product: str, city: str = "Казань") -> str:
|
||||||
|
"""Return a deterministic demo price for the requested product."""
|
||||||
|
prices = {
|
||||||
|
"молоко": "89 рублей",
|
||||||
|
"хлеб": "54 рубля",
|
||||||
|
"сыр": "219 рублей",
|
||||||
|
}
|
||||||
|
return f"{product} в городе {city}: {prices.get(product.lower(), 'цена не найдена')}"
|
||||||
|
|
||||||
def __init__(self, model: str | None = None) -> None:
|
|
||||||
self.model = model or os.getenv("OPENAI_MODEL", "openai/gpt-oss-20b:free")
|
|
||||||
|
|
||||||
def invoke(self, question: str) -> str:
|
def build_agent():
|
||||||
return "".join(self.stream(question))
|
llm = ChatOpenAI(
|
||||||
|
model=os.getenv("OPENAI_MODEL", "openai/gpt-oss-20b:free"),
|
||||||
|
base_url=os.getenv("OPENAI_BASE_URL") or None,
|
||||||
|
api_key=os.getenv("OPENAI_API_KEY", "not-needed"),
|
||||||
|
temperature=0,
|
||||||
|
streaming=True,
|
||||||
|
)
|
||||||
|
return create_react_agent(llm, tools=[get_demo_price])
|
||||||
|
|
||||||
def stream(self, question: str) -> Iterable[str]:
|
|
||||||
client = self._build_client()
|
|
||||||
if client is None:
|
|
||||||
yield from self._offline_stream(question)
|
|
||||||
return
|
|
||||||
|
|
||||||
response = client.chat.completions.create(
|
def format_message(message: AIMessage) -> str:
|
||||||
model=self.model,
|
if message.content:
|
||||||
messages=[
|
return str(message.content)
|
||||||
{
|
if message.tool_calls:
|
||||||
"role": "system",
|
call = message.tool_calls[0]
|
||||||
"content": "You are a concise educational assistant.",
|
return f"{call['name']}({call['args']})"
|
||||||
},
|
return ""
|
||||||
{"role": "user", "content": question},
|
|
||||||
],
|
|
||||||
stream=True,
|
|
||||||
)
|
|
||||||
for chunk in response:
|
|
||||||
delta = chunk.choices[0].delta.content
|
|
||||||
if delta:
|
|
||||||
yield delta
|
|
||||||
|
|
||||||
def _build_client(self):
|
|
||||||
api_key = os.getenv("OPENAI_API_KEY")
|
|
||||||
if not api_key:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
from openai import OpenAI
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
return OpenAI(
|
|
||||||
api_key=api_key,
|
|
||||||
base_url=os.getenv("OPENAI_BASE_URL") or None,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _offline_stream(self, question: str) -> Iterable[str]:
|
def format_chunk_message(chunk: tuple[Any, dict[str, Any]], state: dict[str, int]) -> None:
|
||||||
answer = (
|
message, meta = chunk
|
||||||
"Stream mode returns an answer piece by piece instead of waiting "
|
current_step = int(meta.get("langgraph_step", state["step"]))
|
||||||
"for the whole response. This makes an AI-agent feel faster and "
|
if current_step != state["step"]:
|
||||||
f"lets the UI show progress while processing: {question}"
|
state["step"] = current_step
|
||||||
)
|
print("\n --- --- --- \n")
|
||||||
for token in answer.split(" "):
|
if getattr(message, "content", None):
|
||||||
yield token + " "
|
print(message.content, end="", flush=True)
|
||||||
time.sleep(0.02)
|
|
||||||
|
|
||||||
|
|
||||||
|
def run_stream(question: str) -> None:
|
||||||
|
agent = build_agent()
|
||||||
|
stream = agent.stream(
|
||||||
|
{"messages": [{"role": "human", "content": question}]},
|
||||||
|
stream_mode=["messages", "updates"],
|
||||||
|
)
|
||||||
|
state = {"step": 1}
|
||||||
|
for chunk_type, chunk_data in stream:
|
||||||
|
if chunk_type == "messages":
|
||||||
|
format_chunk_message(chunk_data, state)
|
||||||
|
elif chunk_type == "updates" and chunk_data.get("model"):
|
||||||
|
last_message = chunk_data["model"]["messages"][-1]
|
||||||
|
formatted = format_message(last_message)
|
||||||
|
if formatted:
|
||||||
|
print(f"\n{formatted}")
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser(description="Streaming AI-agent demo")
|
parser = argparse.ArgumentParser(description="Streaming AI-agent demo")
|
||||||
parser.add_argument("question", nargs="*", help="User question")
|
parser.add_argument("question", nargs="*", help="User question")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
question = " ".join(args.question).strip() or "Explain streaming mode"
|
question = " ".join(args.question).strip() or "Сколько стоит молоко и хлеб в Казани?"
|
||||||
agent = StreamingAIAgent()
|
run_stream(question)
|
||||||
for token in agent.stream(question):
|
|
||||||
print(token, end="", flush=True)
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user