diff --git a/01_helpers.ipynb b/01_helpers.ipynb index 5eb2c8e..7cb91b4 100644 --- a/01_helpers.ipynb +++ b/01_helpers.ipynb @@ -405,7 +405,8 @@ "metadata": {}, "outputs": [], "source": [ - "def add_nls(s):\n", + "#| exports\n", + "def _add_nls(s):\n", " \"Add newlines to start and end of `s` if missing\"\n", " if s[ 0]!='\\n': s = '\\n'+s\n", " if s[-1]!='\\n': s = s+'\\n'\n", @@ -433,7 +434,7 @@ " ) -> namedtuple:\n", " \"Create a `doctype` named tuple\"\n", " if source is None: source = hashlib.md5(content.encode()).hexdigest()[:8]\n", - " return doctype(add_nls(str(source).strip()), add_nls(content.strip()))" + " return doctype(_add_nls(str(source).strip()), _add_nls(content.strip()))" ] }, { @@ -473,6 +474,7 @@ "metadata": {}, "outputs": [], "source": [ + "#| exports\n", "def mk_doc(index:int, # The document index\n", " content:str, # The document content\n", " source:Optional[str]=None # URL, filename, etc; defaults to `md5(content)` if not provided\n", @@ -524,6 +526,7 @@ "metadata": {}, "outputs": [], "source": [ + "#| exports\n", "def docs_xml(docs:list[str], # The content of each document\n", " sources:Optional[list]=None, # URLs, filenames, etc; each one defaults to `md5(content)` if not provided\n", " prefix:bool=True # Include Anthropic's suggested prose intro?\n", @@ -621,6 +624,7 @@ "metadata": {}, "outputs": [], "source": [ + "#| exports\n", "def files2ctx(\n", " fnames:list[Union[str,Path]], # List of file names to add to context\n", " prefix:bool=True # Include Anthropic's suggested prose intro?\n", @@ -705,6 +709,7 @@ "metadata": {}, "outputs": [], "source": [ + "#| exports\n", "@delegates(globtastic)\n", "def folder2ctx(\n", " folder:Union[str,Path], # Folder name containing files to add to context\n", diff --git a/02_toolloop.ipynb b/02_toolloop.ipynb index 1b001a9..d470871 100644 --- a/02_toolloop.ipynb +++ b/02_toolloop.ipynb @@ -32,7 +32,7 @@ }, { "cell_type": "markdown", - "id": "57b2cde6", + "id": "63111054", "metadata": {}, "source": [ "Anthropic provides an [interesting example](https://github.com/anthropics/anthropic-cookbook/blob/main/tool_use/customer_service_agent.ipynb) of using tools to mock up a hypothetical ordering system. We're going to take it a step further, and show how we can dramatically simplify the process, whilst completing more complex tasks.\n", @@ -62,7 +62,7 @@ }, { "cell_type": "markdown", - "id": "56b6c3df", + "id": "2d8eed7b", "metadata": {}, "source": [ "We can now define the same functions from the original example -- but note that we don't need to manually create the large JSON schema, since Claudette handles all that for us automatically from the functions directly. We'll add some extra functionality to update order details when cancelling too." @@ -71,7 +71,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f02a8fb7", + "id": "2778dde0", "metadata": {}, "outputs": [], "source": [ @@ -101,7 +101,7 @@ }, { "cell_type": "markdown", - "id": "3593869d", + "id": "a1278535", "metadata": {}, "source": [ "We're now ready to start our chat." @@ -110,7 +110,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9032540c", + "id": "a4231dff", "metadata": {}, "outputs": [], "source": [ @@ -120,7 +120,7 @@ }, { "cell_type": "markdown", - "id": "e166ad45", + "id": "41db970a", "metadata": {}, "source": [ "We'll start with the same request as Anthropic showed:" @@ -129,7 +129,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d4907bf6", + "id": "97cdbf58", "metadata": {}, "outputs": [ { @@ -142,7 +142,7 @@ { "data": { "text/plain": [ - "[ToolUseBlock(id='toolu_01GaTymQyJjjtZuH5QaTUpYV', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')]" + "[ToolUseBlock(id='toolu_01UTF8GiVV5BgdiHZFazp1p6', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')]" ] }, "execution_count": null, @@ -158,7 +158,7 @@ }, { "cell_type": "markdown", - "id": "42874d6a", + "id": "3f6d3ae8", "metadata": {}, "source": [ "Claude asks us to use a tool. Claudette handles that automatically by just passing back the message:" @@ -167,7 +167,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a873f6e2", + "id": "09196e94", "metadata": {}, "outputs": [ { @@ -195,7 +195,7 @@ }, { "cell_type": "markdown", - "id": "e1b7be56", + "id": "26fe5edb", "metadata": {}, "source": [ "Let's consider a more complex case than in the original example -- what happens if a customer wants to cancel all of their orders?" @@ -204,7 +204,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f2fb1012", + "id": "6aa531d0", "metadata": {}, "outputs": [ { @@ -217,8 +217,8 @@ { "data": { "text/plain": [ - "[TextBlock(text=\"Okay, let's cancel all orders for customer C1.\", type='text'),\n", - " ToolUseBlock(id='toolu_018HSZGj5THYQB7Vzmk5XadR', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')]" + "[TextBlock(text=\"Okay, let's cancel all orders for customer C1:\", type='text'),\n", + " ToolUseBlock(id='toolu_01DXvWH5LstcutuCeHjK6vjq', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')]" ] }, "execution_count": null, @@ -235,7 +235,7 @@ }, { "cell_type": "markdown", - "id": "8175a1e6", + "id": "5a2432a1", "metadata": {}, "source": [ "This is the start of a multi-stage tool use process. Doing it manually step by step is inconvenient, so let's write a function to handle this for us:" @@ -244,7 +244,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3fed3e1e", + "id": "3457eeb6", "metadata": {}, "outputs": [], "source": [ @@ -260,18 +260,17 @@ " **kw):\n", " \"Add prompt `pr` to dialog and get a response from Claude, automatically following up with `tool_use` messages\"\n", " r = self(pr, temp=temp, maxtok=maxtok, stop=stop, **kw)\n", - " i=0\n", - " while r.stop_reason=='tool_use' and i\n", "\n", - "- id: msg_018ZS1oLcBnqXjnCjcCWcvWX\n", + "- id: msg_01XMz1SxEoXQkzspHg7F5A9g\n", "- content: [{'text': 'The email address for customer C1 is john@example.com.', 'type': 'text'}]\n", "- model: claude-3-haiku-20240307\n", "- role: assistant\n", @@ -309,7 +308,7 @@ "" ], "text/plain": [ - "ToolsBetaMessage(id='msg_018ZS1oLcBnqXjnCjcCWcvWX', content=[TextBlock(text='The email address for customer C1 is john@example.com.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 732; Out: 19; Total: 751)" + "ToolsBetaMessage(id='msg_01XMz1SxEoXQkzspHg7F5A9g', content=[TextBlock(text='The email address for customer C1 is john@example.com.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 732; Out: 19; Total: 751)" ] }, "execution_count": null, @@ -325,7 +324,7 @@ }, { "cell_type": "markdown", - "id": "31f4a57b", + "id": "db6131f0", "metadata": {}, "source": [ "Let's see if it can handle the multi-stage process now -- we'll add `show_trace` to see each stage of the process:" @@ -334,41 +333,42 @@ { "cell_type": "code", "execution_count": null, - "id": "da5c99b0", + "id": "a45d3eb0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ToolsBetaMessage(id='msg_01DTMHbL3ieoESGypnpJXwWK', content=[TextBlock(text=\"Okay, let's cancel all orders for customer C1.\", type='text'), ToolUseBlock(id='toolu_01Ewn88rpxxgTmeo2NdpLB2j', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 537; Out: 72; Total: 609)\n", + "ToolsBetaMessage(id='msg_016Lt1Xicy86KuYLEsircSuL', content=[TextBlock(text=\"Okay, let's cancel all orders for customer C1:\", type='text'), ToolUseBlock(id='toolu_019kvnDqgyy1VpMTuEeRkVww', input={'customer_id': 'C1'}, name='get_customer_info', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 537; Out: 72; Total: 609)\n", "- Retrieving customer C1\n", - "ToolsBetaMessage(id='msg_018Bwa5Kmtr1pi72qp9cDJoP', content=[TextBlock(text=\"Based on the customer information, it looks like there are 2 orders for customer C1:\\n- Order O1 for Widget A (status: Shipped)\\n- Order O2 for Gadget B (status: Processing)\\n\\nLet's cancel both of these orders:\", type='text'), ToolUseBlock(id='toolu_01V117om2ibdDr9EUSR4eBJq', input={'order_id': 'O1'}, name='cancel_order', type='tool_use'), ToolUseBlock(id='toolu_018XmD9KZkNSmFEQijyoLkjX', input={'order_id': 'O2'}, name='cancel_order', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 745; Out: 157; Total: 902)\n", + "ToolsBetaMessage(id='msg_01PKHb5CtdNhvpLSfz3T4FoB', content=[TextBlock(text=\"Based on the customer information, it looks like there are 2 orders for customer C1:\\n- Order O1 for Widget A\\n- Order O2 for Gadget B\\n\\nLet's cancel both of these orders:\", type='text'), ToolUseBlock(id='toolu_01YUCv13QLFqDgKt3uJZxS17', input={'order_id': 'O1'}, name='cancel_order', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 745; Out: 107; Total: 852)\n", "- Cancelling order O1\n", + "ToolsBetaMessage(id='msg_01T222bARXHYP2YeZc6TXkbq', content=[ToolUseBlock(id='toolu_01BBy11iX2SfHKqvUctYgWs3', input={'order_id': 'O2'}, name='cancel_order', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=In: 864; Out: 57; Total: 921)\n", "- Cancelling order O2\n", - "ToolsBetaMessage(id='msg_018NSi4aB2jn1cPnQrZ8T5Pk', content=[TextBlock(text='The cancellation of both orders was successful. I have now cancelled all orders for customer C1.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 966; Out: 24; Total: 990)\n" + "ToolsBetaMessage(id='msg_018UXRiRM2KkjmSQmkpkBv9f', content=[TextBlock(text='Both order cancellations were successful. I have now cancelled all orders for customer C1.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 933; Out: 23; Total: 956)\n" ] }, { "data": { "text/markdown": [ - "The cancellation of both orders was successful. I have now cancelled all orders for customer C1.\n", + "Both order cancellations were successful. I have now cancelled all orders for customer C1.\n", "\n", "
\n", "\n", - "- id: msg_018NSi4aB2jn1cPnQrZ8T5Pk\n", - "- content: [{'text': 'The cancellation of both orders was successful. I have now cancelled all orders for customer C1.', 'type': 'text'}]\n", + "- id: msg_018UXRiRM2KkjmSQmkpkBv9f\n", + "- content: [{'text': 'Both order cancellations were successful. I have now cancelled all orders for customer C1.', 'type': 'text'}]\n", "- model: claude-3-haiku-20240307\n", "- role: assistant\n", "- stop_reason: end_turn\n", "- stop_sequence: None\n", "- type: message\n", - "- usage: {'input_tokens': 966, 'output_tokens': 24}\n", + "- usage: {'input_tokens': 933, 'output_tokens': 23}\n", "\n", "
" ], "text/plain": [ - "ToolsBetaMessage(id='msg_018NSi4aB2jn1cPnQrZ8T5Pk', content=[TextBlock(text='The cancellation of both orders was successful. I have now cancelled all orders for customer C1.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 966; Out: 24; Total: 990)" + "ToolsBetaMessage(id='msg_018UXRiRM2KkjmSQmkpkBv9f', content=[TextBlock(text='Both order cancellations were successful. I have now cancelled all orders for customer C1.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 933; Out: 23; Total: 956)" ] }, "execution_count": null, @@ -384,7 +384,7 @@ }, { "cell_type": "markdown", - "id": "46e1f121", + "id": "a7a8c9fc", "metadata": {}, "source": [ "OK Claude thinks the orders were cancelled -- let's check one:" @@ -393,7 +393,7 @@ { "cell_type": "code", "execution_count": null, - "id": "650c2650", + "id": "592e9084", "metadata": {}, "outputs": [ { @@ -406,23 +406,23 @@ { "data": { "text/markdown": [ - "The status of order O2 is now 'Cancelled'. This matches the result we got when we successfully cancelled the order earlier.\n", + "The status of order O2 is now 'Cancelled' since I successfully cancelled that order earlier.\n", "\n", "
\n", "\n", - "- id: msg_01VQikjVpyUK4D4CUqVeKbzt\n", - "- content: [{'text': \"The status of order O2 is now 'Cancelled'. This matches the result we got when we successfully cancelled the order earlier.\", 'type': 'text'}]\n", + "- id: msg_015LKdAkYwJTYGabfJHJBE1t\n", + "- content: [{'text': \"The status of order O2 is now 'Cancelled' since I successfully cancelled that order earlier.\", 'type': 'text'}]\n", "- model: claude-3-haiku-20240307\n", "- role: assistant\n", "- stop_reason: end_turn\n", "- stop_sequence: None\n", "- type: message\n", - "- usage: {'input_tokens': 1129, 'output_tokens': 32}\n", + "- usage: {'input_tokens': 1095, 'output_tokens': 26}\n", "\n", "
" ], "text/plain": [ - "ToolsBetaMessage(id='msg_01VQikjVpyUK4D4CUqVeKbzt', content=[TextBlock(text=\"The status of order O2 is now 'Cancelled'. This matches the result we got when we successfully cancelled the order earlier.\", type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 1129; Out: 32; Total: 1161)" + "ToolsBetaMessage(id='msg_015LKdAkYwJTYGabfJHJBE1t', content=[TextBlock(text=\"The status of order O2 is now 'Cancelled' since I successfully cancelled that order earlier.\", type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=In: 1095; Out: 26; Total: 1121)" ] }, "execution_count": null, @@ -458,7 +458,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8a134f44", + "id": "c627b799", "metadata": {}, "outputs": [], "source": [] diff --git a/claudette/_modidx.py b/claudette/_modidx.py index 89f7131..754e5d1 100644 --- a/claudette/_modidx.py +++ b/claudette/_modidx.py @@ -36,8 +36,13 @@ 'claudette.core.mk_toolres': ('core.html#mk_toolres', 'claudette/core.py'), 'claudette.core.text_msg': ('core.html#text_msg', 'claudette/core.py'), 'claudette.core.usage': ('core.html#usage', 'claudette/core.py')}, - 'claudette.helpers': { 'claudette.helpers.hl_md': ('helpers.html#hl_md', 'claudette/helpers.py'), + 'claudette.helpers': { 'claudette.helpers._add_nls': ('helpers.html#_add_nls', 'claudette/helpers.py'), + 'claudette.helpers.docs_xml': ('helpers.html#docs_xml', 'claudette/helpers.py'), + 'claudette.helpers.files2ctx': ('helpers.html#files2ctx', 'claudette/helpers.py'), + 'claudette.helpers.folder2ctx': ('helpers.html#folder2ctx', 'claudette/helpers.py'), + 'claudette.helpers.hl_md': ('helpers.html#hl_md', 'claudette/helpers.py'), 'claudette.helpers.json_to_xml': ('helpers.html#json_to_xml', 'claudette/helpers.py'), + 'claudette.helpers.mk_doc': ('helpers.html#mk_doc', 'claudette/helpers.py'), 'claudette.helpers.mk_doctype': ('helpers.html#mk_doctype', 'claudette/helpers.py'), 'claudette.helpers.to_xml': ('helpers.html#to_xml', 'claudette/helpers.py'), 'claudette.helpers.xt': ('helpers.html#xt', 'claudette/helpers.py')}, diff --git a/claudette/helpers.py b/claudette/helpers.py index eae44b3..d161576 100644 --- a/claudette/helpers.py +++ b/claudette/helpers.py @@ -1,7 +1,8 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../01_helpers.ipynb. # %% auto 0 -__all__ = ['g', 'tags', 'doctype', 'xt', 'hl_md', 'to_xml', 'json_to_xml', 'mk_doctype'] +__all__ = ['g', 'tags', 'doctype', 'xt', 'hl_md', 'to_xml', 'json_to_xml', 'mk_doctype', 'mk_doc', 'docs_xml', 'files2ctx', + 'folder2ctx'] # %% ../01_helpers.ipynb 3 import hashlib,xml.etree.ElementTree as ET @@ -65,10 +66,58 @@ def build_xml(data, parent): # %% ../01_helpers.ipynb 23 doctype = namedtuple('doctype', ['source', 'content']) +# %% ../01_helpers.ipynb 25 +def _add_nls(s): + "Add newlines to start and end of `s` if missing" + if s[ 0]!='\n': s = '\n'+s + if s[-1]!='\n': s = s+'\n' + return s + # %% ../01_helpers.ipynb 27 def mk_doctype(content:str, # The document content source:Optional[str]=None # URL, filename, etc; defaults to `md5(content)` if not provided ) -> namedtuple: "Create a `doctype` named tuple" if source is None: source = hashlib.md5(content.encode()).hexdigest()[:8] - return doctype(add_nls(str(source).strip()), add_nls(content.strip())) + return doctype(_add_nls(str(source).strip()), _add_nls(content.strip())) + +# %% ../01_helpers.ipynb 30 +def mk_doc(index:int, # The document index + content:str, # The document content + source:Optional[str]=None # URL, filename, etc; defaults to `md5(content)` if not provided + ) -> tuple: + "Create an `xt` format tuple for a single doc in Anthropic's recommended format" + dt = mk_doctype(content, source) + content = xt('document_content', dt.content) + source = xt('source', dt.source) + return xt('document', [source, content], index=index) + +# %% ../01_helpers.ipynb 33 +def docs_xml(docs:list[str], # The content of each document + sources:Optional[list]=None, # URLs, filenames, etc; each one defaults to `md5(content)` if not provided + prefix:bool=True # Include Anthropic's suggested prose intro? + )->str: + "Create an XML string containing `docs` in Anthropic's recommended format" + pre = 'Here are some documents for you to reference for your task:\n\n' if prefix else '' + if sources is None: sources = [None]*len(docs) + docs = [mk_doc(i+1, *o) for i,o in enumerate(zip(docs,sources))] + return pre + to_xml(xt('documents', docs)) + +# %% ../01_helpers.ipynb 40 +def files2ctx( + fnames:list[Union[str,Path]], # List of file names to add to context + prefix:bool=True # Include Anthropic's suggested prose intro? +)->str: # XML for Claude context + fnames = [Path(o) for o in fnames] + contents = [o.read_text() for o in fnames] + return docs_xml(contents, fnames, prefix=prefix) + +# %% ../01_helpers.ipynb 44 +@delegates(globtastic) +def folder2ctx( + folder:Union[str,Path], # Folder name containing files to add to context + prefix:bool=True, # Include Anthropic's suggested prose intro? + **kwargs # Passed to `globtastic` +)->str: # XML for Claude context + fnames = globtastic(folder, **kwargs) + return files2ctx(fnames, prefix=prefix) diff --git a/claudette/toolloop.py b/claudette/toolloop.py index c02f063..f529818 100644 --- a/claudette/toolloop.py +++ b/claudette/toolloop.py @@ -3,11 +3,11 @@ # %% auto 0 __all__ = [] -# %% ../02_toolloop.ipynb 3 +# %% ../02_toolloop.ipynb 2 from .core import * from fastcore.utils import * -# %% ../02_toolloop.ipynb 19 +# %% ../02_toolloop.ipynb 16 @patch def toolloop(self:Chat, pr, # Prompt to pass to Claude @@ -19,10 +19,9 @@ def toolloop(self:Chat, **kw): "Add prompt `pr` to dialog and get a response from Claude, automatically following up with `tool_use` messages" r = self(pr, temp=temp, maxtok=maxtok, stop=stop, **kw) - i=0 - while r.stop_reason=='tool_use' and i