diff --git "a/demo/langgraph_meta_prompt.ipynb" "b/demo/langgraph_meta_prompt.ipynb" new file mode 100644--- /dev/null +++ "b/demo/langgraph_meta_prompt.ipynb" @@ -0,0 +1,1614 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment the following line to install the required packages\n", + "\n", + "# %pip install langchain openai langchain_openai langchain_core langgraph" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Not running in Google Colab\n" + ] + } + ], + "source": [ + "import sys\n", + "import os\n", + "\n", + "if 'google.colab' in sys.modules:\n", + " print(\"Running in Google Colab\")\n", + " from google.colab import userdata\n", + "\n", + " # get secret openai_api_key and set it to OS env OPENAI_API_KEY\n", + " try:\n", + " openai_api_key = userdata.get('openai_api_key')\n", + " os.environ['OPENAI_API_KEY'] = openai_api_key\n", + " except:\n", + " print(\"No openai_api_key found in Google Colab\")\n", + "\n", + " # get secret openai_base_url\n", + " try:\n", + " openai_base_url = userdata.get('openai_base_url')\n", + " os.environ['OPENAI_API_BASE'] = openai_base_url\n", + " except:\n", + " print(\"No openai_base_url found in Google Colab\")\n", + "else:\n", + " print(\"Not running in Google Colab\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, Sequence, Dict, Any\n", + "\n", + "from typing_extensions import TypedDict\n", + "\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "from langgraph.graph import StateGraph, END\n", + "from langgraph.graph.message import add_messages\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langchain_core.messages import HumanMessage, SystemMessage, BaseMessage\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.pydantic_v1 import BaseModel\n", + "\n", + "import operator\n", + "import random\n", + "\n", + "# Can converge correctly\n", + "\n", + "# MODEL_NAME = \"anthropic/claude-3.5-sonnet:beta\"\n", + "# MODEL_NAME = \"openai/gpt-4o\"\n", + "# MODEL_NAME = \"openai/gpt-4-turbo\"\n", + "# MODEL_NAME = \"llama3-70b-8192\"\n", + "# MODEL_NAME = \"meta-llama/llama-3-70b-instruct\"\n", + "# MODEL_NAME = \"deepseek/deepseek-chat\"\n", + "# MODEL_NAME = \"qwen/qwen-2-72b-instruct\"\n", + "\n", + "# Failed to converge correctly\n", + "\n", + "# MODEL_NAME = \"llama3-8b-8192\"\n", + "# MODEL_NAME = \"mistralai/mixtral-8x22b-instruct\"\n", + "# MODEL_NAME = \"anthropic/claude-3-haiku:beta\"\n", + "MODEL_NAME = \"google/gemma-2-9b-it\"\n", + "# MODEL_NAME = \"meta-llama/llama-3-8b-instruct\"\n", + "# MODEL_NAME = \"microsoft/phi-3-medium-128k-instruct\"\n", + "# MODEL_NAME = \"mixtral-8x7b-32768\"\n", + "# MODEL_NAME = \"cohere/command-r\"\n", + "\n", + "llm = ChatOpenAI(model_name=MODEL_NAME, temperature=0.5)\n", + "\n", + "# EXECUTOR_MODEL = \"microsoft/phi-3-medium-128k-instruct\"\n", + "# EXECUTOR_MODEL = \"deepseek/deepseek-chat\"\n", + "# EXECUTOR_MODEL = \"gemma-7b-it\"\n", + "# EXECUTOR_MODEL = \"llama3-8b-8192\"\n", + "# EXECUTOR_MODEL = \"llama3-70b-8192\"\n", + "# EXECUTOR_MODEL = \"mixtral-8x7b-32768\"\n", + "# EXECUTOR_MODEL = \"anthropic/claude-3-haiku:beta\"\n", + "# EXECUTOR_MODEL = \"meta-llama/llama-3-8b-instruct\"\n", + "EXECUTOR_MODEL = \"google/gemma-2-9b-it\"\n", + "# EXECUTOR_MODEL = \"anthropic/claude-3.5-sonnet:beta\"\n", + "\n", + "executor_llm = ChatOpenAI(model_name=EXECUTOR_MODEL, temperature=0.01)\n", + "\n", + "class AgentState(BaseModel):\n", + " # messages: Annotated[Sequence[BaseMessage], operator.add] = []\n", + " acceptance_criteria: str = \"Exactly text match.\"\n", + " user_message: str = \"\"\n", + " expected_output: str = \"\"\n", + " system_message: str = \"\"\n", + " output: str = \"\"\n", + " suggestions: str = \"\"\n", + " accepted: bool = False\n", + " analysis: str = \"\"\n", + " best_output: str = \"\"\n", + " best_system_message: str = \"\"\n", + " best_output_age: int = 0\n", + " max_output_age: int = 0\n", + "\n", + "def prompt_developer(state: AgentState) -> AgentState:\n", + " # llm = ChatOpenAI(temperature=0.1)\n", + " \n", + " if not state.system_message:\n", + " # Initial system message creation\n", + " initial_prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", \"\"\"# Expert Prompt Engineer\n", + "\n", + "You are an expert prompt engineer tasked with creating system messages for AI\n", + "assistants.\n", + "\n", + "## Instructions\n", + "\n", + "1. Create a system message based on the given user message and expected output.\n", + "2. Ensure the system message can handle similar user messages.\n", + "3. Output only the system message, without any additional content.\n", + "4. Expected Output text should not appear in System Message as an example. But\n", + " it's OK to use some similar text as an example instead.\n", + "5. Format the system message well, with no more than 80 characters per line\n", + " (except for raw text).\n", + "\n", + "## Output\n", + "\n", + "Provide only the system message, adhering to the above guidelines.\n", + "\"\"\"),\n", + " (\"human\", \"User message: {user_message}\\nExpected output: {expected_output}\\nCreate a system message that will guide the AI to produce the expected output.\")\n", + " ])\n", + " response = llm(initial_prompt.format_messages(\n", + " user_message=state.user_message, \n", + " expected_output=state.expected_output\n", + " ))\n", + " state.system_message = response.content\n", + " else:\n", + " # Update system message based on analysis\n", + " update_prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", \"\"\"# Expert Prompt Engineer\n", + "\n", + "You are an expert prompt engineer tasked with updating system messages for AI\n", + "assistants. You Update System Message according to Suggestions, to improve\n", + "Output and match Expected Output more closely.\n", + "\n", + "## Instructions\n", + "\n", + "1. Update the system message based on the given Suggestion, User Message, and\n", + " Expected Output.\n", + "2. Ensure the updated system message can handle similar user messages.\n", + "3. Modify only the content mentioned in the Suggestion. Do not change the\n", + " parts that are not related to the Suggestion.\n", + "4. Output only the updated system message, without any additional content.\n", + "5. Expected Output text should not appear in System Message as an example. But\n", + " it's OK to use some similar text as an example instead.\n", + " * Remove the Expected Output text or text highly similar to Expected Output\n", + " from System Message, if it's present.\n", + "6. Format the system message well, with no more than 80 characters per line\n", + " (except for raw text).\n", + "\n", + "## Output\n", + "\n", + "Provide only the updated System Message, adhering to the above guidelines.\n", + "\"\"\"),\n", + " (\"human\", \"\"\"Current system message: {system_message}\n", + "# User Message\n", + "\n", + "{user_message}\n", + "\n", + "# Expected Output\n", + "\n", + "{expected_output}\n", + "\n", + "# Suggestions\n", + "\n", + "{suggestions}\n", + "\"\"\")\n", + " ])\n", + " response = llm(update_prompt.format_messages(**state.dict()))\n", + " state.system_message = response.content\n", + " print(state.system_message)\n", + "\n", + " # state.messages.append(SystemMessage(content=state.system_message))\n", + " return state\n", + "\n", + "def prompt_executor(state: AgentState) -> AgentState:\n", + " # llm = ChatOpenAI(temperature=0.1)\n", + " messages = [\n", + " SystemMessage(content=state.system_message),\n", + " HumanMessage(content=state.user_message)\n", + " ]\n", + " response = executor_llm(messages)\n", + " state.output = response.content\n", + " # state.messages.append(HumanMessage(content=state.user_message))\n", + " # state.messages.append(response)\n", + "\n", + " print(response.content)\n", + "\n", + " return state\n", + "\n", + "def prompt_analyzer(state: AgentState) -> AgentState:\n", + " # Updated to compare output and expected output with LLM and format the response\n", + " comparison_prompt_template = \"\"\"\n", + "You are a text comparing program. You compare the following output texts and provide a\n", + "detailed analysis according to `Acceptance Criteria`. Then you decide whether `Actual Output`\n", + "is acceptable.\n", + "\n", + "Provide your analysis in the following format:\n", + "\n", + "```\n", + "- Acceptable Differences: [List acceptable differences succinctly]\n", + "- Unacceptable Differences: [List unacceptable differences succinctly]\n", + "- Accept: [Yes/No]\n", + "```\n", + "\n", + "* Compare Expected Output and Actual Output with the guidance of Accept Criteria.\n", + "* Only set 'Accept' to 'Yes', if Accept Criteria are all met. Otherwise, set 'Accept' to 'No'.\n", + "* List only the acceptable differences according to Accept Criteria in 'acceptable Differences' section.\n", + "* List only the unacceptable differences according to Accept Criteria in 'Unacceptable Differences' section.\n", + "\n", + "# Acceptance Criteria\n", + "\n", + "```\n", + "{acceptance_criteria}\n", + "```\n", + "\"\"\"\n", + " human_prompt_template = \"\"\"\n", + "# Expected Output\n", + "\n", + "```\n", + "{expected_output}\n", + "```\n", + "\n", + "# Actual Output\n", + "\n", + "```\n", + "{output}\n", + "```\n", + "\"\"\"\n", + "\n", + " comparison_prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", comparison_prompt_template),\n", + " (\"human\", human_prompt_template)\n", + " ])\n", + " \n", + " # Format the prompt with the current state\n", + " formatted_prompt = comparison_prompt.format_messages(**state.dict())\n", + " \n", + " # Send the prompt to the LLM\n", + " response = llm(formatted_prompt)\n", + " state.analysis = response.content\n", + "\n", + " print(response.content)\n", + " \n", + " try:\n", + " # Parse the LLM response to update the state\n", + " analysis_result = parse_llm_response(response.content)\n", + " \n", + " # Update state.matched based on the LLM's analysis\n", + " state.accepted = analysis_result['Accept'].lower() == 'yes'\n", + " except KeyError:\n", + " # If the LLM response is not in the expected format, set matched to False\n", + " state.accepted = False\n", + " \n", + " return state\n", + "\n", + "def parse_llm_response(response: str) -> dict:\n", + " \"\"\"\n", + " Parses the LLM response to handle both single-line and multi-line formats for Differences and Suggestions.\n", + " \"\"\"\n", + " lines = response.split('\\n')\n", + " result = {}\n", + "\n", + " # Process each line\n", + " for line in lines:\n", + " # skip the spaces before `- `\n", + " line = line.strip()\n", + " if line.startswith('- Accept:'):\n", + " result['Accept'] = line.split(': ')[1].strip().strip('[]')\n", + " break\n", + "\n", + " return result\n", + "\n", + "def output_history_analyzer(state: AgentState) -> AgentState:\n", + " system_message_template = \"\"\"You are a text comparing program. You read the Acceptance Criteria, compare the\n", + "compare the exptected output with two different outputs, and decide which one is\n", + "more similar to the expected output.\n", + "\n", + "You output the following analysis according to the Acceptance Criteria:\n", + "\n", + "* Your analysis in a Markdown list.\n", + "* The ID of the output that is more similar to the Expected Output as Preferred\n", + " Output ID, with the following format:\n", + " \n", + "```\n", + "# Analysis\n", + "\n", + "...\n", + "\n", + "# Preferred Output ID: [ID]\n", + "```\n", + "\n", + "If both outputs are equally similar to the expected output, output the following:\n", + "\n", + "```\n", + "# Analysis\n", + "\n", + "...\n", + "\n", + "# Draw\n", + "```\n", + "\"\"\"\n", + " human_message_templates = [\n", + " \"\"\"\n", + "# Output ID: A\n", + "\n", + "```\n", + "{best_output}\n", + "```\n", + "\n", + "# Output ID: B\n", + "\n", + "```\n", + "{output}\n", + "```\n", + "\n", + "# Acceptance Criteria\n", + "\n", + "{acceptance_criteria}\n", + "\n", + "# Expected Output\n", + "\n", + "```\n", + "{expected_output}\n", + "```\n", + "\"\"\",\n", + " \"\"\"\n", + "# Output ID: B\n", + "\n", + "```\n", + "{output}\n", + "```\n", + "\n", + "# Output ID: A\n", + "\n", + "```\n", + "{best_output}\n", + "```\n", + "\n", + "# Acceptance Criteria\n", + "\n", + "{acceptance_criteria}\n", + " \n", + "# Expected Output\n", + "\n", + "```\n", + "{expected_output}\n", + "```\n", + "\"\"\"\n", + " ]\n", + "\n", + " # pick a random human message template\n", + " output_comparison_prompt_template = ChatPromptTemplate.from_messages([\n", + " (\"system\", system_message_template),\n", + " (\"human\", human_message_templates[random.randint(0, 1)])\n", + " ])\n", + "\n", + " if (state.best_output is None or state.best_output == \"\") and \\\n", + " (state.best_system_message is None or state.best_system_message == \"\"):\n", + " state.best_output = state.output\n", + " state.best_system_message = state.system_message\n", + " state.best_output_age = 0\n", + "\n", + " return state\n", + "\n", + " response = llm(output_comparison_prompt_template.format_messages(**state.dict()))\n", + "\n", + " print(response.content)\n", + "\n", + " result = parse_output_history_analyzer(response.content, 'A')\n", + "\n", + " if result == 'B':\n", + " state.best_output = state.output\n", + " state.best_system_message = state.system_message\n", + " state.best_output_age = 0\n", + " else:\n", + " state.best_output_age += 1\n", + " state.output = state.best_output\n", + " state.system_message = state.best_system_message\n", + "\n", + " print(\"Best Output Age: \", state.best_output_age)\n", + "\n", + " return state\n", + "\n", + "def parse_output_history_analyzer(response: str, default_result = None) -> dict:\n", + " \"\"\"\n", + " Parses the LLM response to handle both single-line and multi-line formats for Differences and Suggestions.\n", + " \"\"\"\n", + " lines = response.split('\\n')\n", + " result = default_result\n", + "\n", + " # Process each line\n", + " for line in lines:\n", + " # skip the spaces before `- `\n", + " line = line.strip()\n", + " if line.startswith('# Preferred Output ID:'):\n", + " result = line.split(': ')[1].strip().strip('[]')\n", + " break\n", + " elif line.startswith('# Draw'): \n", + " result = default_result\n", + " break\n", + "\n", + " print(\"Result: \", result)\n", + "\n", + " return result\n", + "\n", + "def prompt_suggester(state: AgentState) -> AgentState:\n", + " # Updated to compare output and expected output with LLM and format the response\n", + " suggester_prompt_template = \"\"\"\n", + "Read the following inputs and outputs of an LLM prompt, and also analysis about them.\n", + "Then suggest how to improve System Prompt.\n", + "\n", + "* The goal is to improve the System Prompt to match the Expected Output better.\n", + "* Ignore all Acceptable Differences and focus on Unacceptable Differences.\n", + "* Suggest formal changes first, then semantic changes.\n", + "* Provide your suggestions in a Markdown list, nothing else. Output only the\n", + " suggestions related with Unacceptable Differences.\n", + " * Use `... should ...` to clearly state the desired output.\n", + " * Figue out the contexts of the System Message that conflict with the suggestions,\n", + " and suggest modification or deletion.\n", + "* Expected Output text should not appear in System Message as an example. But\n", + " it's OK to use some similar text as an example instead.\n", + " * Ask to remove the Expected Output text or text highly similar to Expected Output\n", + " from System Message, if it's present.\n", + "* Provide format examples or detected format name, if System Message does not.\n", + " * Specify the detected format name (e.g. XML, JSON, etc.) of Expected Output, if\n", + " System Message does not mention it.\n", + "\"\"\"\n", + " human_prompt_template = \"\"\"\n", + "System Prompt:\n", + "```\n", + "{system_message}\n", + "```\n", + "User Message:\n", + "```\n", + "{user_message}\n", + "```\n", + "Expected Output: \n", + "```\n", + "{expected_output}\n", + "```\n", + "Actual Output: \n", + "```\n", + "{output}\n", + "```\n", + "\n", + "Acceptance Criteria:\n", + "```\n", + "{acceptance_criteria}\n", + "```\n", + "\n", + "Analysis:\n", + "```\n", + "{analysis}\n", + "```\n", + "\"\"\"\n", + "\n", + " suggester_prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", suggester_prompt_template),\n", + " (\"human\", human_prompt_template)\n", + " ])\n", + " \n", + " # Format the prompt with the current state\n", + " formatted_prompt = suggester_prompt.format_messages(**state.dict())\n", + " \n", + " # Send the prompt to the LLM\n", + " response = llm(formatted_prompt)\n", + " state.suggestions = response.content\n", + "\n", + " print(response.content)\n", + " \n", + " return state\n", + "\n", + "def should_exit_on_max_age(state: AgentState) -> str:\n", + " if state.max_output_age <=0:\n", + " # always continue if max age is 0\n", + " return \"continue\"\n", + " \n", + " if state.best_output_age >= state.max_output_age:\n", + " return END\n", + " \n", + " if state.best_output_age > 0:\n", + " # skip prompt_analyzer and prompt_suggester, goto prompt_developer\n", + " return \"rerun\" \n", + " \n", + " return \"continue\"\n", + "\n", + "def should_exit_on_acceptable_output(state: AgentState) -> str:\n", + " if state.accepted:\n", + " return END\n", + " else:\n", + " return \"continue\"\n", + "\n", + "\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "workflow.add_node(\"prompt_developer\", prompt_developer)\n", + "workflow.add_node(\"prompt_executor\", prompt_executor)\n", + "workflow.add_node(\"output_history_analyzer\", output_history_analyzer)\n", + "workflow.add_node(\"prompt_analyzer\", prompt_analyzer)\n", + "workflow.add_node(\"prompt_suggester\", prompt_suggester)\n", + "\n", + "workflow.set_entry_point(\"prompt_developer\")\n", + "\n", + "workflow.add_edge(\"prompt_developer\", \"prompt_executor\")\n", + "workflow.add_edge(\"prompt_executor\", \"output_history_analyzer\")\n", + "\n", + "workflow.add_conditional_edges(\n", + " \"output_history_analyzer\",\n", + " should_exit_on_max_age,\n", + " {\n", + " \"continue\": \"prompt_analyzer\",\n", + " \"rerun\": \"prompt_suggester\",\n", + " END: END\n", + " }\n", + ")\n", + "\n", + "workflow.add_conditional_edges(\n", + " \"prompt_analyzer\",\n", + " should_exit_on_acceptable_output,\n", + " {\n", + " \"continue\": \"prompt_suggester\",\n", + " END: END\n", + " }\n", + ")\n", + "\n", + "workflow.add_edge(\"prompt_suggester\", \"prompt_developer\")\n", + "\n", + "memory = MemorySaver()\n", + "graph = workflow.compile(checkpointer=memory)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(graph.get_graph().draw_mermaid_png()))\n", + "except Exception:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User Message:\n", + " \n", + "今天下午3点,在北京国家会议中心,阿里巴巴集团董事局主席马云宣布将投资100亿元人民币用于农村电商发展。这一决定受到了与会代表的热烈欢迎,大家认为这将为中国农村经济带来新的机遇。\n", + "\n", + "Expected Output:\n", + " \n", + "{\n", + " \"文本分析结果\": {\n", + " \"情感分析\": {\n", + " \"整体情感\": \"积极\",\n", + " \"情感得分\": 0.82,\n", + " \"情感细分\": {\n", + " \"乐观\": 0.75,\n", + " \"兴奋\": 0.60,\n", + " \"期待\": 0.85\n", + " }\n", + " },\n", + " \"实体识别\": [\n", + " {\"实体\": \"北京\", \"类型\": \"地点\", \"起始位置\": 7, \"结束位置\": 9},\n", + " {\"实体\": \"国家会议中心\", \"类型\": \"地点\", \"起始位置\": 9, \"结束位置\": 15},\n", + " {\"实体\": \"阿里巴巴集团\", \"类型\": \"组织\", \"起始位置\": 16, \"结束位置\": 22},\n", + " {\"实体\": \"马云\", \"类型\": \"人物\", \"起始位置\": 26, \"结束位置\": 28},\n", + " {\"实体\": \"100亿元\", \"类型\": \"金额\", \"起始位置\": 32, \"结束位置\": 37},\n", + " {\"实体\": \"人民币\", \"类型\": \"货币\", \"起始位置\": 37, \"结束位置\": 40},\n", + " {\"实体\": \"中国\", \"类型\": \"地点\", \"起始位置\": 71, \"结束位置\": 73}\n", + " ],\n", + " \"关键词提取\": [\n", + " {\"关键词\": \"农村电商\", \"权重\": 0.95},\n", + " {\"关键词\": \"马云\", \"权重\": 0.85},\n", + " {\"关键词\": \"投资\", \"权重\": 0.80},\n", + " {\"关键词\": \"阿里巴巴\", \"权重\": 0.75},\n", + " {\"关键词\": \"经济机遇\", \"权重\": 0.70}\n", + " ]\n", + " }\n", + "}\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/yale/work/meta-prompt/.venv/lib/python3.10/site-packages/langchain_core/_api/deprecation.py:139: LangChainDeprecationWarning: The method `BaseChatModel.__call__` was deprecated in langchain-core 0.1.7 and will be removed in 0.3.0. Use invoke instead.\n", + " warn_deprecated(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "```\n", + "You are a text analysis AI. Given a piece of text in Chinese, analyze it and return the following information in JSON format:\n", + "\n", + "* **文本分析结果:**\n", + " * **情感分析:**\n", + " * **整体情感:** (e.g., 积极, 消极, 中性)\n", + " * **情感得分:** (a number between 0 and 1)\n", + " * **情感细分:** (a dictionary of emotions and their scores)\n", + " * **实体识别:** A list of dictionaries, each containing:\n", + " * **实体:** (e.g., 人名, 地名, 组织名)\n", + " * **类型:** (e.g., 人物, 地点, 组织)\n", + " * **起始位置:** (the starting index of the entity in the text)\n", + " * **结束位置:** (the ending index of the entity in the text)\n", + " * **关键词提取:** A list of dictionaries, each containing:\n", + " * **关键词:** (the extracted keyword)\n", + " * **权重:** (the importance score of the keyword) \n", + "\n", + "\n", + "\n", + "```\n", + "```json\n", + "{\n", + " \"文本分析结果\": {\n", + " \"情感分析\": {\n", + " \"整体情感\": \"积极\",\n", + " \"情感得分\": 0.85,\n", + " \"情感细分\": {\n", + " \"高兴\": 0.6,\n", + " \"期待\": 0.25,\n", + " \"赞赏\": 0.1\n", + " }\n", + " },\n", + " \"实体识别\": [\n", + " {\n", + " \"实体\": \"马云\",\n", + " \"类型\": \"人物\",\n", + " \"起始位置\": 29,\n", + " \"结束位置\": 33\n", + " },\n", + " {\n", + " \"实体\": \"阿里巴巴集团\",\n", + " \"类型\": \"组织\",\n", + " \"起始位置\": 16,\n", + " \"结束位置\": 27\n", + " },\n", + " {\n", + " \"实体\": \"北京国家会议中心\",\n", + " \"类型\": \"地点\",\n", + " \"起始位置\": 7,\n", + " \"结束位置\": 21\n", + " },\n", + " {\n", + " \"实体\": \"中国\",\n", + " \"类型\": \"国家\",\n", + " \"起始位置\": 60,\n", + " \"结束位置\": 63\n", + " }\n", + " ],\n", + " \"关键词提取\": [\n", + " {\n", + " \"关键词\": \"投资\",\n", + " \"权重\": 0.25\n", + " },\n", + " {\n", + " \"关键词\": \"农村电商\",\n", + " \"权重\": 0.2\n", + " },\n", + " {\n", + " \"关键词\": \"马云\",\n", + " \"权重\": 0.18\n", + " },\n", + " {\n", + " \"关键词\": \"阿里巴巴\",\n", + " \"权重\": 0.15\n", + " },\n", + " {\n", + " \"关键词\": \"北京国家会议中心\",\n", + " \"权重\": 0.12\n", + " }\n", + " ]\n", + " }\n", + "}\n", + "``` \n", + "\n", + "**Explanation:**\n", + "\n", + "* **情感分析:** The text expresses a positive sentiment overall, with a score of 0.85. The emotions detected are \"高兴\" (happy), \"期待\" (expectation), and \"赞赏\" (appreciation).\n", + "* **实体识别:** The entities identified are:\n", + " * **马云 (Jack Ma):** A person, the chairman of Alibaba Group.\n", + " * **阿里巴巴集团 (Alibaba Group):** An organization, the company making the investment.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** A location, where the announcement was made.\n", + " * **中国 (China):** A country, the beneficiary of the investment.\n", + "* **关键词提取:** The keywords extracted are:\n", + " * **投资 (investment):** The core action of the announcement.\n", + " * **农村电商 (rural e-commerce):** The focus of the investment.\n", + " * **马云 (Jack Ma):** The key person making the announcement.\n", + " * **阿里巴巴 (Alibaba):** The company behind the investment.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** The location of the announcement, adding context.\n", + "\n", + "\n", + "\n", + "Let me know if you have any other text you'd like me to analyze!\n", + "```\n", + "- Acceptable Differences: \n", + " * Differences in digital values in the table.\n", + " * Differences in JSON field values\n", + " * Differences in section/item orders.\n", + "- Unacceptable Differences: \n", + " * \"情感细分\" field values are different.\n", + " * \"实体识别\" field values are different.\n", + " * \"关键词提取\" field values are different.\n", + "- Accept: No \n", + "``` \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "- The System Prompt should remove the example text. \n", + "- The System Prompt should specify the expected format of the output as JSON. \n", + "- The System Prompt should include a requirement for a \"国家\" (country) entity type. \n", + "\n", + "\n", + "\n", + "```\n", + "You are a text analysis AI. Given a piece of text in Chinese, analyze it and return the following information in JSON format:\n", + "\n", + "* **文本分析结果:**\n", + " * **情感分析:**\n", + " * **整体情感:** (e.g., 积极, 消极, 中性)\n", + " * **情感得分:** (a number between 0 and 1)\n", + " * **情感细分:** (a dictionary of emotions and their scores)\n", + " * **实体识别:** A list of dictionaries, each containing:\n", + " * **实体:** (e.g., 人名, 地名, 组织名)\n", + " * **类型:** (e.g., 人物, 地点, 组织, 国家)\n", + " * **起始位置:** (the starting index of the entity in the text)\n", + " * **结束位置:** (the ending index of the entity in the text)\n", + " * **关键词提取:** A list of dictionaries, each containing:\n", + " * **关键词:** (the extracted keyword)\n", + " * **权重:** (the importance score of the keyword) \n", + "```\n", + "```json\n", + "{\n", + " \"文本分析结果\": {\n", + " \"情感分析\": {\n", + " \"整体情感\": \"积极\",\n", + " \"情感得分\": 0.85,\n", + " \"情感细分\": {\n", + " \"高兴\": 0.6,\n", + " \"期待\": 0.25,\n", + " \"赞赏\": 0.1\n", + " }\n", + " },\n", + " \"实体识别\": [\n", + " {\n", + " \"实体\": \"马云\",\n", + " \"类型\": \"人物\",\n", + " \"起始位置\": 29,\n", + " \"结束位置\": 33\n", + " },\n", + " {\n", + " \"实体\": \"阿里巴巴集团\",\n", + " \"类型\": \"组织\",\n", + " \"起始位置\": 16,\n", + " \"结束位置\": 27\n", + " },\n", + " {\n", + " \"实体\": \"北京国家会议中心\",\n", + " \"类型\": \"地点\",\n", + " \"起始位置\": 7,\n", + " \"结束位置\": 21\n", + " },\n", + " {\n", + " \"实体\": \"中国\",\n", + " \"类型\": \"国家\",\n", + " \"起始位置\": 60,\n", + " \"结束位置\": 63\n", + " }\n", + " ],\n", + " \"关键词提取\": [\n", + " {\n", + " \"关键词\": \"投资\",\n", + " \"权重\": 0.2\n", + " },\n", + " {\n", + " \"关键词\": \"农村电商\",\n", + " \"权重\": 0.18\n", + " },\n", + " {\n", + " \"关键词\": \"马云\",\n", + " \"权重\": 0.15\n", + " },\n", + " {\n", + " \"关键词\": \"阿里巴巴\",\n", + " \"权重\": 0.12\n", + " },\n", + " {\n", + " \"关键词\": \"机遇\",\n", + " \"权重\": 0.1\n", + " }\n", + " ]\n", + " }\n", + "}\n", + "``` \n", + "\n", + "\n", + "**Explanation:**\n", + "\n", + "* **情感分析:** The text expresses a positive sentiment overall, with a score of 0.85. The emotions detected are \"高兴\" (happy), \"期待\" (expectation), and \"赞赏\" (appreciation).\n", + "* **实体识别:** The entities identified are:\n", + " * **马云 (Jack Ma):** A person, the chairman of Alibaba Group.\n", + " * **阿里巴巴集团 (Alibaba Group):** An organization, a multinational technology company.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** A location, a convention center in Beijing.\n", + " * **中国 (China):** A country.\n", + "* **关键词提取:** The keywords extracted are:\n", + " * **投资 (investment):** Reflects the main action in the text.\n", + " * **农村电商 (rural e-commerce):** The focus of the investment.\n", + " * **马云 (Jack Ma):** The person making the announcement.\n", + " * **阿里巴巴 (Alibaba):** The company making the investment.\n", + " * **机遇 (opportunity):** The positive outcome expected from the investment.\n", + "\n", + "\n", + "\n", + "Let me know if you have any other text you'd like me to analyze!\n", + "\n", + "\n", + "# Analysis\n", + "\n", + "* Both outputs provide similar JSON structures with consistent sections: \"文本分析结果\", \"情感分析\", \"实体识别\", and \"关键词提取\".\n", + "* The \"情感分析\" section in both outputs shows a positive sentiment with a score around 0.85.\n", + "* The \"实体识别\" sections identify similar entities, including \"马云\", \"阿里巴巴集团\", \"北京国家会议中心\", and \"中国\".\n", + "* The \"关键词提取\" sections also show overlapping keywords like \"投资\", \"农村电商\", \"马云\", and \"阿里巴巴\".\n", + "\n", + "However, there are some notable differences:\n", + "\n", + "* Output A includes \"北京国家会议中心\" as a keyword, while Output B does not.\n", + "* Output B assigns slightly different weights to some keywords compared to Output A.\n", + "* Output A's \"情感分析\" section includes \"乐观\" and \"兴奋\" as emotions, while Output B uses \"高兴\" and \"期待\".\n", + "\n", + "* Output A's \"实体识别\" section includes \"北京\", \"国家会议中心\", \"100亿元\", and \"人民币\", which are not present in Output B.\n", + "\n", + "# Preferred Output ID: A \n", + "\n", + "\n", + "\n", + "Result: A\n", + "Best Output Age: 1\n", + "\n", + "\n", + "- The System Prompt should remove the example text of the expected output. \n", + "- The System Prompt should specify that the \"实体识别\" field should include \"金额\" and \"货币\" as entity types. \n", + "- The System Prompt should specify that the \"关键词提取\" field should include keywords related to the context of the text. \n", + "\n", + "\n", + "\n", + "\n", + "```\n", + "You are a text analysis AI. Given a piece of text in Chinese, analyze it and return the following information in JSON format:\n", + "\n", + "* **文本分析结果:**\n", + " * **情感分析:**\n", + " * **整体情感:** (e.g., 积极, 消极, 中性)\n", + " * **情感得分:** (a number between 0 and 1)\n", + " * **情感细分:** (a dictionary of emotions and their scores)\n", + " * **实体识别:** A list of dictionaries, each containing:\n", + " * **实体:** (e.g., 人名, 地名, 组织名)\n", + " * **类型:** (e.g., 人物, 地点, 组织, 金额, 货币)\n", + " * **起始位置:** (the starting index of the entity in the text)\n", + " * **结束位置:** (the ending index of the entity in the text)\n", + " * **关键词提取:** A list of dictionaries, each containing:\n", + " * **关键词:** (the extracted keyword)\n", + " * **权重:** (the importance score of the keyword) \n", + "\n", + "\n", + "\n", + "```\n", + "```json\n", + "{\n", + " \"文本分析结果\": {\n", + " \"情感分析\": {\n", + " \"整体情感\": \"积极\",\n", + " \"情感得分\": 0.85,\n", + " \"情感细分\": {\n", + " \"高兴\": 0.6,\n", + " \"期待\": 0.25,\n", + " \"赞赏\": 0.1\n", + " }\n", + " },\n", + " \"实体识别\": [\n", + " {\n", + " \"实体\": \"马云\",\n", + " \"类型\": \"人物\",\n", + " \"起始位置\": 29,\n", + " \"结束位置\": 33\n", + " },\n", + " {\n", + " \"实体\": \"阿里巴巴集团\",\n", + " \"类型\": \"组织\",\n", + " \"起始位置\": 16,\n", + " \"结束位置\": 27\n", + " },\n", + " {\n", + " \"实体\": \"北京国家会议中心\",\n", + " \"类型\": \"地点\",\n", + " \"起始位置\": 7,\n", + " \"结束位置\": 21\n", + " },\n", + " {\n", + " \"实体\": \"100亿元人民币\",\n", + " \"类型\": \"金额\",\n", + " \"起始位置\": 38,\n", + " \"结束位置\": 51\n", + " },\n", + " {\n", + " \"实体\": \"中国农村经济\",\n", + " \"类型\": \"经济\",\n", + " \"起始位置\": 70,\n", + " \"结束位��\": 83\n", + " }\n", + " ],\n", + " \"关键词提取\": [\n", + " {\n", + " \"关键词\": \"马云\",\n", + " \"权重\": 0.25\n", + " },\n", + " {\n", + " \"关键词\": \"阿里巴巴\",\n", + " \"权重\": 0.18\n", + " },\n", + " {\n", + " \"关键词\": \"投资\",\n", + " \"权重\": 0.15\n", + " },\n", + " {\n", + " \"关键词\": \"农村电商\",\n", + " \"权重\": 0.12\n", + " },\n", + " {\n", + " \"关键词\": \"机遇\",\n", + " \"权重\": 0.1\n", + " }\n", + " ]\n", + " }\n", + "}\n", + "``` \n", + "\n", + "**Explanation:**\n", + "\n", + "* **情感分析:** The text expresses a positive sentiment overall, with a score of 0.85. The emotions detected are \"高兴\" (happy), \"期待\" (expectation), and \"赞赏\" (appreciation).\n", + "* **实体识别:** The entities identified are:\n", + " * **马云 (Jack Ma):** A person, the chairman of Alibaba Group.\n", + " * **阿里巴巴集团 (Alibaba Group):** An organization.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** A location.\n", + " * **100亿元人民币 (10 billion yuan):** An amount of money.\n", + " * **中国农村经济 (Chinese rural economy):** An economic concept.\n", + "* **关键词提取:** The keywords extracted are:\n", + " * **马云 (Jack Ma):** The main subject of the announcement.\n", + " * **阿里巴巴 (Alibaba):** The company making the investment.\n", + " * **投资 (investment):** The core action being taken.\n", + " * **农村电商 (rural e-commerce):** The area of focus for the investment.\n", + " * **机遇 (opportunity):** The positive outcome expected from the investment.\n", + "\n", + "\n", + "\n", + "Let me know if you have any other text you'd like me to analyze!\n", + "\n", + "\n", + "# Analysis\n", + "\n", + "* Both outputs provide similar JSON structures with consistent top-level sections: \"文本分析结果\", \"情感分析\", \"实体识别\", and \"关键词提取\".\n", + "* The \"情感分析\" section in both outputs shows a positive sentiment with a score close to 0.85. \n", + "* There are differences in the specific emotions detected and the scores assigned to them.\n", + "* The \"实体识别\" sections identify some overlapping entities but also have differences in the detected entities and their classifications.\n", + "* The \"关键词提取\" sections show variations in the extracted keywords and their assigned weights.\n", + "\n", + "Considering the acceptable differences outlined in the Acceptance Criteria, both outputs demonstrate a reasonable level of similarity to the Expected Output. \n", + "\n", + "# Draw \n", + "\n", + "\n", + "\n", + "Result: A\n", + "Best Output Age: 2\n", + "\n", + "\n", + "- The System Prompt should remove the example text within the `文本分析结果` section. \n", + "- The System Prompt should specify that the `实体识别` section should include all named entities in the text, not just a subset. \n", + "- The System Prompt should specify that the `关键词提取` section should include the most relevant keywords, not just a few. \n", + "\n", + "\n", + "\n", + "```\n", + "You are a text analysis AI. Given a piece of text in Chinese, analyze it and return the following information in JSON format:\n", + "\n", + "* **文本分析结果:**\n", + " * **情感分析:**\n", + " * **整体情感:** (e.g., 积极, 消极, 中性)\n", + " * **情感得分:** (a number between 0 and 1)\n", + " * **情感细分:** (a dictionary of emotions and their scores)\n", + " * **实体识别:** A list of dictionaries, each containing:\n", + " * **实体:** (e.g., 人名, 地名, 组织名)\n", + " * **类型:** (e.g., 人物, 地点, 组织)\n", + " * **起始位置:** (the starting index of the entity in the text)\n", + " * **结束位置:** (the ending index of the entity in the text)\n", + " * **关键词提取:** A list of dictionaries, each containing:\n", + " * **关键词:** (the extracted keyword)\n", + " * **权重:** (the importance score of the keyword) \n", + "\n", + "\n", + "\n", + "```\n", + "```json\n", + "{\n", + " \"文本分析结果\": {\n", + " \"情感分析\": {\n", + " \"整体情感\": \"积极\",\n", + " \"情感得分\": 0.85,\n", + " \"情感细分\": {\n", + " \"高兴\": 0.6,\n", + " \"期待\": 0.25,\n", + " \"赞赏\": 0.1\n", + " }\n", + " },\n", + " \"实体识别\": [\n", + " {\n", + " \"实体\": \"马云\",\n", + " \"类型\": \"人物\",\n", + " \"起始位置\": 29,\n", + " \"结束位置\": 33\n", + " },\n", + " {\n", + " \"实体\": \"阿里巴巴集团\",\n", + " \"类型\": \"组织\",\n", + " \"起始位置\": 16,\n", + " \"结束位置\": 27\n", + " },\n", + " {\n", + " \"实体\": \"北京国家会议中心\",\n", + " \"类型\": \"地点\",\n", + " \"起始位置\": 7,\n", + " \"结束位置\": 21\n", + " },\n", + " {\n", + " \"实体\": \"中国\",\n", + " \"类型\": \"国家\",\n", + " \"起始位置\": 60,\n", + " \"结束位置\": 63\n", + " }\n", + " ],\n", + " \"关键词提取\": [\n", + " {\n", + " \"关键词\": \"投资\",\n", + " \"权重\": 0.25\n", + " },\n", + " {\n", + " \"关键词\": \"农村电商\",\n", + " \"权重\": 0.2\n", + " },\n", + " {\n", + " \"关键词\": \"马云\",\n", + " \"权重\": 0.18\n", + " },\n", + " {\n", + " \"关键词\": \"阿里巴巴\",\n", + " \"权重\": 0.15\n", + " },\n", + " {\n", + " \"关键词\": \"北京国家会议中心\",\n", + " \"权重\": 0.12\n", + " }\n", + " ]\n", + " }\n", + "}\n", + "``` \n", + "\n", + "**Explanation:**\n", + "\n", + "* **情感分析:** The text expresses a positive sentiment overall, with a score of 0.85. The emotions detected are \"高兴\" (happy), \"期待\" (expectation), and \"赞赏\" (appreciation).\n", + "* **实体识别:** The entities identified are:\n", + " * **马云 (Jack Ma):** A person, the chairman of Alibaba Group.\n", + " * **阿里巴巴集团 (Alibaba Group):** An organization, the company making the investment.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** A location, where the announcement was made.\n", + " * **中国 (China):** A country, the beneficiary of the investment.\n", + "* **关键词提取:** The keywords extracted are:\n", + " * **投资 (investment):** The core action of the announcement.\n", + " * **农村电商 (rural e-commerce):** The target area of the investment.\n", + " * **马云 (Jack Ma):** The key person making the announcement.\n", + " * **阿里巴巴 (Alibaba):** The company behind the investment.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** The location of the announcement, adding context.\n", + "\n", + "\n", + "\n", + "Let me know if you have any other text you'd like me to analyze!\n", + "\n", + "\n", + "# Analysis\n", + "\n", + "* Both outputs have the same top-level sections: \"文本分析结果\", \"情感分析\", \"实体识别\", and \"关键词提取\".\n", + "* Both outputs have consistent data types for all JSON fields.\n", + "* Both outputs have similar structures within each section. \n", + "* There are differences in the specific values for \"情感得分\", \"情感细分\", \"实体识别\" entities, and \"关键词提取\" keywords.\n", + "\n", + "# Preferred Output ID: A \n", + "\n", + "\n", + "While both outputs are structured similarly and adhere to the Acceptance Criteria, Output A is preferred because it closely mirrors the expected output's structure and field names. \n", + "\n", + "\n", + "\n", + "Result: A\n", + "Best Output Age: 3\n", + "Final Result: {'acceptance_criteria': '\\n* Consistent with Expected Output:\\n * Formats of all JSON sections\\n * Data types of all JSON fields\\n * Top layer sections\\n* Acceptable differences:\\n * Differences in digital values in the table.\\n * Extra or missing spaces.\\n * Extra or missing line breaks at the beginning or end of the output.\\n * Differences in JSON field values\\n * Differences in section/item orders.\\n * JSON wrapped in backquotes.\\n', 'user_message': '\\n今天下午3点,在北京国家会议中心,阿里巴巴集团董事局主席马云宣布将投资100亿元人民币用于农村电商发展。这一决定受到了与会代表的热烈欢迎,大家认为这将为中国农村经济带来新的机遇。\\n', 'expected_output': '\\n{\\n \"文本分析结果\": {\\n \"情感分析\": {\\n \"整体情感\": \"积极\",\\n \"情感得分\": 0.82,\\n \"情感细分\": {\\n \"乐观\": 0.75,\\n \"兴奋\": 0.60,\\n \"期待\": 0.85\\n }\\n },\\n \"实体识别\": [\\n {\"实体\": \"北京\", \"类型\": \"地点\", \"起始位置\": 7, \"结束位置\": 9},\\n {\"实体\": \"国家会议中心\", \"类型\": \"地点\", \"起始位置\": 9, \"结束位置\": 15},\\n {\"实体\": \"阿里巴巴集团\", \"类型\": \"组织\", \"起始位置\": 16, \"结束位置\": 22},\\n {\"实体\": \"马云\", \"类型\": \"人物\", \"起始位置\": 26, \"结束位置\": 28},\\n {\"实体\": \"100亿元\", \"类型\": \"金额\", \"起始位置\": 32, \"结束位置\": 37},\\n {\"实体\": \"人民币\", \"类型\": \"货币\", \"起始位置\": 37, \"结束位置\": 40},\\n {\"实体\": \"中国\", \"类型\": \"地点\", \"起始位置\": 71, \"结束位置\": 73}\\n ],\\n \"关键词提取\": [\\n {\"关键词\": \"农村电商\", \"权重\": 0.95},\\n {\"关键词\": \"马云\", \"权重\": 0.85},\\n {\"关键词\": \"投资\", \"权重\": 0.80},\\n {\"关键词\": \"阿里巴巴\", \"权重\": 0.75},\\n {\"关键词\": \"经济机遇\", \"权重\": 0.70}\\n ]\\n }\\n}\\n', 'system_message': '```\\nYou are a text analysis AI. Given a piece of text in Chinese, analyze it and return the following information in JSON format:\\n\\n* **文本分析结果:**\\n * **情感分析:**\\n * **整体情感:** (e.g., 积极, 消极, 中性)\\n * **情感得分:** (a number between 0 and 1)\\n * **情感细分:** (a dictionary of emotions and their scores)\\n * **实体识别:** A list of dictionaries, each containing:\\n * **实体:** (e.g., 人名, 地名, 组织名)\\n * **类型:** (e.g., 人物, 地点, 组织)\\n * **起始位置:** (the starting index of the entity in the text)\\n * **结束位置:** (the ending index of the entity in the text)\\n * **关键词提取:** A list of dictionaries, each containing:\\n * **关键词:** (the extracted keyword)\\n * **权重:** (the importance score of the keyword) \\n\\n\\n\\n```', 'output': '```json\\n{\\n \"文本分析结果\": {\\n \"情感分析\": {\\n \"整体情感\": \"积极\",\\n \"情感得分\": 0.85,\\n \"情感细分\": {\\n \"高兴\": 0.6,\\n \"期待\": 0.25,\\n \"赞赏\": 0.1\\n }\\n },\\n \"实体识别\": [\\n {\\n \"实体\": \"马云\",\\n \"类型\": \"人物\",\\n \"起始位置\": 29,\\n \"结束位置\": 33\\n },\\n {\\n \"实体\": \"阿里巴巴集团\",\\n \"类型\": \"组织\",\\n \"起始位置\": 16,\\n \"结束位置\": 27\\n },\\n {\\n \"实体\": \"北京国家会议中心\",\\n \"类型\": \"地点\",\\n \"起始位置\": 7,\\n \"结束位置\": 21\\n },\\n {\\n \"实体\": \"中国\",\\n \"类型\": \"国家\",\\n \"起始位置\": 60,\\n \"结束位置\": 63\\n }\\n ],\\n \"关键词提取\": [\\n {\\n \"关键词\": \"投资\",\\n \"权重\": 0.25\\n },\\n {\\n \"关键词\": \"农村电商\",\\n \"权重\": 0.2\\n },\\n {\\n \"关键词\": \"马云\",\\n \"权重\": 0.18\\n },\\n {\\n \"关键词\": \"阿里巴巴\",\\n \"权重\": 0.15\\n },\\n {\\n \"关键词\": \"北京国家会议中心\",\\n \"权重\": 0.12\\n }\\n ]\\n }\\n}\\n``` \\n\\n**Explanation:**\\n\\n* **情感分析:** The text expresses a positive sentiment overall, with a score of 0.85. The emotions detected are \"高兴\" (happy), \"期待\" (expectation), and \"赞赏\" (appreciation).\\n* **实体识别:** The entities identified are:\\n * **马云 (Jack Ma):** A person, the chairman of Alibaba Group.\\n * **阿里巴巴集团 (Alibaba Group):** An organization, the company making the investment.\\n * **北京国家会议中心 (Beijing National Convention Center):** A location, where the announcement was made.\\n * **中国 (China):** A country, the beneficiary of the investment.\\n* **关键词提取:** The keywords extracted are:\\n * **投资 (investment):** The core action of the announcement.\\n * **农村电商 (rural e-commerce):** The focus of the investment.\\n * **马云 (Jack Ma):** The key person making the announcement.\\n * **阿里巴巴 (Alibaba):** The company behind the investment.\\n * **北京国家会议中心 (Beijing National Convention Center):** The location of the announcement, adding context.\\n\\n\\n\\nLet me know if you have any other text you\\'d like me to analyze!', 'suggestions': '\\n\\n- The System Prompt should remove the example text within the `文本分析结果` section. \\n- The System Prompt should specify that the `实体识别` section should include all named entities in the text, not just a subset. \\n- The System Prompt should specify that the `关键词提取` section should include the most relevant keywords, not just a few. \\n\\n\\n', 'accepted': False, 'analysis': '```\\n- Acceptable Differences: \\n * Differences in digital values in the table.\\n * Differences in JSON field values\\n * Differences in section/item orders.\\n- Unacceptable Differences: \\n * \"情感细分\" field values are different.\\n * \"实体识别\" field values are different.\\n * \"关键词提取\" field values are different.\\n- Accept: No \\n``` \\n\\n\\n', 'best_output': '```json\\n{\\n \"文本分析结果\": {\\n \"情感分析\": {\\n \"整体情感\": \"积极\",\\n \"情感得分\": 0.85,\\n \"情感细分\": {\\n \"高兴\": 0.6,\\n \"期待\": 0.25,\\n \"赞赏\": 0.1\\n }\\n },\\n \"实体识别\": [\\n {\\n \"实体\": \"马云\",\\n \"类型\": \"人物\",\\n \"起始位置\": 29,\\n \"结束位置\": 33\\n },\\n {\\n \"实体\": \"阿里巴巴集团\",\\n \"类型\": \"组织\",\\n \"起始位置\": 16,\\n \"结束位置\": 27\\n },\\n {\\n \"实体\": \"北京国家会议中心\",\\n \"类型\": \"地点\",\\n \"起始位置\": 7,\\n \"结束位置\": 21\\n },\\n {\\n \"实体\": \"中国\",\\n \"类型\": \"国家\",\\n \"起始位置\": 60,\\n \"结束位置\": 63\\n }\\n ],\\n \"关键词提取\": [\\n {\\n \"关键词\": \"投资\",\\n \"权重\": 0.25\\n },\\n {\\n \"关键词\": \"农村电商\",\\n \"权重\": 0.2\\n },\\n {\\n \"关键词\": \"马云\",\\n \"权重\": 0.18\\n },\\n {\\n \"关键词\": \"阿里巴巴\",\\n \"权重\": 0.15\\n },\\n {\\n \"关键词\": \"北京国家会议中心\",\\n \"权重\": 0.12\\n }\\n ]\\n }\\n}\\n``` \\n\\n**Explanation:**\\n\\n* **情感分析:** The text expresses a positive sentiment overall, with a score of 0.85. The emotions detected are \"高兴\" (happy), \"期待\" (expectation), and \"赞赏\" (appreciation).\\n* **实体识别:** The entities identified are:\\n * **马云 (Jack Ma):** A person, the chairman of Alibaba Group.\\n * **阿里巴巴集团 (Alibaba Group):** An organization, the company making the investment.\\n * **北京国家会议中心 (Beijing National Convention Center):** A location, where the announcement was made.\\n * **中国 (China):** A country, the beneficiary of the investment.\\n* **关键词提取:** The keywords extracted are:\\n * **投资 (investment):** The core action of the announcement.\\n * **农村电商 (rural e-commerce):** The focus of the investment.\\n * **马云 (Jack Ma):** The key person making the announcement.\\n * **阿里巴巴 (Alibaba):** The company behind the investment.\\n * **北京国家会议中心 (Beijing National Convention Center):** The location of the announcement, adding context.\\n\\n\\n\\nLet me know if you have any other text you\\'d like me to analyze!', 'best_system_message': '```\\nYou are a text analysis AI. Given a piece of text in Chinese, analyze it and return the following information in JSON format:\\n\\n* **文本分析结果:**\\n * **情感分析:**\\n * **整体情感:** (e.g., 积极, 消极, 中性)\\n * **情感得分:** (a number between 0 and 1)\\n * **情感细分:** (a dictionary of emotions and their scores)\\n * **实体识别:** A list of dictionaries, each containing:\\n * **实体:** (e.g., 人名, 地名, 组织名)\\n * **类型:** (e.g., 人物, 地点, 组织)\\n * **起始位置:** (the starting index of the entity in the text)\\n * **结束位置:** (the ending index of the entity in the text)\\n * **关键词提取:** A list of dictionaries, each containing:\\n * **关键词:** (the extracted keyword)\\n * **权重:** (the importance score of the keyword) \\n\\n\\n\\n```', 'best_output_age': 3, 'max_output_age': 3}\n", + "System Message:\n", + "```\n", + "You are a text analysis AI. Given a piece of text in Chinese, analyze it and return the following information in JSON format:\n", + "\n", + "* **文本分析结果:**\n", + " * **情感分析:**\n", + " * **整体情感:** (e.g., 积极, 消极, 中性)\n", + " * **情感得分:** (a number between 0 and 1)\n", + " * **情感细分:** (a dictionary of emotions and their scores)\n", + " * **实体识别:** A list of dictionaries, each containing:\n", + " * **实体:** (e.g., 人名, 地名, 组织名)\n", + " * **类型:** (e.g., 人物, 地点, 组织)\n", + " * **起始位置:** (the starting index of the entity in the text)\n", + " * **结束位置:** (the ending index of the entity in the text)\n", + " * **关键词提取:** A list of dictionaries, each containing:\n", + " * **关键词:** (the extracted keyword)\n", + " * **权重:** (the importance score of the keyword) \n", + "\n", + "\n", + "\n", + "```\n", + "Output:\n", + "```json\n", + "{\n", + " \"文本分析结果\": {\n", + " \"情感分析\": {\n", + " \"整体情感\": \"积极\",\n", + " \"情感得分\": 0.85,\n", + " \"情感细分\": {\n", + " \"高兴\": 0.6,\n", + " \"期待\": 0.25,\n", + " \"赞赏\": 0.1\n", + " }\n", + " },\n", + " \"实体识别\": [\n", + " {\n", + " \"实体\": \"马云\",\n", + " \"类型\": \"人物\",\n", + " \"起始位置\": 29,\n", + " \"结束位置\": 33\n", + " },\n", + " {\n", + " \"实体\": \"阿里巴巴集团\",\n", + " \"类型\": \"组织\",\n", + " \"起始位置\": 16,\n", + " \"结束位置\": 27\n", + " },\n", + " {\n", + " \"实体\": \"北京国家会议中心\",\n", + " \"类型\": \"地点\",\n", + " \"起始位置\": 7,\n", + " \"结束位置\": 21\n", + " },\n", + " {\n", + " \"实体\": \"中国\",\n", + " \"类型\": \"国家\",\n", + " \"起始位置\": 60,\n", + " \"结束位置\": 63\n", + " }\n", + " ],\n", + " \"关键词提取\": [\n", + " {\n", + " \"关键词\": \"投资\",\n", + " \"权重\": 0.25\n", + " },\n", + " {\n", + " \"关键词\": \"农村电商\",\n", + " \"权重\": 0.2\n", + " },\n", + " {\n", + " \"关键词\": \"马云\",\n", + " \"权重\": 0.18\n", + " },\n", + " {\n", + " \"关键词\": \"阿里巴巴\",\n", + " \"权重\": 0.15\n", + " },\n", + " {\n", + " \"关键词\": \"北京国家会议中心\",\n", + " \"权重\": 0.12\n", + " }\n", + " ]\n", + " }\n", + "}\n", + "``` \n", + "\n", + "**Explanation:**\n", + "\n", + "* **情感分析:** The text expresses a positive sentiment overall, with a score of 0.85. The emotions detected are \"高兴\" (happy), \"期待\" (expectation), and \"赞赏\" (appreciation).\n", + "* **实体识别:** The entities identified are:\n", + " * **马云 (Jack Ma):** A person, the chairman of Alibaba Group.\n", + " * **阿里巴巴集团 (Alibaba Group):** An organization, the company making the investment.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** A location, where the announcement was made.\n", + " * **中国 (China):** A country, the beneficiary of the investment.\n", + "* **关键词提取:** The keywords extracted are:\n", + " * **投资 (investment):** The core action of the announcement.\n", + " * **农村电商 (rural e-commerce):** The focus of the investment.\n", + " * **马云 (Jack Ma):** The key person making the announcement.\n", + " * **阿里巴巴 (Alibaba):** The company behind the investment.\n", + " * **北京国家会议中心 (Beijing National Convention Center):** The location of the announcement, adding context.\n", + "\n", + "\n", + "\n", + "Let me know if you have any other text you'd like me to analyze!\n" + ] + } + ], + "source": [ + "initial_states = [\n", + " AgentState(\n", + " max_output_age=3,\n", + " user_message=\"(2+8)*3\",\n", + " expected_output=\"\"\"(2+8)*3\n", + "= 10*3\n", + "= 30\n", + "\"\"\",\n", + " acceptance_criteria=\"\"\"\n", + "* Exactly text match.\n", + "* Acceptable differences:\n", + " * Extra or missing spaces.\n", + " * Extra or missing line breaks at the beginning or end of the output.\n", + "\"\"\"),\n", + " AgentState(\n", + " max_output_age=3,\n", + " user_message=\"\"\"Here is the GDP data in billions of US dollars (USD) for these years:\n", + "\n", + "Germany:\n", + "\n", + "2015: $3,368.29 billion\n", + "2016: $3,467.79 billion\n", + "2017: $3,677.83 billion\n", + "2018: $3,946.00 billion\n", + "2019: $3,845.03 billion\n", + "France:\n", + "\n", + "2015: $2,423.47 billion\n", + "2016: $2,465.12 billion\n", + "2017: $2,582.49 billion\n", + "2018: $2,787.86 billion\n", + "2019: $2,715.52 billion\n", + "United Kingdom:\n", + "\n", + "2015: $2,860.58 billion\n", + "2016: $2,650.90 billion\n", + "2017: $2,622.43 billion\n", + "2018: $2,828.87 billion\n", + "2019: $2,829.21 billion\n", + "Italy:\n", + "\n", + "2015: $1,815.72 billion\n", + "2016: $1,852.50 billion\n", + "2017: $1,937.80 billion\n", + "2018: $2,073.90 billion\n", + "2019: $1,988.14 billion\n", + "Spain:\n", + "\n", + "2015: $1,199.74 billion\n", + "2016: $1,235.95 billion\n", + "2017: $1,313.13 billion\n", + "2018: $1,426.19 billion\n", + "2019: $1,430.38 billion\n", + "\"\"\",\n", + " expected_output=\"\"\"Year,Germany,France,United Kingdom,Italy,Spain\n", + "2016-2015,2.96%,1.71%,-7.35%,2.02%,3.04%\n", + "2017-2016,5.08%,4.78%,-1.07%,4.61%,6.23%\n", + "2018-2017,7.48%,7.99%,7.89%,7.10%,8.58%\n", + "2019-2018,-2.56%,-2.59%,0.01%,-4.11%,0.30%\n", + "\"\"\",\n", + " acceptance_criteria=\"\"\"\n", + "* Strict text matching of the header row and first column(year).\n", + "* Acceptable differences:\n", + " * Differences in digital/percentage values in the table, even significant ones.\n", + " * Extra or missing spaces.\n", + " * Extra or missing line breaks.\n", + "\"\"\"),\n", + " AgentState(\n", + " max_output_age=3,\n", + " user_message=\"\"\"\n", + "Gene sequence: ATGGCCATGGCGCCCAGAACTGAGATCAATAGTACCCGTATTAACGGGTGA\n", + "Species: Escherichia coli\n", + "\"\"\",\n", + " expected_output=\"\"\"\n", + "{\n", + " \"Gene Sequence Analysis Results\": {\n", + " \"Basic Information\": {\n", + " \"Sequence Length\": 54,\n", + " \"GC Content\": \"51.85%\"\n", + " },\n", + " \"Nucleotide Composition\": {\n", + " \"A\": {\"Count\": 12, \"Percentage\": \"22.22%\"},\n", + " \"T\": {\"Count\": 11, \"Percentage\": \"20.37%\"},\n", + " \"G\": {\"Count\": 16, \"Percentage\": \"29.63%\"},\n", + " \"C\": {\"Count\": 15, \"Percentage\": \"27.78%\"}\n", + " },\n", + " \"Codon Analysis\": {\n", + " \"Start Codon\": \"ATG\",\n", + " \"Stop Codon\": \"TGA\",\n", + " \"Codon Table\": [\n", + " {\"Codon\": \"ATG\", \"Amino Acid\": \"Methionine\", \"Position\": 1},\n", + " {\"Codon\": \"GCC\", \"Amino Acid\": \"Alanine\", \"Position\": 2},\n", + " {\"Codon\": \"ATG\", \"Amino Acid\": \"Methionine\", \"Position\": 3},\n", + " // ... other codons ...\n", + " {\"Codon\": \"TGA\", \"Amino Acid\": \"Stop Codon\", \"Position\": 18}\n", + " ]\n", + " },\n", + " \"Potential Function Prediction\": {\n", + " \"Protein Length\": 17,\n", + " \"Possible Functional Domains\": [\n", + " {\"Domain Name\": \"ABC Transporter\", \"Start Position\": 5, \"End Position\": 15, \"Confidence\": \"75%\"},\n", + " {\"Domain Name\": \"Membrane Protein\", \"Start Position\": 1, \"End Position\": 17, \"Confidence\": \"60%\"}\n", + " ],\n", + " \"Secondary Structure Prediction\": {\n", + " \"α-helix\": [\"2-8\", \"12-16\"],\n", + " \"β-sheet\": [\"9-11\"],\n", + " \"Random Coil\": [\"1\", \"17\"]\n", + " }\n", + " },\n", + " \"Homology Analysis\": {\n", + " \"Most Similar Sequences\": [\n", + " {\n", + " \"Gene Name\": \"abcT\",\n", + " \"Species\": \"Salmonella enterica\",\n", + " \"Similarity\": \"89%\",\n", + " \"E-value\": \"3e-25\"\n", + " },\n", + " {\n", + " \"Gene Name\": \"yojI\",\n", + " \"Species\": \"Escherichia coli\",\n", + " \"Similarity\": \"95%\",\n", + " \"E-value\": \"1e-30\"\n", + " }\n", + " ]\n", + " },\n", + " \"Mutation Analysis\": {\n", + " \"SNP Sites\": [\n", + " {\"Position\": 27, \"Wild Type\": \"A\", \"Mutant\": \"G\", \"Amino Acid Change\": \"Glutamine->Arginine\"},\n", + " {\"Position\": 42, \"Wild Type\": \"C\", \"Mutant\": \"T\", \"Amino Acid Change\": \"None (Synonymous Mutation)\"}\n", + " ]\n", + " }\n", + " }\n", + "}\n", + "\"\"\",\n", + " acceptance_criteria=\"\"\"\n", + "* Consistent with Expected Output:\n", + " * Formats of all JSON sections\n", + " * Data types of all JSON fields\n", + " * Top layer sections\n", + "* Acceptable differences:\n", + " * Extra or missing spaces\n", + " * Extra or missing line breaks at the beginning or end of the output\n", + " * Differences in JSON field values\n", + " * JSON wrapped in backquotes\n", + "\"\"\"),\n", + " AgentState(\n", + " max_output_age=3,\n", + " user_message=\"\"\"\n", + "今天下午3点,在北京国家会议中心,阿里巴巴集团董事局主席马云宣布将投资100亿元人民币用于农村电商发展。这一决定受到了与会代表的热烈欢迎,大家认为这将为中国农村经济带来新的机遇。\n", + "\"\"\",\n", + " expected_output=\"\"\"\n", + "{\n", + " \"文本分析结果\": {\n", + " \"情感分析\": {\n", + " \"整体情感\": \"积极\",\n", + " \"情感得分\": 0.82,\n", + " \"情感细分\": {\n", + " \"乐观\": 0.75,\n", + " \"兴奋\": 0.60,\n", + " \"期待\": 0.85\n", + " }\n", + " },\n", + " \"实体识别\": [\n", + " {\"实体\": \"北京\", \"类型\": \"地点\", \"起始位置\": 7, \"结束位置\": 9},\n", + " {\"实体\": \"国家会议中心\", \"类型\": \"地点\", \"起始位置\": 9, \"结束位置\": 15},\n", + " {\"实体\": \"阿里巴巴集团\", \"类型\": \"组织\", \"起始位置\": 16, \"结束位置\": 22},\n", + " {\"实体\": \"马云\", \"类型\": \"人物\", \"起始位置\": 26, \"结束位置\": 28},\n", + " {\"实体\": \"100亿元\", \"类型\": \"金额\", \"起始位置\": 32, \"结束位置\": 37},\n", + " {\"实体\": \"人民币\", \"类型\": \"货币\", \"起始位置\": 37, \"结束位置\": 40},\n", + " {\"实体\": \"中国\", \"类型\": \"地点\", \"起始位置\": 71, \"结束位置\": 73}\n", + " ],\n", + " \"关键词提取\": [\n", + " {\"关键词\": \"农村电商\", \"权重\": 0.95},\n", + " {\"关键词\": \"马云\", \"权重\": 0.85},\n", + " {\"关键词\": \"投资\", \"权重\": 0.80},\n", + " {\"关键词\": \"阿里巴巴\", \"权重\": 0.75},\n", + " {\"关键词\": \"经济机遇\", \"权重\": 0.70}\n", + " ]\n", + " }\n", + "}\n", + "\"\"\",\n", + " acceptance_criteria=\"\"\"\n", + "* Consistent with Expected Output:\n", + " * Formats of all JSON sections\n", + " * Data types of all JSON fields\n", + " * Top layer sections\n", + "* Acceptable differences:\n", + " * Differences in digital values in the table.\n", + " * Extra or missing spaces.\n", + " * Extra or missing line breaks at the beginning or end of the output.\n", + " * Differences in JSON field values\n", + " * Differences in section/item orders.\n", + " * JSON wrapped in backquotes.\n", + "\"\"\"),\n", + " AgentState(\n", + " max_output_age=3,\n", + " user_message=\"Low-noise amplifier\",\n", + " expected_output=\"\"\"\n", + "A '''low-noise amplifier''' ('''LNA''') is an electronic component that amplifies a very low-power [[signal]] without significantly degrading its [[signal-to-noise ratio]] (SNR). Any [[electronic amplifier]] will increase the power of both the signal and the [[Noise (electronics)|noise]] present at its input, but the amplifier will also introduce some additional noise. LNAs are designed to minimize that additional noise, by choosing special components, operating points, and [[Circuit topology (electrical)|circuit topologies]]. Minimizing additional noise must balance with other design goals such as [[power gain]] and [[impedance matching]].\n", + "\n", + "LNAs are found in [[Radio|radio communications]] systems, [[Amateur Radio]] stations, medical instruments and [[electronic test equipment]]. A typical LNA may supply a power gain of 100 (20 [[decibels]] (dB)) while decreasing the SNR by less than a factor of two (a 3 dB [[noise figure]] (NF)). Although LNAs are primarily concerned with weak signals that are just above the [[noise floor]], they must also consider the presence of larger signals that cause [[intermodulation distortion]].\n", + "\"\"\",\n", + " acceptance_criteria=\"\"\"\n", + "* Consistent with Expected Output:\n", + " * Language\n", + " * Text length\n", + " * Text style\n", + " * Text structures\n", + "* Cover all the major content of Expected Output.\n", + "* Acceptable differences:\n", + " * Minor format differences.\n", + " * Expression differences.\n", + " * Numerical differences.\n", + " * Additional content in Actual Output.\n", + " * Missing minor content in Actual Output.\n", + "\"\"\"\n", + " ),\n", + " AgentState(\n", + " max_output_age=3,\n", + " user_message=\"What is the meaning of life?\",\n", + " expected_output=\"\"\"\n", + "[\n", + " {\"persona\": \"Philosopher\", \"prompt\": \"Explore the concept of life's meaning through the lens of existentialism and purpose-driven existence.\"},\n", + " {\"persona\": \"Scientist\", \"prompt\": \"Examine the biological and evolutionary perspectives on the function and significance of life.\"},\n", + " {\"persona\": \"Child\", \"prompt\": \"Imagine you're explaining to a curious 7-year-old what makes life special and important.\"}\n", + "]\n", + "\"\"\",\n", + " acceptance_criteria=\"\"\"\n", + "* Consistent with Expected Output:\n", + " * Formats of all JSON sections\n", + " * Data types and formats of all JSON fields\n", + " * Top layer sections\n", + "* Acceptable differences:\n", + " * Differences in field values\n", + " * Extra or missing spaces\n", + " * Extra or missing line breaks at the beginning or end of the output\n", + " * JSON wrapped in backquotes\n", + "\"\"\"\n", + " ),\n", + " AgentState(\n", + " max_output_age=3,\n", + " user_message=\"\"\" 0) {\n", + " echo \"Login successful\";\n", + "} else {\n", + " echo \"Login failed\";\n", + "}\n", + "?>\n", + "\"\"\",\n", + " expected_output=\"\"\"\n", + "security_analysis:\n", + " vulnerabilities:\n", + " - type: SQL Injection\n", + " severity: Critical\n", + " description: Unsanitized user input directly used in SQL query\n", + " mitigation: Use prepared statements or parameterized queries\n", + " - type: Password Storage\n", + " severity: High\n", + " description: Passwords stored in plain text\n", + " mitigation: Use password hashing (e.g., bcrypt) before storage\n", + " additional_issues:\n", + " - Lack of input validation\n", + " - No CSRF protection\n", + " - Potential for timing attacks in login logic\n", + " overall_risk_score: 9.5/10\n", + " recommended_actions:\n", + " - Implement proper input sanitization\n", + " - Use secure password hashing algorithms\n", + " - Add CSRF tokens to forms\n", + " - Consider using a secure authentication library\n", + "\"\"\",\n", + " acceptance_criteria=\"\"\"\n", + "* Consistent with Expected Output:\n", + " * Formats of all YAML sections\n", + " * Data types and formats of all YAML fields\n", + " * Top layer sections\n", + "* Acceptable differences:\n", + " * Differences in field values\n", + " * Extra or missing spaces\n", + " * Extra or missing line breaks at the beginning or end of the output\n", + " * YAML wrapped in backquotes\n", + "\"\"\"\n", + " ),\n", + "]\n", + "\n", + "selected_states = initial_states[3:4]\n", + "\n", + "for initial_state in selected_states:\n", + " print(\"User Message:\\n\", initial_state.user_message)\n", + " print(\"Expected Output:\\n\", initial_state.expected_output)\n", + "\n", + " try:\n", + " config = {\"configurable\": {\"thread_id\": \"1\"}, \"recursion_limit\": 25}\n", + " result = graph.invoke(initial_state, config)\n", + " print(\"Final Result:\", result)\n", + "\n", + " # format system message, break it into multiple lines\n", + " print(\"System Message:\")\n", + " print(result['best_system_message'])\n", + " print(\"Output:\")\n", + " print(result['best_output'])\n", + " except Exception as e:\n", + " # print the error message, saying failed to converge\n", + " print(\"Failed to converge.\")\n", + " print(e)\n", + "\n", + " states = graph.get_state(config)\n", + "\n", + " # if the length of states is bigger than 0, print the best system message and output\n", + " if len(states) > 0:\n", + " result = states[0]\n", + "\n", + " print(\"System Message:\")\n", + " print(result['best_system_message'])\n", + " print(\"Output:\")\n", + " print(result['best_output'])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}