From c505bb92514a68b6e23f6332e959e67aad7b11ca Mon Sep 17 00:00:00 2001 From: aiwithvd Date: Thu, 24 Oct 2024 15:38:50 +0530 Subject: [PATCH] Removing unnecessary builds and library --- build/lib/langgraph_lib/__init__.py | 22 -- build/lib/langgraph_lib/client/__init__.py | 5 - build/lib/langgraph_lib/client/client.py | 240 -------------------- build/lib/langgraph_lib/schema/__init__.py | 25 -- build/lib/langgraph_lib/schema/schema.py | 193 ---------------- build/lib/langgraph_lib/service/__init__.py | 5 - build/lib/langgraph_lib/service/service.py | 191 ---------------- dist/langgraph_lib-0.1.0-py3-none-any.whl | Bin 9324 -> 0 bytes dist/langgraph_lib-0.1.0.tar.gz | Bin 8571 -> 0 bytes 9 files changed, 681 deletions(-) delete mode 100644 build/lib/langgraph_lib/__init__.py delete mode 100644 build/lib/langgraph_lib/client/__init__.py delete mode 100644 build/lib/langgraph_lib/client/client.py delete mode 100644 build/lib/langgraph_lib/schema/__init__.py delete mode 100644 build/lib/langgraph_lib/schema/schema.py delete mode 100644 build/lib/langgraph_lib/service/__init__.py delete mode 100644 build/lib/langgraph_lib/service/service.py delete mode 100644 dist/langgraph_lib-0.1.0-py3-none-any.whl delete mode 100644 dist/langgraph_lib-0.1.0.tar.gz diff --git a/build/lib/langgraph_lib/__init__.py b/build/lib/langgraph_lib/__init__.py deleted file mode 100644 index c469e72..0000000 --- a/build/lib/langgraph_lib/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# src/langgraph_fastapi/__init__.py - -""" -LangGraph FastAPI Toolkit - -A Python package providing tools and infrastructure to serve LangGraph agents using FastAPI. -""" - -__version__ = "0.1.0" - -from .client import AgentClient -from .schema import ( - UserInput, - StreamInput, - AgentResponse, - ChatMessage, - Feedback, - FeedbackResponse, - ChatHistoryInput, - ChatHistory, -) -from .service import create_app diff --git a/build/lib/langgraph_lib/client/__init__.py b/build/lib/langgraph_lib/client/__init__.py deleted file mode 100644 index cbeb03c..0000000 --- a/build/lib/langgraph_lib/client/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# src/langgraph_fastapi/client/__init__.py - -from .client import AgentClient - -__all__ = ["AgentClient"] diff --git a/build/lib/langgraph_lib/client/client.py b/build/lib/langgraph_lib/client/client.py deleted file mode 100644 index d0f9323..0000000 --- a/build/lib/langgraph_lib/client/client.py +++ /dev/null @@ -1,240 +0,0 @@ -# src/my_library/client/client.py - -import json -import os -from typing import Any, AsyncGenerator, Generator, Optional, Dict - -import httpx - -from langgraph_lib.schema import ( - ChatHistory, - ChatHistoryInput, - ChatMessage, - Feedback, - StreamInput, - UserInput, -) - - -class AgentClient: - """Client for interacting with the agent service.""" - - def __init__( - self, - base_url: str = "http://localhost:80", - auth_secret: Optional[str] = None, - timeout: Optional[float] = None, - ) -> None: - """ - Initialize the client. - - Args: - base_url (str): The base URL of the agent service. - auth_secret (Optional[str]): Authentication secret for the agent service. - timeout (Optional[float]): Timeout for HTTP requests. - """ - self.base_url = base_url - self.auth_secret = auth_secret or os.getenv("AUTH_SECRET") - self.timeout = timeout - - @property - def _headers(self) -> Dict[str, str]: - headers = {} - if self.auth_secret: - headers["Authorization"] = f"Bearer {self.auth_secret}" - return headers - - async def ainvoke( - self, message: str, model: Optional[str] = None, thread_id: Optional[str] = None - ) -> ChatMessage: - """ - Asynchronously invoke the agent. Only the final message is returned. - - Args: - message (str): The message to send to the agent. - model (Optional[str]): LLM model to use for the agent. - thread_id (Optional[str]): Thread ID for continuing a conversation. - - Returns: - ChatMessage: The response from the agent. - """ - request = UserInput(message=message, model=model, thread_id=thread_id) - async with httpx.AsyncClient() as client: - response = await client.post( - f"{self.base_url}/invoke", - json=request.model_dump(), - headers=self._headers, - timeout=self.timeout, - ) - if response.status_code == 200: - return ChatMessage.model_validate(response.json()) - raise Exception(f"Error: {response.status_code} - {response.text}") - - def invoke( - self, message: str, model: Optional[str] = None, thread_id: Optional[str] = None - ) -> ChatMessage: - """ - Synchronously invoke the agent. Only the final message is returned. - - Args: - message (str): The message to send to the agent. - model (Optional[str]): LLM model to use for the agent. - thread_id (Optional[str]): Thread ID for continuing a conversation. - - Returns: - ChatMessage: The response from the agent. - """ - request = UserInput(message=message, model=model, thread_id=thread_id) - response = httpx.post( - f"{self.base_url}/invoke", - json=request.model_dump(), - headers=self._headers, - timeout=self.timeout, - ) - if response.status_code == 200: - return ChatMessage.model_validate(response.json()) - raise Exception(f"Error: {response.status_code} - {response.text}") - - def _parse_stream_line(self, line: str) -> Optional[ChatMessage | str]: - line = line.strip() - if line.startswith("data: "): - data = line[6:] - if data == "[DONE]": - return None - try: - parsed = json.loads(data) - except Exception as e: - raise Exception(f"Error parsing message from server: {e}") - if parsed["type"] == "message": - try: - return ChatMessage.model_validate(parsed["content"]) - except Exception as e: - raise Exception(f"Server returned invalid message: {e}") - elif parsed["type"] == "token": - return parsed["content"] - elif parsed["type"] == "error": - raise Exception(parsed["content"]) - return None - - def stream( - self, - message: str, - model: Optional[str] = None, - thread_id: Optional[str] = None, - stream_tokens: bool = True, - ) -> Generator[ChatMessage | str, None, None]: - """ - Stream the agent's response synchronously. - - Args: - message (str): The message to send to the agent. - model (Optional[str]): LLM model to use for the agent. - thread_id (Optional[str]): Thread ID for continuing a conversation. - stream_tokens (bool): Stream tokens as they are generated. - - Yields: - Generator[ChatMessage | str, None, None]: The response from the agent. - """ - request = StreamInput( - message=message, model=model, thread_id=thread_id, stream_tokens=stream_tokens - ) - with httpx.stream( - "POST", - f"{self.base_url}/stream", - json=request.model_dump(), - headers=self._headers, - timeout=self.timeout, - ) as response: - if response.status_code != 200: - raise Exception(f"Error: {response.status_code} - {response.text}") - for line in response.iter_lines(): - if line.strip(): - parsed = self._parse_stream_line(line) - if parsed is None: - break - yield parsed - - async def astream( - self, - message: str, - model: Optional[str] = None, - thread_id: Optional[str] = None, - stream_tokens: bool = True, - ) -> AsyncGenerator[ChatMessage | str, None]: - """ - Stream the agent's response asynchronously. - - Args: - message (str): The message to send to the agent. - model (Optional[str]): LLM model to use for the agent. - thread_id (Optional[str]): Thread ID for continuing a conversation. - stream_tokens (bool): Stream tokens as they are generated. - - Yields: - AsyncGenerator[ChatMessage | str, None]: The response from the agent. - """ - request = StreamInput( - message=message, model=model, thread_id=thread_id, stream_tokens=stream_tokens - ) - async with httpx.AsyncClient() as client: - async with client.stream( - "POST", - f"{self.base_url}/stream", - json=request.model_dump(), - headers=self._headers, - timeout=self.timeout, - ) as response: - if response.status_code != 200: - raise Exception(f"Error: {response.status_code} - {response.text}") - async for line in response.aiter_lines(): - if line.strip(): - parsed = self._parse_stream_line(line) - if parsed is None: - break - yield parsed - - async def acreate_feedback( - self, run_id: str, key: str, score: float, kwargs: Optional[Dict[str, Any]] = None - ) -> None: - """ - Create a feedback record for a run. - - Args: - run_id (str): The run ID to record feedback for. - key (str): The feedback key. - score (float): The feedback score. - kwargs (Optional[Dict[str, Any]]): Additional feedback parameters. - """ - request = Feedback(run_id=run_id, key=key, score=score, kwargs=kwargs or {}) - async with httpx.AsyncClient() as client: - response = await client.post( - f"{self.base_url}/feedback", - json=request.model_dump(), - headers=self._headers, - timeout=self.timeout, - ) - if response.status_code != 200: - raise Exception(f"Error: {response.status_code} - {response.text}") - - def get_history(self, thread_id: str) -> ChatHistory: - """ - Get chat history. - - Args: - thread_id (str): Thread ID for identifying a conversation. - - Returns: - ChatHistory: The chat history for the given thread ID. - """ - request = ChatHistoryInput(thread_id=thread_id) - response = httpx.post( - f"{self.base_url}/history", - json=request.model_dump(), - headers=self._headers, - timeout=self.timeout, - ) - if response.status_code == 200: - response_object = response.json() - return ChatHistory.model_validate(response_object) - else: - raise Exception(f"Error: {response.status_code} - {response.text}") diff --git a/build/lib/langgraph_lib/schema/__init__.py b/build/lib/langgraph_lib/schema/__init__.py deleted file mode 100644 index 22aadd8..0000000 --- a/build/lib/langgraph_lib/schema/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# src/langgraph_fastapi/schema/__init__.py - -from .schema import ( - UserInput, - StreamInput, - AgentResponse, - ChatMessage, - Feedback, - FeedbackResponse, - ChatHistoryInput, - ChatHistory, - convert_message_content_to_string, -) - -__all__ = [ - "UserInput", - "StreamInput", - "AgentResponse", - "ChatMessage", - "Feedback", - "FeedbackResponse", - "ChatHistoryInput", - "ChatHistory", - "convert_message_content_to_string", -] diff --git a/build/lib/langgraph_lib/schema/schema.py b/build/lib/langgraph_lib/schema/schema.py deleted file mode 100644 index 3732762..0000000 --- a/build/lib/langgraph_lib/schema/schema.py +++ /dev/null @@ -1,193 +0,0 @@ -# src/my_library/schema/schema.py - -from typing import Any, Dict, List, Optional, Union, Literal - -from langchain_core.messages import ( - AIMessage, - BaseMessage, - HumanMessage, - ToolCall, - ToolMessage, - message_to_dict, - messages_from_dict, -) -from pydantic import BaseModel, Field - - -def convert_message_content_to_string(content: Union[str, List[Union[str, Dict]]]) -> str: - if isinstance(content, str): - return content - text: List[str] = [] - for content_item in content: - if isinstance(content_item, str): - text.append(content_item) - continue - if content_item.get("type") == "text": - text.append(content_item.get("text", "")) - return "".join(text) - - -class UserInput(BaseModel): - """Basic user input for the agent.""" - - message: str = Field( - description="User input to the agent.", - examples=["What is the weather in Tokyo?"], - ) - model: Optional[str] = Field( - default="gpt-4o-mini", - description="LLM Model to use for the agent.", - examples=["gpt-4o-mini", "llama-3.1-70b"], - ) - thread_id: Optional[str] = Field( - default=None, - description="Thread ID to persist and continue a multi-turn conversation.", - examples=["847c6285-8fc9-4560-a83f-4e6285809254"], - ) - - -class StreamInput(UserInput): - """User input for streaming the agent's response.""" - - stream_tokens: bool = Field( - default=True, - description="Whether to stream LLM tokens to the client.", - ) - - -class AgentResponse(BaseModel): - """Response from the agent when called via /invoke.""" - - message: Dict[str, Any] = Field( - description="Final response from the agent, as a serialized LangChain message.", - examples=[ - { - "message": { - "type": "ai", - "data": {"content": "The weather in Tokyo is 70 degrees.", "type": "ai"}, - } - } - ], - ) - - -class ChatMessage(BaseModel): - """Message in a chat.""" - - type: Literal["human", "ai", "tool"] = Field( - description="Role of the message.", - examples=["human", "ai", "tool"], - ) - content: str = Field( - description="Content of the message.", - examples=["Hello, world!"], - ) - tool_calls: List[ToolCall] = Field( - default_factory=list, - description="Tool calls in the message.", - ) - tool_call_id: Optional[str] = Field( - default=None, - description="Tool call that this message is responding to.", - examples=["call_Jja7J89XsjrOLA5r!MEOW!SL"], - ) - run_id: Optional[str] = Field( - default=None, - description="Run ID of the message.", - examples=["847c6285-8fc9-4560-a83f-4e6285809254"], - ) - original: Dict[str, Any] = Field( - default_factory=dict, - description="Original LangChain message in serialized form.", - ) - - @classmethod - def from_langchain(cls, message: BaseMessage) -> "ChatMessage": - """Create a ChatMessage from a LangChain message.""" - original = message_to_dict(message) - content = convert_message_content_to_string(message.content) - if isinstance(message, HumanMessage): - return cls( - type="human", - content=content, - original=original, - ) - elif isinstance(message, AIMessage): - return cls( - type="ai", - content=content, - tool_calls=message.tool_calls or [], - original=original, - ) - elif isinstance(message, ToolMessage): - return cls( - type="tool", - content=content, - tool_call_id=message.tool_call_id, - original=original, - ) - else: - raise ValueError(f"Unsupported message type: {type(message).__name__}") - - def to_langchain(self) -> BaseMessage: - """Convert the ChatMessage to a LangChain message.""" - if self.original: - raw_original = messages_from_dict([self.original])[0] - raw_original.content = self.content - return raw_original - if self.type == "human": - return HumanMessage(content=self.content) - elif self.type == "ai": - return AIMessage(content=self.content, tool_calls=self.tool_calls) - elif self.type == "tool": - return ToolMessage(content=self.content, tool_call_id=self.tool_call_id) - else: - raise NotImplementedError(f"Unsupported message type: {self.type}") - - def pretty_print(self) -> None: - """Pretty print the ChatMessage.""" - lc_msg = self.to_langchain() - lc_msg.pretty_print() - - -class Feedback(BaseModel): - """Feedback for a run, to record to LangSmith.""" - - run_id: str = Field( - description="Run ID to record feedback for.", - examples=["847c6285-8fc9-4560-a83f-4e6285809254"], - ) - key: str = Field( - description="Feedback key.", - examples=["human-feedback-stars"], - ) - score: float = Field( - description="Feedback score.", - examples=[0.8], - ) - kwargs: Dict[str, Any] = Field( - default_factory=dict, - description="Additional feedback kwargs, passed to LangSmith.", - examples=[{"comment": "In-line human feedback"}], - ) - - -class FeedbackResponse(BaseModel): - """Response after submitting feedback.""" - - status: Literal["success"] = "success" - - -class ChatHistoryInput(BaseModel): - """Input for retrieving chat history.""" - - thread_id: str = Field( - description="Thread ID to persist and continue a multi-turn conversation.", - examples=["847c6285-8fc9-4560-a83f-4e6285809254"], - ) - - -class ChatHistory(BaseModel): - """Chat history containing a list of messages.""" - - messages: List[ChatMessage] diff --git a/build/lib/langgraph_lib/service/__init__.py b/build/lib/langgraph_lib/service/__init__.py deleted file mode 100644 index 44b9b4a..0000000 --- a/build/lib/langgraph_lib/service/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# src/langgraph_fastapi/service/__init__.py - -from .service import create_app - -__all__ = ["create_app"] diff --git a/build/lib/langgraph_lib/service/service.py b/build/lib/langgraph_lib/service/service.py deleted file mode 100644 index f41ab4f..0000000 --- a/build/lib/langgraph_lib/service/service.py +++ /dev/null @@ -1,191 +0,0 @@ -# src/langgraph_fastapi/service/service.py - -import json -import os -import warnings -from contextlib import asynccontextmanager -from typing import Any, AsyncGenerator, Dict, Optional, Tuple -from uuid import uuid4 - -from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status -from fastapi.responses import StreamingResponse -from fastapi.security import HTTPBearer -from langchain_core._api import LangChainBetaWarning -from langchain_core.messages import AnyMessage -from langchain_core.runnables import RunnableConfig -from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver -from langgraph.graph.state import CompiledStateGraph -from langsmith import Client as LangsmithClient - -from ..schema import ( - ChatHistory, - ChatHistoryInput, - ChatMessage, - Feedback, - FeedbackResponse, - StreamInput, - UserInput, - convert_message_content_to_string, -) - -warnings.filterwarnings("ignore", category=LangChainBetaWarning) - - -def verify_bearer(request: Request) -> None: - auth_secret = os.getenv("AUTH_SECRET") - if auth_secret: - auth_header = request.headers.get("Authorization") - if not auth_header or not auth_header.startswith("Bearer "): - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") - token = auth_header[len("Bearer ") :] - if token != auth_secret: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") - - -def create_app(agent: CompiledStateGraph) -> FastAPI: - """ - Create a FastAPI app to serve the provided LangGraph agent. - - Args: - agent (CompiledStateGraph): The LangGraph agent to serve. - - Returns: - FastAPI: The configured FastAPI application. - """ - - @asynccontextmanager - async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: - # Construct agent with Sqlite checkpointer - async with AsyncSqliteSaver.from_conn_string("checkpoints.db") as saver: - agent.checkpointer = saver - app.state.agent = agent - yield - # context manager will clean up the AsyncSqliteSaver on exit - - app = FastAPI(lifespan=lifespan, dependencies=[Depends(verify_bearer)]) - router = APIRouter() - - def _parse_input(user_input: UserInput) -> Tuple[Dict[str, Any], str]: - run_id = str(uuid4()) - thread_id = user_input.thread_id or str(uuid4()) - input_message = ChatMessage(type="human", content=user_input.message) - kwargs = { - "input": {"messages": [input_message.to_langchain()]}, - "config": RunnableConfig( - configurable={"thread_id": thread_id, "model": user_input.model}, run_id=run_id - ), - } - return kwargs, run_id - - def _remove_tool_calls(content: Any) -> Any: - """Remove tool calls from content.""" - if isinstance(content, str): - return content - return [ - content_item - for content_item in content - if isinstance(content_item, str) or content_item.get("type") != "tool_use" - ] - - @router.post("/invoke") - async def invoke(user_input: UserInput) -> ChatMessage: - """ - Invoke the agent with user input to retrieve a final response. - """ - agent_instance: CompiledStateGraph = app.state.agent - kwargs, run_id = _parse_input(user_input) - try: - response = await agent_instance.ainvoke(**kwargs) - output = ChatMessage.from_langchain(response["messages"][-1]) - output.run_id = run_id - return output - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - async def message_generator(user_input: StreamInput) -> AsyncGenerator[str, None]: - """ - Generate a stream of messages from the agent. - """ - agent_instance: CompiledStateGraph = app.state.agent - kwargs, run_id = _parse_input(user_input) - - async for event in agent_instance.astream_events(**kwargs, version="v2"): - if not event: - continue - - if ( - event["event"] == "on_chain_end" - and any(t.startswith("graph:step:") for t in event.get("tags", [])) - and "messages" in event["data"]["output"] - ): - new_messages = event["data"]["output"]["messages"] - for message in new_messages: - try: - chat_message = ChatMessage.from_langchain(message) - chat_message.run_id = run_id - except Exception as e: - yield f"data: {json.dumps({'type': 'error', 'content': f'Error parsing message: {e}'})}\n\n" - continue - if chat_message.type == "human" and chat_message.content == user_input.message: - continue - yield f"data: {json.dumps({'type': 'message', 'content': chat_message.model_dump()})}\n\n" - - if ( - event["event"] == "on_chat_model_stream" - and user_input.stream_tokens - and "llama_guard" not in event.get("tags", []) - ): - content = _remove_tool_calls(event["data"]["chunk"].content) - if content: - yield f"data: {json.dumps({'type': 'token', 'content': convert_message_content_to_string(content)})}\n\n" - continue - - yield "data: [DONE]\n\n" - - @router.post("/stream", response_class=StreamingResponse) - async def stream_agent(user_input: StreamInput) -> StreamingResponse: - """ - Stream the agent's response to a user input, including intermediate messages and tokens. - """ - return StreamingResponse(message_generator(user_input), media_type="text/event-stream") - - @router.post("/feedback") - async def feedback(feedback: Feedback) -> FeedbackResponse: - """ - Record feedback for a run to LangSmith. - """ - client = LangsmithClient() - kwargs = feedback.kwargs or {} - client.create_feedback( - run_id=feedback.run_id, - key=feedback.key, - score=feedback.score, - **kwargs, - ) - return FeedbackResponse() - - @router.post("/history") - def history(input: ChatHistoryInput) -> ChatHistory: - """ - Get chat history. - """ - agent_instance: CompiledStateGraph = app.state.agent - try: - state_snapshot = agent_instance.get_state( - config=RunnableConfig( - configurable={ - "thread_id": input.thread_id, - } - ) - ) - messages: List[AnyMessage] = state_snapshot.values["messages"] - chat_messages: List[ChatMessage] = [] - for message in messages: - chat_messages.append(ChatMessage.from_langchain(message)) - return ChatHistory(messages=chat_messages) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - app.include_router(router) - - return app diff --git a/dist/langgraph_lib-0.1.0-py3-none-any.whl b/dist/langgraph_lib-0.1.0-py3-none-any.whl deleted file mode 100644 index 3c4df012d1e17a3312b1f052788aa7b6d9a12802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9324 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 o^#2F055RjnQ1bsL^<{9 diff --git a/dist/langgraph_lib-0.1.0.tar.gz b/dist/langgraph_lib-0.1.0.tar.gz deleted file mode 100644 index 4836511e3b87db918bf118dbba37540cf425e778..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8571 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