commit d06929ba6e750c1e96335b06278d1a7e8bddcc17 Author: kuzakhmetovartur Date: Thu May 28 12:55:03 2026 +0300 feat: solution for 'Практическое задание: AI-агент на LangChain' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b16538b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.env +dist/ +build/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..c555051 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# Shopping List AI Agent + +This project demonstrates a simple hierarchical AI agent built with LangChain that helps plan a shopping list, fetches prices for each item using a sub‑agent, and calculates the total cost. + +## Prerequisites + +- Python 3.10+ +- A local LLM server (e.g., LM Studio) running at `http://localhost:1234/v1`. + The server must expose an OpenAI‑compatible API. + +## Installation + +bash +# Clone the repository +git clone https://github.com/your-username/shopping-list-agent.git +cd shopping-list-agent + +# Create a virtual environment (optional but recommended) +python -m venv venv +source venv/bin/activate # On Windows use `venv\\Scripts\\activate` + +# Install dependencies +pip install -r requirements.txt + + +## Running the Agent + +bash +python src/main.py + + +The script will ask the agent to plan a shopping list for the following request: + + +Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани. + + +The agent will: + +1. Parse the list of products and the city. +2. For each product, invoke the `get_price` tool, which internally creates a sub‑agent that generates a realistic price table. +3. Sum the prices and output the final total. + +All intermediate tool calls and the final answer are printed to the console. + +## Customization + +- **Model**: Change the `model` parameter in `src/main.py` to match the model name exposed by your LM Studio instance. +- **Temperature**: Adjust the `temperature` argument to control the randomness of the responses. + +## License + +MIT License \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..f2af716 --- /dev/null +++ b/main.py @@ -0,0 +1,70 @@ +import json +from langchain_openai import ChatOpenAI +from pydantic import SecretStr +from langchain.tools import tool +from langchain.agents import create_agent + +# Configure the local LLM +llm = ChatOpenAI( + model="gpt-4o-mini", # Replace with your LM Studio model name + base_url="http://localhost:1234/v1", + api_key=SecretStr("fake"), + temperature=0.7, +) + +@tool +def get_price(product: str, city: str) -> str: + """Get realistic price for a product in a city. Returns a markdown table.""" + # Create a sub-agent that generates a price table + sub_agent = create_agent( + model=llm, + tools=[], + system_prompt=f"You are a price estimator. Provide a realistic price for {product} in {city}. Return a markdown table with columns: Product, Price (rub.), Store.", + ) + # Invoke the sub-agent + result = sub_agent.invoke( + { + "messages": [ + {"role": "user", "content": f"Provide price for {product} in {city}."} + ] + } + ) + # Extract the assistant message content + messages = result.get("messages", []) + for msg in messages: + if msg.get("role") == "assistant" and msg.get("content"): + return msg["content"] + return "No price data available." + +def main(): + # Main agent that uses the get_price tool + agent = create_agent( + model=llm, + tools=[get_price], + system_prompt="You are a shopping list planner. Use the get_price tool to find prices for items.", + ) + # Sample user query + user_query = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани." + # Invoke the agent + result = agent.invoke( + { + "messages": [ + {"role": "human", "content": user_query} + ] + } + ) + # Print all messages, including tool calls and final answer + for msg in result.get("messages", []): + role = msg.get("role") + content = msg.get("content") + tool_calls = msg.get("tool_calls") + if content: + print(f"{role}: {content}") + elif tool_calls: + for call in tool_calls: + print(f"{role} calls {call.get('name')} with args {call.get('arguments')}") + else: + print(f"{role}: (no content)") + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1939613 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +langchain==1.2.10 +langchain-openai==1.1.9 \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..c8d971f --- /dev/null +++ b/src/main.py @@ -0,0 +1,50 @@ +import os +from langchain_openai import ChatOpenAI +from langchain.tools import tool +from langchain.agents import create_agent +from pydantic import SecretStr + +# Initialize the LLM +llm = ChatOpenAI( + model="gpt-4o-mini", + base_url="http://localhost:1234/v1", + api_key=SecretStr("fake"), + temperature=0.7, +) + +@tool +def get_price(product: str, city: str) -> str: + """Get price for a product in a city. Returns a markdown table with columns: Продукт, Цена (руб.), Магазин.""" + # Create a sub-agent to estimate the price + sub_agent = create_agent( + model=llm, + tools=[], + system_prompt=f"""You are a price estimator for product '{product}' in city '{city}'. Provide a realistic price based on historical data. Output a markdown table with columns: Продукт, Цена (руб.), Магазин.""" + ) + # Invoke the sub-agent + sub_response = sub_agent.invoke({"messages": [{"role": "human", "content": ""}]}) + # Return the content of the last message + return sub_response["messages"][-1]["content"] + +# Main agent +main_agent = create_agent( + model=llm, + tools=[get_price], + system_prompt="Ты помощник по планированию покупок. Принимаешь список продуктов и город. Для каждого продукта вызывай инструмент get_price, затем суммируй цены и выдавай итоговую стоимость." +) + +def main(): + question = "Помоги составить список покупок: молоко, хлеб, яблоки. Я нахожусь в Казани." + response = main_agent.invoke({"messages": [{"role": "human", "content": question}]}) + # Print all messages + for msg in response["messages"]: + role = msg.get("role", "") + if role == "assistant": + print(msg["content"]) + elif role == "tool": + print(f"Tool call: {msg['name']} with args {msg.get('arguments')}") + else: + print(msg["content"]) + +if __name__ == "__main__": + main() \ No newline at end of file