Examples

Admin Authentication

There are two primary mechanisms for authentication within Cape: admin authentication, which provides full access to your account and user authentication, which only provides access to the answer endpoint.

There are two different ways to authenticate as an administrator, either through the cape.client.CapeClient.login() method:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')

Or you can authenticate using an admin token when creating the CapeClient object. This admin token can be retrieved through the Cape UI:

from cape.client import CapeClient

cc = CapeClient(admin_token='youradmintoken')

Answering Questions

Authentication

Requests to the answer endpoint require a “user token”, this enables developers to provide access to their Cape AI by embedding their user token within their application.

The user token for your AI can either be retrieved through the Cape UI or through a call to the API:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
print(cc.get_user_token())

This will output a user token specific to your account, for example:

08aerv08ajkdp

Please note that while it is safe to distribute your user token as part of your application you should not include your login credentials, as this provides full administration access to your account.

Answering A Question

With the user token retrieved in the previous step we can now make calls to the answer endpoint. In its simplest use-case this just requires us to pass the question and the user token to the cape.client.CapeClient.answer() method:

from cape.client import CapeClient

USER_TOKEN = '08aerv08ajkdp'

cc = CapeClient()
answers = cc.answer('How easy is this API to use?', USER_TOKEN)
print(answers)

This will output the following answer list:

[
    {
        'answerText': "Hopefully it's pretty easy",
        'answerContext: "Welcome to the Cape API 0.1. Hopefully it's pretty easy to use.",
        'confidence': 0.75,
        'sourceType': 'document',
        'sourceId': '358e1b77c9bcc353946dfe107d6b32ff',
        'answerTextStartOffset': 30,
        'answerTextEndOffset': 56,
        'answerContextStartOffset': 0,
        'answerContextEndOffset': 64
    }
]

By default cape.client.CapeClient.answer() will only fetch the answer with the highest confidence value, for details on fetching multiple results see the multiple answers section.

Each answer in the list contains the following properties:

Property Description
answerText The proposed answer to the question
answerContext The context surrounding the proposed answer to the question
confidence How confident the AI is that this is the correct answer
sourceType Whether this result came from a ‘document’or a ‘saved_reply’
sourceId The ID of the document or saved reply this answer was found in (depending on sourceType)
answerTextStartOffset The starting position of this answer in the document (if sourceType is ‘document’)
answerTextEndOffset The end position of this answer in the document (if sourceType is ‘document’)
answerContextStartOffset The starting position of this answer context in the document (if sourceType is ‘document’)
answerContextEndOffset The end position of this answer context in the document (if sourceType is ‘document’)

Multiple Answers

In some cases, such as when searching through a document or extracting information from multiple documents, it may be desirable to retrieve more than one answer. This can be done via the number_of_items and offset parameters. For example to retrieve the first 5 answers:

from cape.client import CapeClient

USER_TOKEN = '08aerv08ajkdp'

cc = CapeClient()
answers = cc.answer('When were people born?',
                    USER_TOKEN,
                    number_of_items=5)
print(answers)

Which will produce output like:

[
    {
        'answerText': "Sam was born in 1974",
        'answerContext: "did very good work. Sam was born in 1974 on the sunny island of",
        'confidence': 0.75,
        'sourceType': 'document',
        'sourceId': 'employee_info.txt',
        'answerTextStartOffset': 80,
        'answerTextEndOffset': 100,
        'answerContextStartOffset':40,
        'answerContextEndOffset':123
    },
    {
        'answerText': "James was born in 1982",
        'answerContext: "James was born in 1982 on the sunny island of",
        'confidence': 0.64,
        'sourceType': 'document',
        'sourceId': 'employee_info.txt',
        'answerTextStartOffset': 0,
        'answerTextEndOffset': 22,
        'answerContextStartOffset':0,
        'answerContextEndOffset':45
    },
    {
        'answerText': "Alice was born in 1973",
        'answerContext: "did very good work. Alice was born in 1973 on the sunny island of",
        'confidence': 0.61,
        'sourceType': 'document',
        'sourceId': 'employee_info.txt',
        'answerTextStartOffset': 220,
        'answerTextEndOffset': 242,
        'answerContextStartOffset':200,
        'answerContextEndOffset':265
    },
    {
        'answerText': "Bob was born in 1965",
        'answerContext: "did very good work. Bob was born in 1965 on the sunny island of",
        'confidence': 0.59,
        'sourceType': 'document',
        'sourceId': 'employee_info.txt',
        'answerTextStartOffset': 180,
        'answerTextEndOffset': 200,
        'answerContextStartOffset':140,
        'answerContextEndOffset':223
    },
    {
        'answerText': "Jill was born in 1986",
        'answerContext: "did very good work. Jill was born in 1986 on the sunny island of",
        'confidence': 0.57,
        'sourceType': 'document',
        'sourceId': 'employee_info.txt',
        'answerTextStartOffset': 480,
        'answerTextEndOffset': 501,
        'answerContextStartOffset':440,
        'answerContextEndOffset':524
    }
]

If we then wished to retrieve the next 5 answers we could run:

answers = cc.answer('When were people born?',
                    USER_TOKEN,
                    number_of_items=5,
                    offset=5)

Which will return a further 5 answers starting with the 6th one. This allows us to retrieve answers in batches, only fetching more when the user needs them.

Searching Specific Documents

If we wish to search within a specific document (e.g. the document the user is currently viewing in our application) or in a set of documents we can specify the document_ids when requesting an answer. For example:

from cape.client import CapeClient

USER_TOKEN = '08aerv08ajkdp'

cc = CapeClient()
answers = cc.answer('When was James born?',
                    USER_TOKEN,
                    document_ids = ['employee_info_2016.txt',
                                    'employee_info_2017.txt',
                                    'employee_info_2018.txt'])
print(answers)

If we’re explicitly searching through a document we may also wish to disable saved reply responses, this can be done with the source_type parameter:

answers = cc.answer('When was James born?',
                    USER_TOKEN,
                    document_ids = ['employee_info_2016.txt',
                                    'employee_info_2017.txt',
                                    'employee_info_2018.txt'],
                    source_type = 'document')

Managing Documents

Documents can be uploaded, updated and deleted using the client API. This functionality is only available to users with administrative access.

Creating Documents

There are two ways to create a new document, we can either provide the text contents of a document via the text parameter of the cape.client.CapeClient.add_document() method or we can upload a file via the file_path parameter.

To create a document using the text parameter:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
doc_id = cc.add_document("Document title",
                            "Hello and welcome to my document!")
print(doc_id)

If we don’t supply a document_id when calling cape.client.CapeClient.add_document() an ID will be automatically generated for us. Automatically generated IDs are created by taking the SHA256 hash of the document contents. So for this document the following ID will be produced:

356477322741dbf8d8f0375ecdc6ae03357641829ae7ccf10283af36c5508a9d

Alternatively we can upload a file:

from cape.client import CapeClient

# Create an example file
fh = open('/tmp/example_file.txt', 'w')
fh.write("Hello! This is an example file!")
fh.close()

cc = CapeClient()
cc.login('username', 'password')
doc_id = cc.add_document("Document title",
                            file_path="/tmp/example_file.txt",
                            document_id='my_document_id')
print(doc_id)

Because we supplied a document_id in this example the document ID we get returned will be what we requested:

my_document_id

As large file uploads could take a long time we may wish to provide the user with updates on the progress of our upload. To do this we can provide a callback function via the monitor_callback parameter which will provide us with frequent updates about the upload’s progress:

from cape.client import CapeClient

def upload_cb(monitor):
    print("%d/%d" % (monitor.bytes_read, monitor.len))

# Create a large example file
fh = open('/tmp/large_example.txt', 'w')
fh.write("Hello! This is a large example file! " * 100000)
fh.close()

cc = CapeClient()
cc.login('username', 'password')
doc_id = cc.add_document("Document title",
                            file_path="/tmp/large_example.txt",
                            monitor_callback=upload_cb)

This will then print a series of status updates showing the progress of our file upload:

...
2523136/3700494
2531328/3700494
2539520/3700494
2547712/3700494
2555904/3700494
2564096/3700494
2572288/3700494
2580480/3700494
...

Updating Documents

To update a document we simply upload a new document with the same document_id and set the replace parameter to True. Without explicitly informing the server that we wish to replace the document it will report an error to avoid accidental replacement of documents. For example:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')

# Create the original document
doc_id = cc.add_document("My document",
                         "This is a good document.")

# Replace it with an improved version
cc.add_document("My document",
                "This is a great document.",
                document_id=doc_id,
                replace=True)

Deleting Documents

To delete a document simply call the cape.client.CapeClient.delete_document() method with the ID of the document you wish to remove:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')

cc.delete_document('my_bad_document')

Retrieving Documents

The cape.client.CapeClient.get_documents() method can be used to retrieve all previously uploaded documents:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')

documents = cc.get_documents()
print(documents)

This will output:

{
    'totalItems': 2,
    'items': [
        {
            'id': 'custom_id_2',
            'title': 'document2.txt',
            'origin': 'document2.txt',
            'text': 'This is another document.',
            'created': 1508169352
        },
        {
           'id': '358e1b77c9bcc353946dfe107d6b32ff',
            'title': 'cape_api.txt',
            'origin': 'cape_api.txt',
            'text': "Welcome to the Cape API 0.1. " \
                    "Hopefully it's pretty easy to use.",
            'created': 1508161723
        }
    ]
}

By default this will retrieve 30 documents at a time. The number_of_items and offset parameters can be used to control the size of the batches and retrieve multiple batches of documents (similar to the mechanism described in the multiple answers section). The response also includes the totalItems property which tells us the total number of items available (beyond those retrieved in this specific batch).

Each document in the list contains the following properties:

Property Description
id The ID of this document
title The document’s title (specified at upload)
origin Where this document originally came from
text The contents of the document
type Whether this document was created by submitting text or from a file upload
created Timestamp of when this document was first uploaded

Managing Saved Replies

Saved replies are made up of a canonical question and the response it should produce. In addition to the canonical question a saved reply may have many paraphrased questions associated with it which should produce the same answer (e.g. “How old are you?” vs “What is your age?”). This functionality is only available to users with administrative access.

Creating Saved Replies

To create a new saved reply simply call the cape.client.CapeClient.add_saved_reply() method with a question and answer pair:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
reply_id = cc.add_saved_reply('What colour is the sky?', 'Blue')
print(reply_id)

This will respond with a dictionary containing the ID of the new reply and the ID of the new answer:

{
    'replyId': 'f9f1cf90-c3b1-11e7-91a1-9801a7ae6c69',
    'answerId': 'd2780710-c3c3-11e7-8d29-d15d28ee5381'
}

Saved replies must have a unique question. If this question already exists then an error is returned.

Deleting Saved Replies

To delete a saved reply pass its ID to the cape.client.CapeClient.delete_saved_reply() method:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
cc.delete_saved_reply("f9f1cf90-c3b1-11e7-91a1-9801a7ae6c69")

Retrieving Saved Replies

To retrieve a list of all saved replies use the cape.client.CapeClient.get_saved_replies() method:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
replies = cc.get_saved_replies()
print(replies)

This will return a list of replies:

{
    'totalItems': 2,
    'items': [
        {
            'id': 'd277e000-c3c3-11e7-8d29-d15d28ee5381',
            'canonicalQuestion': 'How old are you?',
            'answers': [
                {
                    'id': 'd2780710-c3c3-11e7-8d29-d15d28ee5381',
                    'answer': '18'
                }
            ],
            'paraphraseQuestions': [
                {
                    'id': 'd2780711-c3c3-11e7-8d29-d15d28ee5381',
                    'question': 'What is your age?'
                },
                {
                    'id': 'd2780712-c3c3-11e7-8d29-d15d28ee5381',
                    'question': 'How many years old are you?'
                }
            ],
            'created': 1508161734,
            'modified': 1508161734
        },
        {
            'id': 'd2780713-c3c3-11e7-8d29-d15d28ee5381',
            'canonicalQuestion': 'What colour is the sky?',
            'answers': [
                {
                    'id': 'd2780714-c3c3-11e7-8d29-d15d28ee5381',
                    'answer': 'Blue'
                }
            ],
            'paraphraseQuestions': [],
            'created': 1508161323,
            'modified': 1508161323
        }
    ]
}

By default this will retrieve 30 saved replies at a time. The number_of_items and offset parameters can be used to control the size of the batches and retrieve multiple batches of saved replies (similar to the mechanism described in the multiple answers section). The response also includes the totalItems property which tells us the total number of items available (beyond those retrieved in this specific batch).

Each saved reply in the list contains the following properties:

Property Description
id The reply ID
canonicalQuestion The question to which the saved reply corresponds
answers A list of saved answers, one of which will be selected at random as the response to the question.
paraphraseQuestions A list of questions which paraphase the canonical question
modified Timestamp indicating when this saved reply was last modified
created Timestamp indicating when this saved reply was created

It’s also possible to search saved replies, for example to retrieve only saved replies containing the word ‘blue’:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
replies = cc.get_saved_replies(search_term='blue')

Editing Saved Replies

There are three different parts of a saved reply that can be edited, the canonical question, the paraphrase questions and the answers.

Adding Paraphrase Questions

Paraphrase questions are alternative phrasings of the canonical question which should produce the same answer. For example “What is your age?” can be considered a paraphrase of “How old are you?”. These can be added with the cape.client.CapeClient.add_paraphrase_question() method:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
question_id = cc.add_paraphrase_question("f9f1cf90-c3b1-11e7-91a1-9801a7ae6c69", 'What is your age?')
print(question_id)

This will respond with the ID of the newly created question:

21e9689e-c3b2-11e7-8a22-9801a7ae6c69

Editing Paraphrase Questions

To edit a paraphrase question call cape.client.CapeClient.edit_paraphrase_question() with the ID of the question to edit and the new question text to modify it with:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
cc.edit_paraphrase_question("21e9689e-c3b2-11e7-8a22-9801a7ae6c69", 'How many years old are you?')

Deleting Paraphrase Questions

To delete a paraphrase question simply call cape.client.CapeClient.delete_paraphrase_question() with the ID of question to be deleted:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
cc.delete_paraphrase_question("21e9689e-c3b2-11e7-8a22-9801a7ae6c69")

Adding Answers

If multiple answers are added to a saved reply then one will be selected at random when responding. Additional answers can be added with the cape.client.CapeClient.add_answer() method:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
answer_id = cc.add_answer("68c445cc-c3b2-11e7-8a88-9801a7ae6c69", 'Grey')
print(answer_id)

This will respond with the ID of the new answer:

703acab4-c3b2-11e7-b8b1-9801a7ae6c69

Deleting Answers

To delete an answer call cape.client.CapeClient.delete_answer() with the ID of the answer to be deleted:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
cc.delete_answer("703acab4-c3b2-11e7-b8b1-9801a7ae6c69")

Because every saved reply must have at least one answer it’s not possible to delete the last remaining answer in a saved reply, in this case you may wish to consider deleting the saved reply itself.

Editing Canonical Questions

To edit the canonical question call cape.client.CapeClient.edit_canonical_question() with the ID of the saved reply that it belongs to:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
cc.edit_canonical_question("f9f1cf90-c3b1-11e7-91a1-9801a7ae6c69", 'What age are you?')

Managing The Inbox

The inbox provides a list of questions that have been asked by users and the response the system has replied with. This functionality is only available to users with administrative access.

Retrieving Inbox Items

To retrieve inbox items call the cape.client.CapeClient.get_inbox() method:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
inbox = cc.get_inbox()
print(inbox)

This returns a list of inbox items:

{
    'totalItems': 2,
    'items': [
        {
            'id': '4124',
            'answered': False,
            'read': False,
            'question': 'Who are you?',
            'questionSource': 'API',
            'created': 1508162032,
            'answers': []
        },
        {
            'id': '4123',
            'answered': True,
            'read': False,
            'question': 'How easy is the API to use?',
            'questionSource': 'API',
            'created': 1508161834,
            'answers': [
                {
                    'answerText': "Hopefully it's pretty easy",
                    'answerContext: "Welcome to the Cape API 0.1. Hopefully it's pretty easy to use.",
                    'confidence': 0.75,
                    'sourceType': 'document',
                    'sourceId': '358e1b77c9bcc353946dfe107d6b32ff',
                    'answerTextStartOffset': 30,
                    'answerTextEndOffset': 56,
                    'answerContextStartOffset': 0,
                    'answerContextEndOffset': 64
                }
            ]
        }
    ]
}

By default this will retrieve 30 inbox items at a time. The number_of_items and offset parameters can be used to control the size of the batches and retrieve multiple batches of inbox items (similar to the mechanism described in the multiple answers section). The response also includes the totalItems property which tells us the total number of items available (beyond those retrieved in this specific batch).

Each inbox item in the list has the following properties:

Property Description
id Unique ID for this inbox item
question The question that a user asked
read Whether this item has been read
answered Whether an answer could be found for this question
answers A list of answer objects
created Timestamp indicating when this question was asked

Inbox items can be searched and filtered, for example to retrieve only inbox items that haven’t been read but have been answered and contain the word ‘API’:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
inbox = cc.get_inbox(read=False, answered=True, search_term='api')

Marking Inbox Items As Read

To mark an inbox item as having been read call the cape.client.CapeClient.mark_inbox_read() method with the ID of the inbox item to mark as having been read:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
cc.mark_inbox_read('4123')

Archiving Inbox Items

Once an inbox item has been archived it will no longer appear in the list of inbox items returned by cape.client.CapeClient.get_inbox(). To archive an item call cape.client.CapeClient.archive_inbox() with the ID of the inbox item to archive:

from cape.client import CapeClient

cc = CapeClient()
cc.login('username', 'password')
cc.archive_inbox('4123')