import os import sys import asyncio from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage from langchain.tools import tool from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend from pydantic import BaseModel, Field from langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import PromptTemplate load_dotenv() # Pydantic models class PersonInfo(BaseModel): name: str = Field(description="Full name of the person") age: int | None = Field(description="Age in years, optional", default=None) profession: str = Field(description="Current profession") skills: list[str] = Field(description="List of skills") class MeetingNotes(BaseModel): date: str = Field(description="Date of the meeting") participants: list[str] = Field(description="List of participants") topics: list[str] = Field(description="Discussion topics") decisions: list[str] = Field(description="Decisions made") next_steps: list[str] = Field(description="Next steps to be taken") # LLM configuration llm = ChatOpenAI( model="openai/gpt-oss-20b:free", base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OPENAI_API_KEY"), temperature=0.0, ) # Backend for deepagents backend = CompositeBackend([ LocalShellBackend(workspace_dir="./workspace"), FilesystemBackend(), ]) # Parsers person_parser = PydanticOutputParser(pydantic_object=PersonInfo) meeting_parser = PydanticOutputParser(pydantic_object=MeetingNotes) # Prompt template prompt_template = PromptTemplate( input_variables=["format_instructions", "text"], template=( "Extract structured data from the following text. " "Follow the format:\n{format_instructions}\n\nText:\n{text}\n\nOutput:" ), ) # Helper to decide which parser to use def choose_parser(text: str): lower = text.lower() if any(word in lower for word in ["age", "years", "profession", "skills", "skill"]): return person_parser if any(word in lower for word in ["meeting", "participants", "topics", "decisions", "next steps", "next_step"]): return meeting_parser # Default to person_parser return person_parser # Tool for extraction @tool def extract_structured(text: str) -> str: """ Extract structured data from the given text and return a JSON string. """ parser = choose_parser(text) format_instructions = parser.get_format_instructions() prompt = prompt_template.format(format_instructions=format_instructions, text=text) raw_output = llm.invoke(prompt).content try: parsed_obj = parser.parse(raw_output) return parsed_obj.model_dump_json() except Exception as e: return f"Error parsing output: {e}" # Create the agent agent = create_deep_agent( model=llm, tools=[extract_structured], backend=backend, system_prompt="You are a helpful agent that extracts structured data from text.", ) # CLI logic async def run_agent(text: str): result = await agent.ainvoke( {"messages": [HumanMessage(content=text)]}, {"configurable": {"thread_id": "session-1"}}, ) output = result["messages"][-1].content try: data = PersonInfo.model_validate_json(output) obj_type = "PersonInfo" except Exception: try: data = MeetingNotes.model_validate_json(output) obj_type = "MeetingNotes" except Exception: print("Failed to parse JSON output.") return print("\nParsed object:") print(data.model_dump()) print("\nSummary:") if obj_type == "PersonInfo": print(f"{obj_type}: {data.name}, age={data.age}, profession={data.profession}, skills={data.skills}") else: print(f"{obj_type}: date={data.date}, participants={data.participants}, topics={data.topics}, decisions={data.decisions}, next_steps={data.next_steps}") def main(): if len(sys.argv) > 1: input_text = " ".join(sys.argv[1:]) asyncio.run(run_agent(input_text)) else: examples = [ ( "Анна, 28 лет, Python-разработчик. Навыки: FastAPI, Docker.", "PersonInfo example", ), ( "Встреча 12.09.2026. Участники: Иван, Мария. Темы: проект X, бюджет. Решения: увеличить бюджет. Следующие шаги: подготовить отчёт.", "MeetingNotes example", ), ] for text, title in examples: print(f"\n=== {title} ===") asyncio.run(run_agent(text)) if __name__ == "__main__": main()