From f38d0aa58788d83bdfd51bdbb19a112a421654ec Mon Sep 17 00:00:00 2001 From: zeeland Date: Sat, 20 Jul 2024 03:56:15 +0800 Subject: [PATCH 1/4] feat: add dify chatbot to chat pne --- docs/index.html | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/index.html b/docs/index.html index d18c4265..78387be6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -101,8 +101,21 @@ - - + + + From fe7c29f0b9542da38398a6e9c72682b6dde16187 Mon Sep 17 00:00:00 2001 From: zeeland Date: Sat, 20 Jul 2024 04:03:19 +0800 Subject: [PATCH 2/4] docs: add how to guide --- docs/_sidebar.md | 9 ++-- docs/get_started/how-to-guide.md | 18 ++++++++ docs/get_started/quick_start.md | 17 ++++---- docs/use_cases/streamlit+pne.chat().md | 4 -- .../build-math-application-with-agent.ipynb | 43 ++++--------------- promptulate/agents/base.py | 2 +- 6 files changed, 40 insertions(+), 53 deletions(-) create mode 100644 docs/get_started/how-to-guide.md diff --git a/docs/_sidebar.md b/docs/_sidebar.md index eedba6df..6e146816 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,6 +1,7 @@ - Get started - - [:bookmark_tabs: Introduction](README.md) - - [:bookmark: Quick Start](get_started/quick_start.md#quick-start) + - [Introduction](README.md) + - [Quick Start](get_started/quick_start.md#quick-start) + - [How-to Guide](get_started/how-to-guide.md#how-to-guides) - Use Cases - [Best practices](use_cases/intro.md#use-cases) @@ -11,9 +12,9 @@ - Modules - [:robot: Agent](modules/agent.md#agent) - - [:alien: Assistant Agent](modules/agents/assistant_agent_usage.md#assistant-agent) + - [Assistant Agent](modules/agents/assistant_agent_usage.md#assistant-agent) - [:notebook_with_decorative_cover: LLMs](modules/llm/llm.md#llm) - - [ LLM Factory](modules/llm/llm-factory-usage.md#LLMFactory) + - [LLM Factory](modules/llm/llm-factory-usage.md#LLMFactory) - [Custom LLM](modules/llm/custom_llm.md#custom-llm) - [OpenAI](modules/llm/openai.md#openai) - [Erniebot 百度文心](modules/llm/erniebot.md#百度文心erniebot) diff --git a/docs/get_started/how-to-guide.md b/docs/get_started/how-to-guide.md new file mode 100644 index 00000000..d38a713c --- /dev/null +++ b/docs/get_started/how-to-guide.md @@ -0,0 +1,18 @@ +# How-to guides + +Here you’ll find answers to “How do I….?” types of questions. These guides are goal-oriented and concrete; they're meant to help you complete a specific task. + +## Key features + +This highlights functionality that is core to using Promptulate. + +- [How to: return structured data from a model](use_cases/chat_usage.md#structured-output) + + +- [How to write model name in pne](other/how_to_write_model_name.md) + + +- [How to use pne.chat() and AIChat()](use_cases/chat_usage.md#chat) + + +- [How to build a streamlit app by pne](use_cases/streamlit+pne.chat().md#build-a-simple-chatbot-using-streamlit-and-pne) diff --git a/docs/get_started/quick_start.md b/docs/get_started/quick_start.md index 97599e38..6c9b56ad 100644 --- a/docs/get_started/quick_start.md +++ b/docs/get_started/quick_start.md @@ -29,7 +29,7 @@ The following diagram shows the core architecture of `promptulate`: Now let's see how to use `pne.chat()` to chat with the model. The following example we use `gpt-4-turbo` to chat with the model. ```python -import promptulate as pne +import pne response: str = pne.chat(messages="What is the capital of China?", model="gpt-4-turbo") ``` @@ -85,7 +85,7 @@ The powerful model support of pne allows you to easily build any third-party mod Now let's see how to run local llama3 models of ollama with pne. ```python -import promptulate as pne +import pne resp: str = pne.chat(model="ollama/llama2", messages=[{"content": "Hello, how are you?", "role": "user"}]) ``` @@ -95,7 +95,7 @@ resp: str = pne.chat(model="ollama/llama2", messages=[{"content": "Hello, how ar You can use the available multimodal capabilities of it in any of your promptulate applications! ```python -import promptulate as pne +import pne messages=[ { @@ -163,7 +163,7 @@ pne.chat() 是 pne 中最强大的函数,在实际的 LLM Agent 应用开发 ```python from typing import List -import promptulate as pne +import pne from pydantic import BaseModel, Field class LLMResponse(BaseModel): @@ -183,7 +183,7 @@ provinces=['Anhui', 'Fujian', 'Gansu', 'Guangdong', 'Guizhou', 'Hainan', 'Hebei' ```python import os -import promptulate as pne +import pne from langchain.agents import load_tools os.environ["OPENAI_API_KEY"] = "your-key" @@ -292,7 +292,7 @@ Agent是`promptulate`的核心组件之一,其核心思想是使用llm、Tool 下面的示例展示了如何使用`ToolAgent`结合Tool进行使用。 ```python -import promptulate as pne +import pne from promptulate.tools import ( DuckDuckGoTool, Calculator, @@ -338,7 +338,7 @@ Below is an example of how to use the promptulate and langchain libraries to cre > You need to set the `OPENAI_API_KEY` environment variable to your OpenAI API key. Click [here](https://undertone0809.github.io/promptulate/#/modules/tools/langchain_tool_usage?id=langchain-tool-usage) to see the detail. ```python -import promptulate as pne +import pne from langchain.agents import load_tools tools: list = load_tools(["dalle-image-generator"]) @@ -363,10 +363,9 @@ Here is the generated image: [![Halloween Night at a Haunted Museum](https://oai 下面的示例展示了在 WebAgent 中使用格式化输出的最佳实践: ```python +import pne from pydantic import BaseModel, Field -import promptulate as pne - class Response(BaseModel): city: str = Field(description="City name") diff --git a/docs/use_cases/streamlit+pne.chat().md b/docs/use_cases/streamlit+pne.chat().md index e49078c5..f3096563 100644 --- a/docs/use_cases/streamlit+pne.chat().md +++ b/docs/use_cases/streamlit+pne.chat().md @@ -135,7 +135,3 @@ pip install -r requirements.txt ```shell streamlit run app.py ``` - -The running result is as follows: - -![streamlit+pne](./img/streamlit+pne.png) \ No newline at end of file diff --git a/example/build-math-application-with-agent/build-math-application-with-agent.ipynb b/example/build-math-application-with-agent/build-math-application-with-agent.ipynb index e091a782..2e8d8ef8 100644 --- a/example/build-math-application-with-agent/build-math-application-with-agent.ipynb +++ b/example/build-math-application-with-agent/build-math-application-with-agent.ipynb @@ -37,7 +37,6 @@ { "cell_type": "code", "execution_count": 1, - "outputs": [], "source": [ "OPENAI_API_KEY=\"your_openai_api_key\"" ], @@ -48,7 +47,8 @@ "start_time": "2024-05-16T12:59:12.434018100Z" } }, - "id": "bdba0ee6cdddfda1" + "id": "bdba0ee6cdddfda1", + "outputs": [] }, { "cell_type": "markdown", @@ -115,7 +115,6 @@ { "cell_type": "code", "execution_count": 2, - "outputs": [], "source": [ "from promptulate.tools.wikipedia.tools import wikipedia_search\n", "from promptulate.tools.math.tools import calculator\n", @@ -128,7 +127,8 @@ "start_time": "2024-05-16T12:59:12.454715600Z" } }, - "id": "bf24915502504abb" + "id": "bf24915502504abb", + "outputs": [] }, { "cell_type": "markdown", @@ -265,7 +265,6 @@ { "cell_type": "code", "execution_count": 6, - "outputs": [], "source": [ "# reasoning based tool\n", "def word_problem_tool(question: str) -> str:\n", @@ -293,7 +292,8 @@ "start_time": "2024-05-16T12:59:15.261878200Z" } }, - "id": "9e135028cb2f3b9" + "id": "9e135028cb2f3b9", + "outputs": [] }, { "cell_type": "markdown", @@ -309,34 +309,6 @@ { "cell_type": "code", "execution_count": 7, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001B[31;1m\u001B[1;3m[Agent] Tool Agent start...\u001B[0m\n", - "\u001B[36;1m\u001B[1;3m[User instruction] I have 3 apples and 4 oranges.I give half of my oranges away and buy two dozen new ones,along with three packs of strawberries.Each pack of strawberry has 30 strawberries.How many total pieces of fruit do I have at the end?\u001B[0m\n", - "\u001B[33;1m\u001B[1;3m[Thought] To determine the total number of pieces of fruit, we calculate the number of remaining oranges after giving half away, then add the new oranges purchased and the total strawberries from the three packs. Apples remain constant at 3.\u001B[0m\n", - "\u001B[33;1m\u001B[1;3m[Action] word_problem_tool args: {'question': 'If a person has 4 oranges and gives half away, how many are left? They then buy 24 more oranges and acquire 3 packs of strawberries with each pack containing 30 strawberries. How many pieces of fruit do they have in total if they originally had 3 apples?'}\u001B[0m\n", - "\u001B[33;1m\u001B[1;3m[Observation] - Start with the number of oranges the person initially has: 4 oranges.\n", - "- They give away half of these oranges, so let's calculate half of 4:\n", - " - 4 / 2 = 2 oranges.\n", - "- The number of oranges the person has left after giving half away is 2.\n", - "- The person then buys 24 more oranges, so we add these to the remaining oranges:\n", - " - 2 oranges (remaining after giving away half) + 24 oranges (bought) = 26 oranges.\n", - "- Next, we account for the acquired strawberry packs. There are 3 packs, each with 30 strawberries.\n", - " - 3 packs * 30 strawberries per pack = 90 strawberries.\n", - "- The person originally had 3 apples, which we'll add to the total count of fruit:\n", - " - 3 apples (originally had).\n", - "- To calculate the total pieces of fruit, add the number of oranges, strawberries, and apples together:\n", - " - 26 oranges (after transactions) + 90 strawberries (from the packs) + 3 apples (originally had) = 119 pieces of fruit.\n", - "- Final answer: The person has 119 pieces of fruit in total.\u001B[0m\n", - "\u001B[32;1m\u001B[1;3m[Agent Result] 119\u001B[0m\n", - "\u001B[38;5;200m\u001B[1;3m[Agent] Agent End.\u001B[0m\n", - "119\n" - ] - } - ], "source": [ "# agent\n", "agent = pne.ToolAgent(tools=[wikipedia_tool, math_tool, word_problem_tool],\n", @@ -352,7 +324,8 @@ "start_time": "2024-05-16T12:59:15.277354200Z" } }, - "id": "5b03977f863172b" + "id": "5b03977f863172b", + "outputs": [] }, { "cell_type": "markdown", diff --git a/promptulate/agents/base.py b/promptulate/agents/base.py index 3723f9b3..eafeda2b 100644 --- a/promptulate/agents/base.py +++ b/promptulate/agents/base.py @@ -48,7 +48,7 @@ def run( prompt = ( f"{formatter.get_formatted_instructions()}\n##User input:\n{result}" ) - json_response = self.get_llm()(prompt) + json_response: str = self.get_llm()(prompt) return formatter.formatting_result(json_response) Hook.call_hook( From 55098bf73034ce8d1d0ac1cfe6e42953dda30ca6 Mon Sep 17 00:00:00 2001 From: zeeland Date: Sat, 20 Jul 2024 04:04:19 +0800 Subject: [PATCH 3/4] feat: add memory for aichat --- promptulate/chat.py | 46 ++++++++++++++++++++++++++++++++----------- promptulate/schema.py | 38 ++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- tests/test_chat.py | 14 +++++++++++++ 4 files changed, 86 insertions(+), 14 deletions(-) diff --git a/promptulate/chat.py b/promptulate/chat.py index 93195c14..c59fa4a7 100644 --- a/promptulate/chat.py +++ b/promptulate/chat.py @@ -1,6 +1,7 @@ from typing import Dict, List, Optional, TypeVar, Union from promptulate.agents.base import BaseAgent +from promptulate.agents.tool_agent.agent import ToolAgent from promptulate.beta.agents.assistant_agent import AssistantAgent from promptulate.llms import BaseLLM from promptulate.llms.factory import LLMFactory @@ -11,6 +12,7 @@ BaseMessage, MessageSet, StreamIterator, + SystemMessage, ) from promptulate.tools.base import BaseTool, ToolTypes from promptulate.utils.logger import logger @@ -30,7 +32,6 @@ def _convert_message(messages: Union[List, MessageSet, str]) -> MessageSet: """ if isinstance(messages, str): messages: List[Dict] = [ - {"content": "You are a helpful assistant", "role": "system"}, {"content": messages, "role": "user"}, ] if isinstance(messages, list): @@ -79,6 +80,7 @@ def __init__( tools: Optional[List[ToolTypes]] = None, custom_llm: Optional[BaseLLM] = None, enable_plan: bool = False, + enable_memory: bool = False, ): """Initialize the AIChat. @@ -89,18 +91,20 @@ def __init__( will use Agent to run. custom_llm(Optional[BaseLLM]): custom LLM instance. enable_plan(bool): use Agent with plan ability if True. + enable_memory(bool): enable memory if True. """ self.llm: BaseLLM = _get_llm(model, model_config, custom_llm) self.tools: Optional[List[ToolTypes]] = tools self.agent: Optional[BaseAgent] = None + self.enable_memory: bool = enable_memory + self.memory: MessageSet = MessageSet(messages=[]) + if tools: if enable_plan: self.agent = AssistantAgent(tools=self.tools, llm=self.llm) logger.info("[pne chat] invoke AssistantAgent with plan ability.") else: - from promptulate.agents.tool_agent.agent import ToolAgent - self.agent = ToolAgent(tools=self.tools, llm=self.llm) logger.info("[pne chat] invoke ToolAgent.") @@ -113,7 +117,7 @@ def run( stream: bool = False, **kwargs, ) -> Union[str, BaseMessage, T, List[BaseMessage], StreamIterator]: - """Run the AIChat. + """Run the AIChat, AIChat use self.memory to store chat messages. Args: messages(Union[List, MessageSet, str]): chat messages. It can be str or @@ -139,33 +143,51 @@ def run( "stream, tools and output_schema can't be True at the same time, " "because stream is used to return Iterator[BaseMessage]." ) + + if not self.enable_memory: + self.memory: MessageSet = MessageSet(messages=[]) + + _: MessageSet = _convert_message(messages) + self.memory.add_from_message_set(_) + + # initialize memory with system message if it is empty + if len(self.memory.messages) == 1: + self.memory.messages = [ + SystemMessage(content="You are a helpful assistant"), + *self.memory.messages, + ] + if self.agent: - return self.agent.run(messages, output_schema=output_schema) + response: Union[str, BaseModel] = self.agent.run( + self.memory.string_messages, output_schema=output_schema + ) + self.memory.add_ai_message(response) - messages: MessageSet = _convert_message(messages) + return response # add output format into the last prompt if provide if output_schema: instruction: str = get_formatted_instructions( json_schema=output_schema, examples=examples ) - messages.messages[-1].content += f"\n{instruction}" + self.memory.messages[-1].content += f"\n{instruction}" logger.info(f"[pne chat] messages: {messages}") response: Union[AssistantMessage, StreamIterator] = self.llm.predict( - messages, stream=stream, **kwargs + self.memory, stream=stream, **kwargs ) + # TODO: add stream memory support if stream: return response - if isinstance(response, AssistantMessage): - # Access additional_kwargs only if response is AssistantMessage - logger.info(f"[pne chat] response: {response.additional_kwargs}") + logger.info( + f"[pne chat] response: {response.additional_kwargs or response.content}" + ) + self.memory.add_ai_message(response.content) # return output format if provide if output_schema: - logger.info("[pne chat] return formatted response.") return formatting_result( pydantic_obj=output_schema, llm_output=response.content ) diff --git a/promptulate/schema.py b/promptulate/schema.py index b9bf7d74..6c6adba0 100644 --- a/promptulate/schema.py +++ b/promptulate/schema.py @@ -1,4 +1,6 @@ +import warnings from abc import abstractmethod +from datetime import datetime from enum import Enum from typing import Any, Callable, Dict, Iterator, List, Optional, Union @@ -198,6 +200,15 @@ def __init__( self.messages: List[BaseMessage] = messages self.conversation_id: Optional[str] = conversation_id self.additional_kwargs: dict = additional_kwargs or {} + self.created_at: datetime = datetime.now() + + if conversation_id: + # show tip, this will be deprecated in v1.9.0 + warnings.warn( + "The parameter 'conversation_id' is deprecated and will be removed in version 1.9.0.", # noqa + DeprecationWarning, + stacklevel=2, + ) @classmethod def from_listdict_data( @@ -268,9 +279,34 @@ def add_system_message(self, message: str) -> None: def add_user_message(self, message: str) -> None: self.messages.append(UserMessage(content=message)) - def add_ai_message(self, message: str) -> None: + def add_ai_message(self, message: Union[str, BaseModel]) -> None: + """Add a message from an AI model. If the message has a model_dump method, which + means it's a pydantic model, it will be dumped to a string and added to the + message set. Otherwise, it will be added as a string. + + Args: + message(str | BaseModel): The message from the AI model. + + Returns: + None + """ + if hasattr(message, "model_dump"): + _: dict = message.model_dump() + self.messages.append(AssistantMessage(content=str(_), additional_kwargs=_)) + return + self.messages.append(AssistantMessage(content=message)) + def add_from_message_set(self, message_set: "MessageSet") -> None: + """Add messages from another message. + Args: + message_set: + + Returns: + None + """ + self.messages.extend(message_set.messages) + def init_chat_message_history( system_content: str, user_content: str, llm: LLMType diff --git a/pyproject.toml b/pyproject.toml index 0b364079..87a10185 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ name = "promptulate" readme = "README.md" homepage = "https://github.com/Undertone0809/promptulate" repository = "https://github.com/Undertone0809/promptulate" -version = "1.16.7" +version = "1.17.0" keywords = [ "promptulate", "pne", diff --git a/tests/test_chat.py b/tests/test_chat.py index b0200521..992bf369 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -182,3 +182,17 @@ def test_streaming(): assert answer[1].content == "is" assert answer[2].content == "fake" assert answer[3].content == "message" + + +def test_aichat_memory(): + model = FakeLLM() + ai = pne.AIChat(custom_llm=model, enable_memory=True) + + ai.run("hello") + ai.run("bye") + assert len(ai.memory.messages) == 5 + assert ai.memory.messages[0].content == "You are a helpful assistant" + assert ai.memory.messages[1].content == "hello" + assert ai.memory.messages[2].content == "fake response" + assert ai.memory.messages[3].content == "bye" + assert ai.memory.messages[4].content == "fake response" From 7657a22fa9853e94aa937f3f71178a8512cb2787 Mon Sep 17 00:00:00 2001 From: zeeland Date: Sat, 20 Jul 2024 04:04:36 +0800 Subject: [PATCH 4/4] feat: add created_at for BaseMessage --- promptulate/schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/promptulate/schema.py b/promptulate/schema.py index 6c6adba0..2c587f05 100644 --- a/promptulate/schema.py +++ b/promptulate/schema.py @@ -24,6 +24,7 @@ class BaseMessage(BaseModel): content: str additional_kwargs: dict = Field(default_factory=dict) + created_at: datetime = Field(default_factory=datetime.now) @property @abstractmethod