Update stream_agent.py

This commit is contained in:
2026-05-18 10:52:42 +00:00
parent 4fd98b291d
commit 7667e99d7f
+58 -55
View File
@@ -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"),
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(
model=self.model,
messages=[
{
"role": "system",
"content": "You are a concise educational assistant.",
},
{"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, 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 _offline_stream(self, question: str) -> Iterable[str]:
answer = ( def format_message(message: AIMessage) -> str:
"Stream mode returns an answer piece by piece instead of waiting " if message.content:
"for the whole response. This makes an AI-agent feel faster and " return str(message.content)
f"lets the UI show progress while processing: {question}" if message.tool_calls:
call = message.tool_calls[0]
return f"{call['name']}({call['args']})"
return ""
def format_chunk_message(chunk: tuple[Any, dict[str, Any]], state: dict[str, int]) -> None:
message, meta = chunk
current_step = int(meta.get("langgraph_step", state["step"]))
if current_step != state["step"]:
state["step"] = current_step
print("\n --- --- --- \n")
if getattr(message, "content", None):
print(message.content, end="", flush=True)
def run_stream(question: str) -> None:
agent = build_agent()
stream = agent.stream(
{"messages": [{"role": "human", "content": question}]},
stream_mode=["messages", "updates"],
) )
for token in answer.split(" "): state = {"step": 1}
yield token + " " for chunk_type, chunk_data in stream:
time.sleep(0.02) 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__":