diff --git a/examples/code_assistant/langgraph_code_assistant.ipynb b/examples/code_assistant/langgraph_code_assistant.ipynb index 6cf6e2501..988c3029b 100644 --- a/examples/code_assistant/langgraph_code_assistant.ipynb +++ b/examples/code_assistant/langgraph_code_assistant.ipynb @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "e3900420", "metadata": {}, "outputs": [], @@ -47,7 +47,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "c2eb35d1-4990-47dc-a5c4-208bae588a82", "metadata": {}, "outputs": [], @@ -79,12 +79,14 @@ "\n", "### Code solution\n", "\n", + "We can consider [Claude3](https://docs.anthropic.com/claude/docs/models-overview) variants, too.\n", + "\n", "We de-couple code solution and code formatting so that any LLM can be used for code solution. " ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "3ba3df70-f6b4-4ea5-a210-e10944960bc6", "metadata": {}, "outputs": [], @@ -104,41 +106,6 @@ " (\"placeholder\", \"{messages}\")]\n", ")\n", "\n", - "# code_gen_llm = ChatOpenAI(temperature=0, model=\"gpt-4-0125-preview\")\n", - "code_gen_llm = ChatAnthropic(temperature=0, model='claude-3-opus-20240229')\n", - "\n", - "code_gen_chain = code_gen_prompt | code_gen_llm | StrOutputParser()\n", - "question = \"How do I build a RAG chain in LCEL?\"\n", - "solution = code_gen_chain.invoke({\"context\" : concatenated_content, \"messages\" : [(\"user\",question)]})" - ] - }, - { - "cell_type": "markdown", - "id": "eb01dcde-b446-4fae-90af-a303222f90e7", - "metadata": {}, - "source": [ - "### Formatted code\n", - "\n", - "The structured output generation is handled in a seperate step." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "4ad38291-807b-43e2-952b-8d9007617630", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "from langchain_core.pydantic_v1 import BaseModel, Field\n", - "\n", - "# Prompt\n", - "prompt = ChatPromptTemplate.from_messages(\n", - " [(\"system\",\"\"\"You are an expert a code formatting, strating with a code solution \\n\n", - " Structure the solution in three parts with a prefix that defines the problem, then \\n \n", - " list the imports, and finally list the functioning code block.\"\"\" ),\n", - " (\"user\", \"Here is the code solution: {code}\"),]) \n", - " \n", "# Data model\n", "class code(BaseModel):\n", " \"\"\"Code output\"\"\"\n", @@ -147,29 +114,19 @@ " imports: str = Field(description=\"Code block import statements\")\n", " code: str = Field(description=\"Code block not including import statements\")\n", "\n", - "# Formatter \n", - "llm = ChatOpenAI(model=\"gpt-4-0125-preview\", temperature=0)\n", - "llm_formatter = llm.with_structured_output(code)\n", - "structured_code_formatter = prompt | llm_formatter\n", - "output = structured_code_formatter.invoke([(\"code\",solution)])" - ] - }, - { - "cell_type": "markdown", - "id": "00d6a0b4-9b7e-4067-8622-832201ad9b56", - "metadata": {}, - "source": [ - "## Parameters" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "01598aee-f272-4d72-aefc-2cb15dcbfdfc", - "metadata": {}, - "outputs": [], - "source": [ - "max_iterations = 3" + "'''\n", + "expt_llm = \"claude3-opus\"\n", + "code_gen_llm = ChatAnthropic(temperature=0, model='claude-3-opus-20240229')\n", + "expt_llm = \"claude3-haiku\"\n", + "code_gen_llm = ChatAnthropic(temperature=0, model=\"claude-3-haiku-20240307\")\n", + "'''\n", + "\n", + "expt_llm = \"gpt-4-0125-preview\"\n", + "llm = ChatOpenAI(temperature=0, model=expt_llm)\n", + "code_gen_chain = code_gen_prompt | llm.with_structured_output(code)\n", + "question = \"How do I build a RAG chain in LCEL?\"\n", + "solution = code_gen_chain.invoke({\"context\":concatenated_content,\"messages\":[(\"user\",question)]})\n", + "solution" ] }, { @@ -184,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "c185f1a2-e943-4bed-b833-4243c9c64092", "metadata": {}, "outputs": [], @@ -220,7 +177,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "id": "b70e8301-63ae-4f7e-ad8f-c9a052fe3566", "metadata": {}, "outputs": [], @@ -230,6 +187,14 @@ "from langchain_core.pydantic_v1 import BaseModel, Field\n", "from langchain_core.runnables import RunnablePassthrough\n", "\n", + "### Parameter\n", + "\n", + "# Max tries\n", + "max_iterations = 3\n", + "# Reflect\n", + "# flag = 'reflect'\n", + "flag = 'do not reflect'\n", + " \n", "### Nodes\n", "\n", "def generate(state: GraphState):\n", @@ -248,18 +213,20 @@ " # State\n", " messages = state[\"messages\"]\n", " iterations = state[\"iterations\"]\n", - " \n", - " # Solution\n", - " solution = code_gen_chain.invoke({\"context\" : concatenated_content, \"messages\" : messages})\n", + " error = state[\"error\"]\n", "\n", - " # Structured output\n", - " code_solution = structured_code_formatter.invoke([(\"code\",solution)])\n", + " # We have been routed back to generation with an error\n", + " if error == \"yes\":\n", + " messages += [(\"user\",\"Now, try again. Be sure to structure your answer with a prefix, imports, and code block:\")]\n", + " \n", + " # Solution\n", + " code_solution = code_gen_chain.invoke({\"context\": concatenated_content, \"messages\" : messages})\n", + " messages += [(\"assistant\",f\"{code_solution.prefix} \\n Imports: {code_solution.imports} \\n Code: {code_solution.code}\")]\n", " \n", " # Increment\n", " iterations = iterations + 1\n", " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations}\n", "\n", - "\n", "def code_check(state: GraphState):\n", " \"\"\"\n", " Check code\n", @@ -273,7 +240,7 @@ "\n", " print(\"---CHECKING CODE---\")\n", " \n", - " ## State\n", + " # State\n", " messages = state[\"messages\"]\n", " code_solution = state[\"generation\"]\n", " iterations = state[\"iterations\"]\n", @@ -288,7 +255,7 @@ " exec(imports)\n", " except Exception as e:\n", " print(\"---CODE IMPORT CHECK: FAILED---\")\n", - " error_message = [(\"user\", f\"Import Test Failure: You are required to fix the import error: {e}\")]\n", + " error_message = [(\"assistant\", f\"Your solution failed the import test: {e}\")]\n", " messages += error_message\n", " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations, \"error\": \"yes\"}\n", " \n", @@ -297,14 +264,14 @@ " exec(imports + \"\\n\" + code)\n", " except Exception as e:\n", " print(\"---CODE BLOCK CHECK: FAILED---\")\n", - " error_message = [(\"user\", f\"Execution Test Failure: You are required to fix the code execution error: {e}\")]\n", + " error_message = [(\"assistant\", f\"Your solution failed the code execution test: {e}\")]\n", + " messages += error_message\n", " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations, \"error\": \"yes\"}\n", " \n", " # No errors\n", " print(\"---NO CODE TEST FAILURES---\")\n", " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations, \"error\": \"no\"}\n", "\n", - "\n", "def reflect(state: GraphState):\n", " \"\"\"\n", " Reflect on errors\n", @@ -321,19 +288,18 @@ " # State\n", " messages = state[\"messages\"]\n", " iterations = state[\"iterations\"]\n", + " code_solution = state[\"generation\"]\n", "\n", " # Prompt reflection\n", " reflection_message = [(\"user\", \"\"\"You tried to solve this problem and failed a unit test. Reflect on this failure\n", - " given the provided documentation. Carefully write suggestions based on the \n", + " given the provided documentation. Write a few key suggestions based on the \n", " documentation to avoid making this mistake again.\"\"\")]\n", - " messages += reflection_message\n", " \n", - " # Suggesting\n", + " # Add reflection\n", " reflections = code_gen_chain.invoke({\"context\" : concatenated_content, \"messages\" : messages})\n", - " messages += reflections\n", + " messages += [(\"assistant\" , f\"Here are reflections on the error: {reflections}\")]\n", " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations}\n", "\n", - "\n", "### Edges\n", "\n", "def decide_to_finish(state: GraphState):\n", @@ -354,12 +320,15 @@ " return \"end\"\n", " else:\n", " print(\"---DECISION: RE-TRY SOLUTION---\")\n", - " return \"reflect\" # Or, directly back to generate" + " if flag == 'reflect':\n", + " return \"reflect\"\n", + " else:\n", + " return \"generate\"" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "id": "f66b4e00-4731-42c8-bc38-72dd0ff7c92c", "metadata": {}, "outputs": [], @@ -382,6 +351,7 @@ " {\n", " \"end\": END,\n", " \"reflect\": \"reflect\",\n", + " \"generate\": \"generate\",\n", " },\n", ")\n", "workflow.add_edge(\"reflect\", \"generate\")\n", @@ -390,36 +360,12 @@ }, { "cell_type": "code", - "execution_count": 30, - "id": "c4b1b35b-c8bd-4c68-a4b1-f09a7dfc8707", + "execution_count": null, + "id": "9bcaafe4-ddcf-4fab-8620-2d9b6c508f98", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---GENERATING CODE SOLUTION---\n", - "---CHECKING CODE---\n", - "---NO CODE TEST FAILURES---\n", - "---DECISION: FINISH---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'error': 'no',\n", - " 'messages': [('user', 'How do I build a RAG chain in LCEL?')],\n", - " 'generation': code(prefix=\"Problem: Building a Retrieval-Augmented Generation (RAG) Chain using LangChain Expression Language (LCEL).\\n\\nApproach: To construct a RAG chain in LCEL, it's essential to integrate a retriever, a prompt template, a language model, and an output parser. The process involves creating a retriever to fetch relevant documents based on a query, defining a prompt template that incorporates the retrieved context and a question, instantiating a language model, and creating a chain by connecting the retriever, prompt, model, and output parser. Finally, the chain is invoked with a question to obtain the answer.\", imports='from langchain_community.vectorstores import FAISS\\nfrom langchain_core.output_parsers import StrOutputParser\\nfrom langchain_core.prompts import ChatPromptTemplate\\nfrom langchain_core.runnables import RunnablePassthrough\\nfrom langchain_openai import ChatOpenAI, OpenAIEmbeddings', code='# Load documents into a vector store\\nvectorstore = FAISS.from_texts(\\n [\"harrison worked at kensho\"], embedding=OpenAIEmbeddings()\\n)\\nretriever = vectorstore.as_retriever()\\n\\n# Define prompt template\\ntemplate = \"\"\"Answer the question based only on the following context:\\n{context}\\nQuestion: {question}\"\"\"\\nprompt = ChatPromptTemplate.from_template(template)\\n\\n# Instantiate model and output parser\\nmodel = ChatOpenAI()\\noutput_parser = StrOutputParser()\\n\\n# Create RAG chain \\nchain = (\\n {\"context\": retriever, \"question\": RunnablePassthrough()}\\n | prompt \\n | model\\n | output_parser\\n)\\n\\n# Run the chain\\nchain.invoke(\"where did harrison work?\")'),\n", - " 'iterations': 1}" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "question = \"How do I build a RAG chain in LCEL?\"\n", + "question = \"How can I directly pass a string to a runnable and use it to construct the input needed for my prompt?\"\n", "app.invoke({\"messages\":[(\"user\",question)],\"iterations\":0})" ] }, @@ -445,7 +391,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "id": "678e8954-56b5-4cc6-be26-f7f2a060b242", "metadata": {}, "outputs": [], @@ -476,7 +422,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "id": "455a34ea-52cb-4ae5-9f4a-7e4a08cd0c09", "metadata": {}, "outputs": [], @@ -511,14 +457,14 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "id": "c8fa6bcb-b245-4422-b79a-582cd8a7d7ea", "metadata": {}, "outputs": [], "source": [ "def predict_base_case(example: dict):\n", " \"\"\" Context stuffing \"\"\"\n", - " solution = code_gen_chain.invoke({\"context\" : concatenated_content, \"messages\" : [(\"user\",example[\"question\"])] })\n", + " solution = code_gen_chain.invoke({\"context\" : concatenated_content, \"messages\" : [(\"user\",example[\"question\"])]})\n", " solution_structured = structured_code_formatter.invoke([(\"code\",solution)])\n", " return {\"imports\": solution_structured.imports, \"code\": solution_structured.code}\n", "\n", @@ -532,64 +478,9 @@ { "cell_type": "code", "execution_count": null, - "id": "2dacccf0-d73f-4017-aaf0-9806ffe5bd2c", + "id": "d9c57468-97f6-47d6-a5e9-c09b53bfdd83", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/l9/bpjxdmfx7lvd1fbdjn38y5dh0000gn/T/ipykernel_43078/1906326331.py:10: UserWarning: Function evaluate is in beta.\n", - " experiment_results = evaluate(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "View the evaluation results for experiment: 'test-without-langgraph:1f3259c' at:\n", - "https://smith.langchain.com/o/1fa8b1f4-fcb9-4072-9aa9-983e35ad61b8/datasets/76bc1d66-8cb6-4f6d-b633-7cd077937e46/compare?selectedSessions=1c92918c-b111-412d-b845-a96f2ee37f7c\n", - "\n", - "\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "981c40cd7ce34b97a06d8f4daa339710", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "0it [00:00, ?it/s]" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n", - "Error running target function: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of concurrent connections has exceeded your rate limit. Please try again later or contact sales at https://www.anthropic.com/contact-sales to discuss your options for a rate limit increase.'}}\n" - ] - } - ], + "outputs": [], "source": [ "from langsmith.evaluation import evaluate\n", "\n", @@ -597,358 +488,68 @@ "code_evalulator = [check_import,check_execution]\n", "\n", "# Dataset\n", - "dataset_name = \"test-LCEL-code-gen\"\n", - "\n", + "dataset_name = \"test-LCEL-code-gen\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2dacccf0-d73f-4017-aaf0-9806ffe5bd2c", + "metadata": {}, + "outputs": [], + "source": [ "# Run base case\n", - "experiment_results = evaluate(\n", + "experiment_results_ = evaluate(\n", " predict_base_case,\n", " data=dataset_name,\n", " evaluators=code_evalulator,\n", - " experiment_prefix=\"test-without-langgraph\",\n", + " experiment_prefix=f\"test-without-langgraph-{expt_llm}\", \n", + " max_concurrency=2,\n", " metadata={\n", - " \"variant\": \"Claude3\",\n", + " \"llm\": expt_llm,\n", " },\n", - ")\n", - "\n", - "# Run with langgraph\n", - "experiment_results = evaluate(\n", - " predict_langgraph,\n", - " data=dataset_name,\n", - " evaluators=code_evalulator,\n", - " experiment_prefix=\"test-with-langgraph\",\n", - " metadata={\n", - " \"variant\": \"Claude3\",\n", - " },\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "5caa3a37-5fd2-4e4c-b310-577c239f9d61", - "metadata": {}, - "source": [ - "## TODO: Clean This Later ## \n", - "\n", - "Compute standard error across 4 trials." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "14f8484b-9d57-4132-8801-74a4067f97db", - "metadata": {}, - "outputs": [], - "source": [ - "# You will have to update these to match the tests you ran.\n", - "# The test name can be found at langgraph_results[\"project_name\"]\n", - "langgraph = [\n", - " \"80db-context-stuffing-with-langgraph\",\n", - " \"060c-context-stuffing-with-langgraph\",\n", - " \"93cd-context-stuffing-with-langgraph\",\n", - " \"60ef-context-stuffing-with-langgraph\",\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "d19deeb6-9fc3-46b0-affb-5baaef4e9bba", - "metadata": {}, - "outputs": [], - "source": [ - "no_langgraph = [\n", - " \"b493-context-stuffing-no-langgraph\",\n", - " \"eb8a-context-stuffing-no-langgraph\",\n", - " \"b88c-context-stuffing-no-langgraph\",\n", - " \"0aaa-context-stuffing-no-langgraph\",\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "d6340773-ca0b-4320-b841-093bbfb1161a", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "\n", - "\n", - "def prepare_dataframe(project, trial_number, chain):\n", - " df = client.get_test_results(project_name=project)\n", - " df = df.dropna(subset=[\"feedback.check_execution\", \"feedback.check_import\"])\n", - " df = df[[\"input.question\", \"feedback.check_execution\", \"feedback.check_import\"]]\n", - " df[\"trial #\"] = trial_number\n", - " df[\"chain\"] = chain\n", - " return df\n", - "\n", - "\n", - "# Prepare each dataframe\n", - "dfs_chain1 = [\n", - " prepare_dataframe(project, i + 1, \"LangGraph\")\n", - " for i, project in enumerate(langgraph)\n", - "]\n", - "dfs_chain2 = [\n", - " prepare_dataframe(project, i + 1, \"No LangGraph\")\n", - " for i, project in enumerate(no_langgraph)\n", - "]\n", - "\n", - "# Combine all dataframes\n", - "final_df = pd.concat(dfs_chain1 + dfs_chain2, ignore_index=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "30dd9b44-b23f-4709-a993-f1e6873ff3f2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "chain\n", - "LangGraph 78\n", - "No LangGraph 79\n", - "dtype: int64" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "final_df.groupby(\"chain\").size()" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "2c4e226c-511b-41fa-b850-2a0a3f67a44d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Fraction Imports CorrectFraction Execution CorrectImports Correct Std ErrorExecution Correct Std Error
chain
LangGraph1.0000000.8076920.0000000.044625
No LangGraph0.9873420.5569620.0125780.055888
\n", - "
" - ], - "text/plain": [ - " Fraction Imports Correct Fraction Execution Correct \\\n", - "chain \n", - "LangGraph 1.000000 0.807692 \n", - "No LangGraph 0.987342 0.556962 \n", - "\n", - " Imports Correct Std Error Execution Correct Std Error \n", - "chain \n", - "LangGraph 0.000000 0.044625 \n", - "No LangGraph 0.012578 0.055888 " - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "\n", - "\n", - "def group_standard_error(group):\n", - " \"\"\"\n", - " Calculate the standard error for the 'correct' column in a given group.\n", - "\n", - " The function assumes the 'correct' column contains binary values (0 or 1).\n", - " It computes the standard error based on the formula for the standard error\n", - " of a proportion, which is sqrt(p * (1 - p) / n), where p is the proportion\n", - " of successes (1s) and n is the total number of trials.\n", - "\n", - " Args:\n", - " group (pd.DataFrame): A DataFrame group with a 'correct' column.\n", - "\n", - " Returns:\n", - " pd.Series: A series containing the standard error of the 'correct' column.\n", - " \"\"\"\n", - " # 3 trials x 20 questions per trial = 60\n", - " total_trials = len(group)\n", - " std_errors = {}\n", - " for column in [\"feedback.check_import\", \"feedback.check_execution\"]:\n", - " # Number correct\n", - " occurrences = group[column].sum()\n", - " # Total trials\n", - " fraction = occurrences / total_trials\n", - " # Standard error\n", - " std_errors[column] = (fraction * (1 - fraction) / total_trials) ** 0.5\n", - " return pd.Series(std_errors)\n", - "\n", - "\n", - "# Calculate standard errors\n", - "std_errors = final_df.groupby([\"chain\"]).apply(group_standard_error)\n", - "\n", - "# Calculate the fraction of correct answers\n", - "grouped_frac_correct = (\n", - " final_df.groupby(\"chain\")[\n", - " [\"feedback.check_import\", \"feedback.check_execution\"]\n", - " ].sum()\n", - " / final_df.groupby(\"chain\")[\n", - " [\"feedback.check_import\", \"feedback.check_execution\"]\n", - " ].count()\n", - ")\n", - "\n", - "# Concatenate the fraction correct data with the standard errors\n", - "correct_frac_and_errors = pd.concat([grouped_frac_correct, std_errors], axis=1)\n", - "\n", - "# If you want to rename the columns for clarity\n", - "correct_frac_and_errors.columns = [\n", - " \"Fraction Imports Correct\",\n", - " \"Fraction Execution Correct\",\n", - " \"Imports Correct Std Error\",\n", - " \"Execution Correct Std Error\",\n", - "]\n", - "correct_frac_and_errors" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "b737b809-04dc-49f1-b070-f0609898e9d3", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAB8YAAAN5CAYAAAB61FHjAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB1dUlEQVR4nOzdebyWc/4/8PfpdDonJy1opalEkq3mWCYxiVSW0GAsQ8sYYoQKM5ahsjWDki/ZicgwY/uONSKMEUaUZUSRZWgjFa3qXL8//M797XZOdY7O6ejq+Xw8zuPR/bm293Xd93X3+Nyv6/pcOUmSJAEAAAAAAAAAKVWjugsAAAAAAAAAgKokGAcAAAAAAAAg1QTjAAAAAAAAAKSaYBwAAAAAAACAVBOMAwAAAAAAAJBqgnEAAAAAAAAAUk0wDgAAAAAAAECqCcYBAAAAAAAASDXBOAAAAAAAAACpJhgHoMLuvPPOyMnJiY8//jjT1rJlyzj00EM3aB0ff/xx5OTkxNVXX13l2+rbt2/UqVOnyrfzQ/vtt1/svPPOG3y7VI2yzp0NJScnJwYMGLDBt7s2lf29UfKdcOedd1baOgEA2LTo7244+rtrNnTo0MjJyanuMn6SqrPf91P8zFbF+duyZcvo27dvpa4T4KdCMA6wkSjpnJf1d95551V3eRutZcuWxTXXXBN77bVX1KtXLwoKCqJNmzYxYMCA+OCDD6q7vPVSXT9uVJYlS5bE0KFD4/nnny/X/M8///waz5Fjjz22aov9gSuuuCIeeeSRDbrNn5I5c+bEOeecE23bto3NNtssCgsLo6ioKC677LJYsGBBdZcHAMBPjP5u1Uh7f3dNn5mCgoLqLq9cKtrn3VDWdFybNGmyQeu49957Y9SoURt0mz8laT5/AapTzeouAICKueSSS6JVq1ZZbT+1q1U3Fl9++WX06NEjJk+eHIceemgcf/zxUadOnXj//ffjvvvui1tuuSVWrFhR3WVuspYsWRLDhg2LiO+vyi6vM888M/bYY4+stpYtW1ZiZet2xRVXxFFHHRVHHHFEVvuJJ54Yxx57bOTn52/Qejakf//733HwwQfHt99+GyeccEIUFRVFRMTrr78ef/7zn+PFF1+Mp59+ukq23aJFi1i6dGnk5eVVyfoBAKha+ruVZ1Po7+bn58dtt91Wqj03N7caqqm4tfV5//SnP1XrRSEHHnhg9O7dO6utdu3aG7SGe++9N955550YOHBgVvum0O+r7vP3/fffjxo13FMJpJNgHGAjc9BBB8Xuu+9e3WWkQt++fePNN9+MBx54II488sisaZdeemlceOGF1VTZpq24uHi9Onj77rtvHHXUUeWad+XKlVFcXBy1atX60duriNzc3I3mR5ofY8GCBdGrV6/Izc2NN998M9q2bZs1/fLLL49bb721yra/Md0dAgBAafq7lWdT6O/WrFkzTjjhhOouo0rUrFkzatasvp/u27RpU+5jmyRJLFu2bIMF55tCv6+6z980X8wP4LIfgJR58sknY999943CwsLYfPPN45BDDol333231HzTpk2Lo446KrbYYosoKCiI3XffPf7xj3+Umu/dd9+N/fffP2rXrh3bbLNNXHbZZVFcXLzG7T/99NPRvn37KCgoiHbt2sVDDz2UNX3+/PlxzjnnxC677BJ16tSJunXrxkEHHRRTp04tta5ly5bF0KFDo02bNlFQUBBNmzaNX/3qV/Hhhx+ucftJksQpp5wStWrVKrXt1b366qvx+OOPx0knnVSqkxHxfSegrGe5ff7553HEEUdEnTp1omHDhnHOOefEqlWrsuYpLi6OUaNGxU477RQFBQXRuHHj6N+/f3z99del1vfkk09G586dY/PNN4+6devGHnvsEffee+8a6474/hhvttlmcdxxx8XKlSvXOu8PlTwb7/nnn4/dd989ateuHbvssktm6LaHHnoodtlllygoKIiioqJ48803s5YvGZ79o48+iu7du0dhYWE0a9YsLrnkkkiSJGvexYsXx9lnnx3NmzeP/Pz82GGHHeLqq68uNV/Js6/HjRsXO+20U+Tn58dNN90UDRs2jIiIYcOGZYZuGzp0aIX2d3WrP6Nv1KhR0bp168jPz4///Oc/sWLFirj44oujqKgo6tWrF4WFhbHvvvvGxIkTS62nuLg4rr322sxxatiwYfTo0SNef/31zP4sXrw47rrrrkzdJc/mWtMzxm+44YbMvjdr1ixOP/30UkOOlzzL7D//+U906dIlNttss9h6663jyiuvrNBxGDduXOywww6Z9/jFF1/MTJs4cWLk5OTEww8/XGq5e++9N3JycmLSpElrXPfNN98cn3/+eYwcObJUKB4R0bhx4/jTn/5Uqv2ll16KPffcMwoKCmLbbbeNsWPHZk0v7/dGWc+aK/nMlufcBQDgp01/V3+3IpIkiS5dukTDhg1j7ty5mfYVK1bELrvsEq1bt47Fixdn2u+5554oKiqK2rVrxxZbbBHHHntsfPbZZ6XW++qrr8bBBx8cDRo0iMLCwth1113j2muvzUzfb7/9yhz1rG/fvpnRzD7++OO19nnLesb4ypUr49JLL830ZVu2bBkXXHBBLF++PGu+kn7/uvpZP1bJ+sePH5/5XeHmm2+OiIgxY8bE/vvvH40aNYr8/Pxo165d3HjjjWWuZ22fj/322y8ef/zx+OSTTzLHZvVjV9Yzxp977rnM90P9+vXj8MMPj/feey9rnpLjOmPGjOjbt2/Ur18/6tWrF/369YslS5aU+xhMnjw59t5776hdu3a0atUqbrrppsy0b7/9NgoLC+Oss84qtdx///vfyM3NjeHDh69x3VV5/l599dWx9957x5Zbbhm1a9eOoqKieOCBB0qt64fPGC/5LeNf//pXDB48OBo2bBiFhYXRq1evmDdv3hr3BeCnSDAOsJFZuHBhfPnll1l/Je6+++445JBDok6dOvGXv/wlLrroovjPf/4T++yzT1YQ9+6778YvfvGLeO+99+K8886LESNGRGFhYRxxxBFZgdjs2bOjS5cuMWXKlDjvvPNi4MCBMXbs2KwO3+qmT58exxxzTBx00EExfPjwqFmzZhx99NHxzDPPZOb56KOP4pFHHolDDz00Ro4cGeeee268/fbb0blz5/jiiy8y861atSoOPfTQGDZsWBQVFcWIESPirLPOioULF8Y777xT5vZXrVoVffv2jbFjx8bDDz8cv/rVr9Z4HEt+FDnxxBPXfsB/sP7u3bvHlltuGVdffXV07tw5RowYEbfcckvWfP37949zzz03OnXqFNdee23069cvxo0bF927d4/vvvsuM9+dd94ZhxxySMyfPz/OP//8+POf/xzt27ePp556ao01PPbYY3HYYYfF0UcfHffcc8+PuoJ8xowZcfzxx0fPnj1j+PDh8fXXX0fPnj1j3LhxMWjQoDjhhBNi2LBh8eGHH8avf/3rUj8MrVq1Knr06BGNGzeOK6+8MoqKimLIkCExZMiQzDxJksRhhx0W11xzTfTo0SNGjhwZO+ywQ5x77rkxePDgUjU999xzMWjQoDjmmGPi2muvjT322CPTee7Vq1fcfffdcffdd6/1PS3xzTfflDpHVt+HMWPGxHXXXRennHJKjBgxIrbYYotYtGhR3HbbbbHffvvFX/7ylxg6dGjMmzcvunfvHlOmTMla/0knnRQDBw6M5s2bx1/+8pc477zzoqCgIF555ZWI+P48zM/Pj3333TdTd//+/ddY79ChQ+P000+PZs2axYgRI+LII4+Mm2++Obp165b1eYmI+Prrr6NHjx6x2267xYgRI6Jt27bxxz/+MZ588sl1HpeIiBdeeCEGDhwYJ5xwQlxyySXx1VdfRY8ePTLn1H777RfNmzePcePGlVp23Lhx0bp16+jYseMa1/+Pf/wjateuXe479iO+/zweddRRceCBB8aIESOiQYMG0bdv36wfOMv7vbEm5T13AQCoXvq7+rsV7e/+8PPy5ZdfxqJFiyLi+4uW77jjjli2bFmceuqpmWWGDBkS7777bowZMyYKCwsj4vvRrXr37h3bb799jBw5MgYOHBjPPvts/PKXv8y6aPmZZ56JX/7yl/Gf//wnzjrrrBgxYkR06dIlHnvssXId5xINGzascJ/3d7/7XVx88cXx85//PK655pro3LlzDB8+PI499thS85ann7U2y5YtK3VcVw/g33///TjuuOPiwAMPjGuvvTbat28fERE33nhjtGjRIi644IIYMWJENG/ePH7/+9/H6NGjs9a/rs/HhRdeGO3bt4+tttoqc2zW9rzxCRMmRPfu3WPu3LkxdOjQGDx4cLz88svRqVOnUhemR0T8+te/jm+++SaGDx8ev/71r+POO+/MDGu/Ll9//XUcfPDBUVRUFFdeeWVss802cdppp8Udd9wRERF16tSJXr16xf33318qmP7rX/8aSZLEb37zmzWuvyrP32uvvTY6dOgQl1xySVxxxRWZ77HHH3+8XNs544wzYurUqTFkyJA47bTT4tFHH40BAwaUu06An4QEgI3CmDFjkogo8y9JkuSbb75J6tevn5x88slZy82ePTupV69eVvsBBxyQ7LLLLsmyZcsybcXFxcnee++dbL/99pm2gQMHJhGRvPrqq5m2uXPnJvXq1UsiIpk5c2amvUWLFklEJA8++GCmbeHChUnTpk2TDh06ZNqWLVuWrFq1KqvGmTNnJvn5+ckll1ySabvjjjuSiEhGjhxZ6lgUFxdnlouI5Kqrrkq+++675Jhjjklq166djB8/fu0HM0mSXr16JRGRfP311+ucN0mSpE+fPklEZNWYJEnSoUOHpKioKPP6n//8ZxIRybhx47Lme+qpp7LaFyxYkGy++ebJXnvtlSxdurTM/UuSJOncuXOy0047JUmSJA8++GCSl5eXnHzyyaWO4ZpqLiwszGoreZ9efvnlTNv48eOTiEhq166dfPLJJ5n2m2++OYmIZOLEiaWOwxlnnJFV7yGHHJLUqlUrmTdvXpIkSfLII48kEZFcdtllWds/6qijkpycnGTGjBmZtohIatSokbz77rtZ886bNy+JiGTIkCHr3NckSZKJEyeu8RyZOXNm5vNSt27dZO7cuVnLrly5Mlm+fHlW29dff500btw4+e1vf5tpe+6555KISM4888xS21/9fSssLEz69OlTap6S87jk3Jk7d25Sq1atpFu3blnv6fXXX59ERHLHHXdk2jp37pxERDJ27NhM2/Lly5MmTZokRx555DqPT8mxeP311zNtn3zySVJQUJD06tUr03b++ecn+fn5yYIFCzJtc+fOTWrWrLnO96JBgwbJbrvtts5aSpR8Hl988cWsbeXn5ydnn312pq283xsl7/GYMWMybeU9dwEAqD76u/9Hf7f8/d01fWa6d++eNW9J3/aee+5JXnnllSQ3NzcZOHBgZvrHH3+c5ObmJpdffnnWcm+//XZSs2bNTPvKlSuTVq1aJS1atCh1bH+4X507dy6z5hYtWmRer63PO2TIkMznP0mSZMqUKUlEJL/73e+y5jvnnHOSiEiee+65TFt5+1lrsqbjWtLPKln/U089VWrZJUuWlGrr3r17su2222Zel/fzccghh2QdrxJl9fvat2+fNGrUKPnqq68ybVOnTk1q1KiR9O7dO9NWclxX7+cnyffnzJZbbln2AVlNSb98xIgRmbbly5dntr9ixYokSf7vd5Ynn3wya/ldd921zM/GD2upivM3SUq/PytWrEh23nnnZP/9989qb9GiRdZvGiXf0V27ds16jwYNGpTk5uZm/X4A8FPnjnGAjczo0aPjmWeeyfqL+P6q5QULFsRxxx2XdUVvbm5u7LXXXpkhoefPnx/PPfdc5urYkvm++uqr6N69e0yfPj0+//zziIh44okn4he/+EXsueeeme03bNhwjVe2NmvWLHr16pV5Xbdu3ejdu3e8+eabMXv27Ij4fsinGjW+/+9n1apV8dVXX0WdOnVihx12iDfeeCOz7IMPPhhbbbVVnHHGGaW288PhxFasWBFHH310PPbYY/HEE09Et27d1nkcS64g33zzzdc57+pWv8o84vvnWX/00UeZ13//+9+jXr16ceCBB2a9D0VFRVGnTp3M+/DMM8/EN998k7nbeG37F/H9VcXHHHNM9O/fP26++ebMMfwx2rVrl3XX71577RUREfvvv3/87Gc/K9W++v6VWP2K4JKh0FesWBETJkyIiO8/O7m5uXHmmWdmLXf22WdHkiSl7nDu3LlztGvX7kfv0+ouvvjiUudIkyZNMtOPPPLIzJB1JXJzczPPGS8uLo758+fHypUrY/fddy/1uczJycm6O75EWe/bukyYMCFWrFgRAwcOzHpPTz755Khbt26pq7br1KmT9Zy3WrVqxZ577lnme1SWjh07RlFRUeb1z372szj88MNj/PjxmSvZe/fuHcuXL88aTu3++++PlStXrvMZc4sWLarwOdWuXbvYd999M68bNmwYO+ywQ9Y+lfd7Y23Wde4CAFD99Hf1dyvS3y0oKCj1eXnmmWfiz3/+c9Z8p5xySnTv3j3OOOOMOPHEE6N169ZxxRVXZKY/9NBDUVxcHL/+9a+z9qtJkyax/fbbZ/brzTffjJkzZ8bAgQOjfv3669yvyvTEE09ERJQage3ss8+OiCjVdyxPP2ttDj/88FLHtXv37pnprVq1ynpdYvXnjJeMANG5c+f46KOPYuHChRFR8c/HusyaNSumTJkSffv2jS222CLTvuuuu8aBBx6YOXarK+uz/tVXX2XOnbWpWbNm1qhwtWrViv79+8fcuXNj8uTJERHRtWvXaNasWdZobO+880689dZb5epXR1T++RuR/f58/fXXsXDhwth3333L3a8+5ZRTst6jfffdN1atWhWffPJJhWoFqE4VH38VgGq15557xu67716qffr06RHxfbhZlrp160bE98NpJUkSF110UVx00UVlzjt37tzYeuut45NPPsmEo6vbYYcdylxuu+22K9WJadOmTUR8/wyoJk2aZJ7PfMMNN8TMmTOzhpXacsstM//+8MMPY4cddijX0GnDhw+Pb7/9Np588skyn+NVlpLj8c0335Tq0K5JyfOkV9egQYOsZ6lNnz49Fi5cGI0aNSpzHSXPNSt5btzOO++8zu3OnDkzTjjhhDj66KPjuuuuK1eta7N6+B0RUa9evYiIaN68eZntP3xWXI0aNWLbbbfNalv9fY6I+OSTT6JZs2alOnI77rhjZvrqWrVqVdHdWKNddtklunbtusbpa9rWXXfdFSNGjIhp06ZlDQG4+vwffvhhNGvWLKuzvT5KjsMPz6latWrFtttuW+o4bbPNNqXOsQYNGsRbb71Vru1tv/32pdratGkTS5YsiXnz5kWTJk2ibdu2sccee8S4cePipJNOiojvh1H/xS9+Edttt91a11+3bt345ptvylVLiR9+HiNKn1fl/d5Yk/KcuwAAVD/93dL0d9csNzd3rX2/1d1+++3RunXrmD59erz88stZAeH06dMjSZIy+0sREXl5eRFRsf2qbJ988knUqFGjVJ+sSZMmUb9+/VJ9x/L0s9Zmm222+VH96n/9618xZMiQmDRpUqlndi9cuDDq1atX6cdxTf3qiO9/gxg/fnwsXrw4M2x+ROnj06BBg4j4/vePkvNnTZo1a5a1rojs74Jf/OIXUaNGjfjNb34TN954YyxZsiQ222yzGDduXBQUFMTRRx+91vVX1fkb8f3jCi677LKYMmVK1tD45b0gYW3HDWBjIRgHSImSZyjffffdWXfHlijpcJfMd84555R5dW9ErDP8Wh9XXHFFXHTRRfHb3/42Lr300thiiy2iRo0aMXDgwFLPsi6v7t27x1NPPRVXXnll7LfffqWuOC5L27ZtIyLi7bffzrqKem1yc3PXOU9xcXE0atSozGc0R0Spjkp5NG3aNJo2bRpPPPFEvP7662X+UFQRa9qPNbUnSbJe2yuP1X+UqI5t3XPPPdG3b9844ogj4txzz41GjRpFbm5uDB8+PNNp/ynYUO9R796946yzzor//ve/sXz58njllVfi+uuvX+dybdu2jSlTpsSKFSsyd+CvS3n2aX2/N8pz7gIA8NOlv6u/u76ef/75TBD49ttvZ42iVlxcHDk5OfHkk0+WeRzq1KlToW3l5OSU2Uf74fOmf4zyBphV3Xcsq1/94YcfxgEHHBBt27aNkSNHRvPmzaNWrVrxxBNPxDXXXPOjz4GqsCH61r17946rrroqHnnkkTjuuOPi3nvvjUMPPTRzE8KaVNX5+89//jMOO+yw+OUvfxk33HBDNG3aNPLy8mLMmDFx7733rtd2NsTvRgCVRTAOkBKtW7eOiIhGjRqt9arekjt98/Ly1nlldYsWLTJX5q/u/fffL3P+kqvzV++offDBBxER0bJly4iIeOCBB6JLly5x++23Zy27YMGC2GqrrbL259VXX43vvvsuc3X2mvziF7+IU089NQ499NA4+uij4+GHH17nlfc9e/aM4cOHxz333FPujkZ5tG7dOiZMmBCdOnVaa9hb8n6988476/xhpqCgIB577LHYf//9o0ePHvHCCy/ETjvtVGk1V1RxcXF89NFHmSuiI0q/zy1atIgJEybEN998k3XX+LRp0zLT16Wqh6Jb3QMPPBDbbrttPPTQQ1nb/eGQ6a1bt47x48fH/Pnz13rXeHlrLzkO77//ftZd+CtWrIiZM2eW++6H8irrfP7ggw9is802y/oR69hjj43BgwfHX//611i6dGnk5eXFMcccs8719+zZMyZNmhQPPvhgHHfccZVWd3m/NwAASCf9Xf3d9TFr1qw444wzolu3blGrVq3MhRMl/bHWrVtHkiTRqlWrrH7uD62+X2v7fDVo0KDMIct/eFd3Rfq8LVq0iOLi4pg+fXpmJLaIiDlz5sSCBQvK1ceuao8++mgsX748/vGPf2TdWVwyFH2J8n4+fky/+oemTZsWW221Vak7vNfHF198UeoO9B9+F0R8f0d8hw4dYty4cbHNNtvEp59+Wq5REarq/H3wwQejoKAgxo8fH/n5+Zn2MWPGVNo2ADYGnjEOkBLdu3ePunXrxhVXXJE1DHSJefPmRcT3PyTst99+cfPNN8esWbPWOF9ExMEHHxyvvPJKvPbaa1nT13R1+BdffBEPP/xw5vWiRYti7Nix0b59+8xV/bm5uaWuJP373/+eec5biSOPPDK+/PLLMu9SLetK1K5du8Z9990XTz31VJx44onrvBK5Y8eO0aNHj7jtttvikUceKTV9xYoVcc4556x1HWX59a9/HatWrYpLL7201LSVK1fGggULIiKiW7dusfnmm8fw4cNj2bJlWfOVtX/16tWL8ePHR6NGjeLAAw+s9ruYV39fkiSJ66+/PvLy8uKAAw6IiO8/O6tWrSr1/l1zzTWRk5MTBx100Dq3sdlmm0VEZI5ZVSq56nn1Y//qq6/GpEmTsuY78sgjI0mSGDZsWKl1rL5sYWFhueru2rVr1KpVK/7nf/4na/nbb789Fi5cGIccckhFd2WtJk2alPXssM8++yz+93//N7p165Z15fdWW20VBx10UNxzzz0xbty46NGjR7kC6FNPPTWaNm0aZ599duaHgdXNnTs3LrvssgrXXd7vDQAA0kl/V393fZx88slRXFwct99+e9xyyy1Rs2bNOOmkkzK1/OpXv4rc3NwYNmxYqfqSJImvvvoqIiJ+/vOfR6tWrWLUqFGl+nurL9e6deuYNm1a1udt6tSp8a9//StrmYr0eQ8++OCIiBg1alRW+8iRIyMiKr3v+GOU1a9euHBhqeC1vJ+PwsLCzHPJ16Zp06bRvn37uOuuu7KO5TvvvBNPP/105thVlpUrV8bNN9+ceb1ixYq4+eabo2HDhlFUVJQ174knnhhPP/10jBo1Krbccsty/RZSVedvbm5u5OTkZI1c8PHHH5e5DYA0c8c4QErUrVs3brzxxjjxxBPj5z//eRx77LHRsGHD+PTTT+Pxxx+PTp06ZTrdo0ePjn322Sd22WWXOPnkk2PbbbeNOXPmxKRJk+K///1vTJ06NSIi/vCHP8Tdd98dPXr0iLPOOisKCwvjlltuiRYtWpT5TOM2bdrESSedFP/+97+jcePGcccdd8ScOXOyOkGHHnpoXHLJJdGvX7/Ye++94+23345x48aVemZ17969Y+zYsTF48OB47bXXYt99943FixfHhAkT4ve//30cfvjhpbZ/xBFHxJgxY6J3795Rt27drI5KWcaOHRvdunWLX/3qV9GzZ8844IADorCwMKZPnx733XdfzJo1K66++uoKvQ+dO3eO/v37x/Dhw2PKlCnRrVu3yMvLi+nTp8ff//73uPbaa+Ooo46KunXrxjXXXBO/+93vYo899ojjjz8+GjRoEFOnTo0lS5bEXXfdVWrdW221VTzzzDOxzz77RNeuXeOll16KrbfeukL1VYaCgoJ46qmnok+fPrHXXnvFk08+GY8//nhccMEFmbuOe/bsGV26dIkLL7wwPv7449htt93i6aefjv/93/+NgQMHZq4QX5vatWtHu3bt4v777482bdrEFltsETvvvHOVPM/t0EMPjYceeih69eoVhxxySMycOTNuuummaNeuXXz77beZ+bp06RInnnhi/M///E9Mnz49evToEcXFxfHPf/4zunTpEgMGDIiIiKKiopgwYUKMHDkymjVrFq1atSrz+YUNGzaM888/P4YNGxY9evSIww47LN5///244YYbYo899ogTTjihUvdz5513ju7du8eZZ54Z+fn5ccMNN0RElBn09+7dO4466qiIiDJ/+CpLgwYN4uGHH46DDz442rdvHyeccELmh4E33ngj/vrXv2YNWVhe5f3eAAAgnfR39XfLsnLlyrjnnnvKnNarV68oLCyMMWPGxOOPPx533nlnbLPNNhERcd1118UJJ5wQN954Y/z+97+P1q1bx2WXXRbnn39+fPzxx3HEEUfE5ptvHjNnzoyHH344TjnllDjnnHOiRo0aceONN0bPnj2jffv20a9fv2jatGlMmzYt3n333Rg/fnxERPz2t7+NkSNHRvfu3eOkk06KuXPnxk033RQ77bRTLFq0KFNjRfq8u+22W/Tp0yduueWWWLBgQXTu3Dlee+21uOuuu+KII46ILl26lPt9rCold+T37Nkz+vfvH99++23ceuut0ahRo6wLVcr7+SgqKor7778/Bg8eHHvssUfUqVMnevbsWea2r7rqqjjooIOiY8eOcdJJJ8XSpUvjuuuui3r16sXQoUMrdT+bNWsWf/nLX+Ljjz+ONm3axP333x9TpkyJW265pdQIEMcff3z84Q9/iIcffjhOO+20dY4QUaIqzt9DDjkkRo4cGT169Ijjjz8+5s6dG6NHj47tttuuzO88gNRKANgojBkzJomI5N///vda55s4cWLSvXv3pF69eklBQUHSunXrpG/fvsnrr7+eNd+HH36Y9O7dO2nSpEmSl5eXbL311smhhx6aPPDAA1nzvfXWW0nnzp2TgoKCZOutt04uvfTS5Pbbb08iIpk5c2ZmvhYtWiSHHHJIMn78+GTXXXdN8vPzk7Zt2yZ///vfs9a3bNmy5Oyzz06aNm2a1K5dO+nUqVMyadKkpHPnzknnzp2z5l2yZEly4YUXJq1atUry8vKSJk2aJEcddVTy4YcfJkmSJDNnzkwiIrnqqquylrvhhhuSiEjOOeecdR7XJUuWJFdffXWyxx57JHXq1Elq1aqVbL/99skZZ5yRzJgxIzNfnz59ksLCwlLLDxkyJCnrv9NbbrklKSoqSmrXrp1svvnmyS677JL84Q9/SL744ous+f7xj38ke++9d1K7du2kbt26yZ577pn89a9/zUzv3LlzstNOO2UtM2PGjKRp06bJjjvumMybN2+N+1ZWzSXv0w9FRHL66adntZV1fEvW+eGHHybdunVLNttss6Rx48bJkCFDklWr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48dn1KhRWbp0adasWZORI0fu81qaWE8BOHz0yX+gTwagMdAj/4G1FA6cYByOErfffnv+/u//PsuXL6833q1btyxbtqze2LJly3LyySenSZMm+3ydzZs357XXXsvNN9+cCy+8MN26dct77723z+c55ZRT8tJLL9Ub++z28OHD8+yzz+aVV17Z5/Mnf/jcuNra2tx99905++yzc/LJJ+f3v//9TvM+/fTTrFq1qm77tddey5YtW9KtW7f9ui4A7MnerlG706pVq1RVVdVbP2tqavLyyy/Xbbdt2zaDBg3Kvffem23btu1XrcuWLcs555yT0aNHp3fv3uncuXPeeOONneb927/9Wz7++OO67RdffDEtWrRI+/bt9+u6AHCg9Mk70ycDcCTSIwMHS9OGLgA4PE4//fRcdtllmT17dr3xH/zgBznzzDNz6623ZtiwYVm+fHnuvffe3Hfffbs9X01NTdasWVNvrFmzZjnllFPStm3b3H///TnxxBOzYcOG/PVf//U+13vttdfmvPPOy6xZszJkyJA8//zzefrpp+v+Yj5JbrjhhvziF7/IhRdemClTpuRrX/tajj/++PzmN7/J008/vcdfWHTu3DmffPJJ5syZkyFDhmTZsmWZP3/+TvOOOeaYXHvttZk9e3aaNm2aMWPG5Oyzz96rV9YBwJ68//77O62pJ5xwwl6tUXty7bXXZsaMGencuXO6du2aOXPm5L333qu3nt53330ZMGBA+vbtm6lTp6ZHjx4pLy/PSy+9lHXr1u3xtXJdunTJT37ykzzzzDPp2LFjHn744bz00kvp2LFjvXk7duzIqFGjcvPNN2f9+vWZMmVKxowZk/Jyf6sLQMPQJ+9MnwxAQ9Mj65HhUPIvDI4it9xyy06vWjnjjDPy2GOPZdGiRenevXsmT56cW265JSNGjNjtuT788MP07t273teQIUNSXl6eRYsWZfXq1enevXtuuOGG3Hnnnftc64ABAzJ//vzMmjUrPXv2zJIlS3LDDTekoqKibk5FRUWqq6szceLEPPTQQzn33HPTrVu3jB07NgMGDMjixYt3e42ePXtm1qxZmTlzZrp3755HHnkkM2bM2Gle8+bNM3HixFx66aUZMGBAWrRokUcffXSf7wkAduWFF17YaU19+OGH92qN2pOJEydm+PDhufzyy9O/f/+0aNEigwcPrreefvWrX80rr7ySgQMHZtKkSenZs2f69u2bOXPmZPz48bn11lt3e43vf//7+e53v5thw4alX79+2bx5c0aPHr3TvAsvvDBdunTJeeedl2HDhuXb3/52pk6dus/3BAAHkz65Pn0yAA1NjwwcSmWlffmABYAGdNVVV2XdunX553/+58N2zQULFmTs2LHZsmXLYbsmABwqtbW16datWy6++OI9NvMH04gRI7Jly5Y9/jIeANg3+mQA2H96ZDj6eJU6cMS66667MmjQoFRWVubpp5/OwoUL9/jqOgDg//3Xf/1Xli5dmvPPPz/bt2/Pvffem9/97ne59NJLG7o0AGA/6JMBYP/pkQHBOHDEWrlyZe6444588MEH6dSpU2bPnp2/+qu/auiyAKDRKC8vz4IFCzJ+/PiUSqV07949zz33XLp169bQpQEA+0GfDAD7T48MeJU6AAAAAAAAAIVW3tAFAAAAAAAAAMChJBgHAAAAAAAAoNAE4wAAAAAAAAAUmmAcAAAAAAAAgEITjAMAAAAAAABQaIJxAGCflJWVZfHixQd0jhEjRmTo0KEHpR4AAABoKHpkAGg8BOMAQD0bN27Mtddem06dOqVZs2Zp3759hgwZkurq6oN2jXvuuScLFiw4aOcDAACAQ0GPDADF0bShCwAAjhzr16/PgAED0rp169x55505/fTT88knn+SZZ57JNddck3Xr1h2U67Rq1eqgnAcAAAAOFT0yABSLJ8YBgDqjR49OWVlZVq5cme9973s5+eSTc9ppp2XcuHF58cUX6+a98847ueiii9K8efN06dIlf/d3f1e3r6amJqNGjUrHjh1z3HHH5ZRTTsk999xT7zqffU3cBRdckOuuuy4TJkxImzZt0q5du0ydOvVQ3y4AAAB8Lj0yABSLYBwASJK8++67WbJkSa655ppUVlbutL9169Z130+bNi0XX3xx/v3f/z1//ud/nssuuyzvvvtukqS2tjZf/vKX8/jjj+fXv/51Jk+enJtuuimPPfbYbq+/cOHCVFZWZsWKFbnjjjtyyy235Nlnnz2o9wgAAAB7Q48MAMUjGAcAkiS//e1vUyqV0rVr1z3OHTFiRIYPH57OnTtn+vTp+fDDD7Ny5cokyTHHHJNp06alb9++6dixYy677LKMHDlyj01/jx49MmXKlHTp0iWXX355+vbte1A/sw0AAAD2lh4ZAIrHZ4wDAEmSUqm013N79OhR931lZWVatmyZt956q25s7ty5efDBB7Nhw4Z8/PHH2bFjR3r16rXX50ySE088sd45AQAA4HDRIwNA8XhiHABIknTp0iVlZWVZt27dHucec8wx9bbLyspSW1ubJFm0aFHGjx+fUaNGZenSpVmzZk1GjhyZHTt27Pc5AQAA4HDSIwNA8QjGAYAkSZs2bTJ48ODMnTs327Zt22n/li1b9uo8y5YtyznnnJPRo0end+/e6dy5c954442DXC0AAAAcOnpkACgewTgAUGfu3LmpqanJWWedlSeeeCKvv/561q5dm9mzZ6d///57dY4uXbpk1apVeeaZZ/Kb3/wmP/zhD/PSSy8d4soBAADg4NIjA0CxCMYBgDqdOnXKyy+/nK9//ev5wQ9+kO7du2fQoEGprq7OvHnz9uoc3//+9/Pd7343w4YNS79+/bJ58+aMHj36EFcOAAAAB5ceGQCKpaxUKpUauggAAAAAAAAAOFQ8MQ4AAAAAAABAoQnGAQAAAAAAACg0wTgAAAAAAAAAhSYYBwAAAAAAAKDQBOMAAAAAAAAAFJpgHAAAAAAAAIBCE4wDAAAAAAAAUGiCcQAAAAAAAAAKTTAOAAAAAAAAQKEJxgEAAAAAAAAoNME4AAAAAAAAAIX2fzQA0BrNzT3TAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "\n", - "\n", - "def plt_combined_bar_graph(df, fraction_fields, error_fields, titles, ylabels):\n", - " \"\"\"\n", - " Plot bar graphs with error bars for specified fields in the provided DataFrame as subplots.\n", - "\n", - " Args:\n", - " df (pd.DataFrame): The DataFrame containing the data to be plotted.\n", - " fraction_fields (list[str]): List of column names in the DataFrame to be plotted on the y-axis for fractions.\n", - " error_fields (list[str]): List of column names in the DataFrame to be plotted for standard errors.\n", - " titles (list[str]): Titles of the plots.\n", - " ylabels (list[str]): Labels for the y-axis.\n", - "\n", - " This function does not return any value but displays the bar graph.\n", - " \"\"\"\n", - " n = len(fraction_fields) # Number of plots to create\n", - " fig, axs = plt.subplots(1, n, figsize=(10 * n, 9), sharey=True)\n", - "\n", - " for i, (fraction_field, error_field, title, ylabel) in enumerate(\n", - " zip(fraction_fields, error_fields, titles, ylabels)\n", - " ):\n", - " barplot = sns.barplot(\n", - " x=\"chain\",\n", - " y=fraction_field,\n", - " data=df.sort_values(\n", - " \"chain\", ascending=False\n", - " ), # Sort the DataFrame to reverse the order\n", - " ax=axs[i],\n", - " capsize=0.1,\n", - " errorbar=None,\n", - " )\n", - "\n", - " # Add error bars manually\n", - " for j, bar in enumerate(barplot.patches):\n", - " # Get the error for the current bar\n", - " error = df.sort_values(\"chain\", ascending=False)[error_field].iloc[j]\n", - " # Add error bars to each bar\n", - " axs[i].errorbar(\n", - " x=bar.get_x() + bar.get_width() / 2,\n", - " y=bar.get_height(),\n", - " yerr=error,\n", - " fmt=\"none\",\n", - " capsize=5,\n", - " color=\"black\",\n", - " )\n", - "\n", - " axs[i].set_title(title)\n", - " axs[i].set_xlabel(\"Chain\")\n", - " axs[i].set_ylabel(ylabel)\n", - "\n", - " plt.tight_layout()\n", - " plt.show()\n", - "\n", - "\n", - "# Define the columns and labels for the plots\n", - "fraction_fields = [\"Fraction Imports Correct\", \"Fraction Execution Correct\"]\n", - "error_fields = [\"Imports Correct Std Error\", \"Execution Correct Std Error\"]\n", - "\n", - "titles = [\n", - " \"Feedback Check Import Fraction by Chain\",\n", - " \"Feedback Check Execution Fraction by Chain\",\n", - "]\n", - "ylabels = [\"Fraction Correct\", \"Fraction Correct\"]\n", - "\n", - "# Call the function with the specified arguments\n", - "plt_combined_bar_graph(\n", - " correct_frac_and_errors, fraction_fields, error_fields, titles, ylabels\n", ")" ] }, { "cell_type": "code", "execution_count": null, - "id": "50c26dac-6825-4001-9cb5-4a4691a9685d", + "id": "71d90f9e-9dad-410c-a709-093d275029ae", + "metadata": {}, + "outputs": [], + "source": [ + "# Run with langgraph\n", + "experiment_results = evaluate(\n", + " predict_langgraph,\n", + " data=dataset_name,\n", + " evaluators=code_evalulator,\n", + " experiment_prefix=f\"test-with-langgraph-{expt_llm}-{flag}\",\n", + " max_concurrency=10,\n", + " metadata={\n", + " \"llm\": expt_llm,\n", + " \"feedback\": flag,\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d69da747-b4ea-455d-9314-60c3d9d30549", + "metadata": {}, + "source": [ + "Results:\n", + "\n", + "LangGraph w/o reflection performs the best by a wide margin.\n", + "\n", + "Reflection may confuse the re-try, and needs prompt engineering.\n", + "\n", + "https://smith.langchain.com/public/78a3d858-c811-4e46-91cb-0f10ef56260b/d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96f85440-3622-4ae3-ae6f-7c0613466ffb", "metadata": {}, "outputs": [], "source": []