Download src/doc_analysis_graph.py from hibanail/langgraph-demo: direct link, hf CLI and curl.
- Browser
- Download file 4.21 kB
-
https://huggingface.co/hibanail/langgraph-demo/resolve/main/src/doc_analysis_graph.py
- Command line
-
hf download hf://hibanail/langgraph-demo/src/doc_analysis_graph.py
-
curl -L -o doc_analysis_graph.py https://huggingface.co/hibanail/langgraph-demo/resolve/main/src/doc_analysis_graph.py
4.21 kB
| import base64 | |
| from typing import List, TypedDict, Annotated, Optional | |
| from langchain_openai import ChatOpenAI | |
| from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage | |
| from langgraph.graph.message import add_messages | |
| from langgraph.graph import START, END, StateGraph | |
| from langgraph.prebuilt import ToolNode, tools_condition | |
| from IPython.display import Image, display | |
| from dotenv import load_dotenv | |
| load_dotenv() # reads OPENAI_API_KEY from .env | |
| # How state got updated? (by Node return partial State update) | |
| # In LangGraph, a Node typically does not mutate the shared State directly. | |
| # It returns a partial State update, and LangGraph applies that update | |
| # Node receives State → performs work → returns partial updates → LangGraph merges updates into State. | |
| # lang chain llm message returned like this -> its pre defined | |
| # AIMessage( | |
| # content="Hello! How can I help you?", | |
| # tool_calls=[], | |
| # id="run-123", | |
| # response_metadata={...}, | |
| # usage_metadata={...} | |
| # ) | |
| class AgentState(TypedDict): | |
| input_file: Optional[str] | |
| messages: Annotated[list[AnyMessage], add_messages] | |
| vision_llm = ChatOpenAI(model="gpt-4o") # model supports reading iages | |
| def extract_text(img_path: str) -> str: | |
| """Extract text from an image using a vision model.""" | |
| with open(img_path, "rb") as image_file: | |
| image_bytes = image_file.read() | |
| image_base64 = base64.b64encode(image_bytes).decode("utf-8") # base64 encode the image | |
| message = [ | |
| HumanMessage( | |
| content=[ | |
| { | |
| "type": "text", | |
| "text": "Extract all the text from this image. Return only the extracted text." | |
| }, | |
| { | |
| "type": "image_url", | |
| "image_url": { | |
| "url": f"data:image/png;base64,{image_base64}" | |
| } | |
| } | |
| ] | |
| ) | |
| ] | |
| response = vision_llm.invoke(message) | |
| return response.content.strip() | |
| def divide(a: int, b: int) -> float: | |
| """Divide a and b.""" | |
| return a / b | |
| tools = [divide, extract_text] | |
| llm = ChatOpenAI(model="gpt-4o") | |
| llm_with_tools = llm.bind_tools( | |
| tools, | |
| parallel_tool_calls=False | |
| ) | |
| # بتستدعي الـ LLM وبترجع جوابه أو طلب استخدام أداة. | |
| def assistant(state: AgentState): | |
| image = state["input_file"] | |
| sys_msg = SystemMessage( | |
| content=f""" | |
| You are a helpful butler named Alfred | |
| that serves Mr. Wayne and Batman. | |
| You can analyse documents and run computations | |
| with provided tools: | |
| extract_text(img_path: str) -> str: | |
| Extract text from an image file. | |
| divide(a: int, b: int) -> float: | |
| Divide a and b. | |
| Currently the loaded image is: {image} | |
| """ | |
| ) | |
| return { | |
| "messages": [ | |
| llm_with_tools.invoke( # return AIMessage | |
| [sys_msg] + state["messages"] | |
| ) | |
| ], | |
| "input_file": state["input_file"] | |
| } | |
| # connect the graph: | |
| # 1. Create the graph builder | |
| builder = StateGraph(AgentState) | |
| # 2. Add nodes | |
| # The assistant node is responsible for both calling tools and generating the final response. | |
| builder.add_node("assistant", assistant) | |
| builder.add_node("tools", ToolNode(tools)) | |
| # 3. Set the starting point | |
| builder.add_edge(START, "assistant") | |
| # 4. Route based on tool calls work the same as tools_condition | |
| def route_assistant(state: AgentState): | |
| last_message = state["messages"][-1] | |
| if last_message.tool_calls: | |
| return "tools" | |
| return END | |
| builder.add_conditional_edges( | |
| "assistant", | |
| route_assistant, | |
| { | |
| "tools": "tools", | |
| END: END | |
| } | |
| ) | |
| # 5. Return from tools to assistant | |
| builder.add_edge("tools", "assistant") | |
| # 6. Compile | |
| react_graph = builder.compile() | |
| ############ call example: ############ | |
| initial_state = { | |
| "input_file": None, | |
| "messages": [ | |
| HumanMessage(content="Divide 100 by 5") | |
| ] | |
| } | |
| #invoke() — executes the Graph and returns the final State. | |
| #stream() — lets us observe what's happening during execution. | |
| result = react_graph.invoke(initial_state) | |
| print(result) |