├── .gitignore ├── artifacts ├── architecture │ ├── architecture-README.md │ ├── LoanRisk-Single-AI-Agent-Conceptual.png │ └── LoanRisk-Single-AI-Agent-Deployment.png ├── usage-examples │ ├── UsageExample1.png │ ├── UsageExample2.png │ ├── UsageExample3.png │ ├── UsageExample4.png │ └── usage-examples-README.md ├── data │ ├── Bank Loan Overall Risk Policy.pdf │ └── Bank Loan Interest Rate Policy.pdf ├── wxAssistantOrchestrate │ ├── wx-asst-agentic-ai-app.zip │ └── agentic-ai-app-custom-ext-openapi.json ├── deployment │ ├── Deploying-Loan-Risk-AI-Agent-on-Code-Engine.pdf │ ├── Deploying-Loan-Risk-AI-Agent-on-Code-Engine.docx │ └── deployment-README.md └── python-notebook │ └── NB-ai-agent-loan-risk-demo-v1.ipynb ├── public ├── images │ ├── input_sources.png │ ├── wxasst-header.png │ ├── wxasst-background.png │ ├── input_sources_blue.png │ ├── input_sources_pink.png │ └── LoanRisk-Single-AI-Agent-Conceptual.png ├── Bank Loan Overall Risk Policy.pdf ├── Bank Loan Interest Rate Policy.pdf ├── wx-template.html ├── wx-template2.html ├── usage-single-agent.html ├── stylesheets │ └── general.css └── index-single-agent.html ├── Dockerfile ├── tsconfig.json ├── DockerfileRH ├── package.json ├── README.md ├── LICENSE └── main.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /artifacts/architecture/architecture-README.md: -------------------------------------------------------------------------------- 1 | tbd 2 | -------------------------------------------------------------------------------- /public/images/input_sources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/images/input_sources.png -------------------------------------------------------------------------------- /public/images/wxasst-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/images/wxasst-header.png -------------------------------------------------------------------------------- /public/images/wxasst-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/images/wxasst-background.png -------------------------------------------------------------------------------- /public/images/input_sources_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/images/input_sources_blue.png -------------------------------------------------------------------------------- /public/images/input_sources_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/images/input_sources_pink.png -------------------------------------------------------------------------------- /public/Bank Loan Overall Risk Policy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/Bank Loan Overall Risk Policy.pdf -------------------------------------------------------------------------------- /artifacts/usage-examples/UsageExample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/usage-examples/UsageExample1.png -------------------------------------------------------------------------------- /artifacts/usage-examples/UsageExample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/usage-examples/UsageExample2.png -------------------------------------------------------------------------------- /artifacts/usage-examples/UsageExample3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/usage-examples/UsageExample3.png -------------------------------------------------------------------------------- /artifacts/usage-examples/UsageExample4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/usage-examples/UsageExample4.png -------------------------------------------------------------------------------- /public/Bank Loan Interest Rate Policy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/Bank Loan Interest Rate Policy.pdf -------------------------------------------------------------------------------- /artifacts/data/Bank Loan Overall Risk Policy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/data/Bank Loan Overall Risk Policy.pdf -------------------------------------------------------------------------------- /artifacts/data/Bank Loan Interest Rate Policy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/data/Bank Loan Interest Rate Policy.pdf -------------------------------------------------------------------------------- /public/images/LoanRisk-Single-AI-Agent-Conceptual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/public/images/LoanRisk-Single-AI-Agent-Conceptual.png -------------------------------------------------------------------------------- /artifacts/wxAssistantOrchestrate/wx-asst-agentic-ai-app.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/wxAssistantOrchestrate/wx-asst-agentic-ai-app.zip -------------------------------------------------------------------------------- /artifacts/architecture/LoanRisk-Single-AI-Agent-Conceptual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/architecture/LoanRisk-Single-AI-Agent-Conceptual.png -------------------------------------------------------------------------------- /artifacts/architecture/LoanRisk-Single-AI-Agent-Deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/architecture/LoanRisk-Single-AI-Agent-Deployment.png -------------------------------------------------------------------------------- /artifacts/deployment/Deploying-Loan-Risk-AI-Agent-on-Code-Engine.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/deployment/Deploying-Loan-Risk-AI-Agent-on-Code-Engine.pdf -------------------------------------------------------------------------------- /artifacts/deployment/Deploying-Loan-Risk-AI-Agent-on-Code-Engine.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/ai-agent-for-loan-risk/main/artifacts/deployment/Deploying-Loan-Risk-AI-Agent-on-Code-Engine.docx -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | ENV APPLICATION_HOME_DOCKER=/usr/src/app 4 | ENV APPLICATION_PORT=8080 5 | 6 | WORKDIR $APPLICATION_HOME_DOCKER 7 | 8 | COPY . . 9 | RUN npm install 10 | RUN npm run build 11 | 12 | EXPOSE $APPLICATION_PORT 13 | CMD ["npm","run", "start"] 14 | 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "esModuleInterop": true, 5 | "target": "esnext", 6 | "moduleResolution": "nodenext", 7 | "sourceMap": true, 8 | "outDir": "build", 9 | "strict": false, 10 | "noImplicitAny": false, 11 | "strictNullChecks": false, 12 | "strictFunctionTypes": false, 13 | "strictBindCallApply": false, 14 | "strictPropertyInitialization": false, 15 | "noImplicitThis": false, 16 | "alwaysStrict": false 17 | }, 18 | "lib": ["esnext"] 19 | } 20 | 21 | -------------------------------------------------------------------------------- /DockerfileRH: -------------------------------------------------------------------------------- 1 | FROM registry.redhat.io/rhel9/s2i-core:9.5@sha256:9bf05bcc1acc961f8dfe64752ab36be188a6fe3d219b0d3343d28ef7e00ffb62 2 | 3 | USER root 4 | RUN yum update -y && yum upgrade -y 5 | RUN npm -v 6 | 7 | ENV APPLICATION_HOME_DOCKER=/usr/src/app 8 | ENV APPLICATION_PORT=8080 9 | 10 | WORKDIR $APPLICATION_HOME_DOCKER 11 | 12 | COPY . . 13 | RUN npm install 14 | RUN npm run build 15 | 16 | RUN chown -R 1001:0 ${APPLICATION_HOME_DOCKER} && chmod -R u+rwx ${APPLICATION_HOME_DOCKER} && \ 17 | chmod -R g=u ${APPLICATION_HOME_DOCKER} 18 | 19 | USER 1001 20 | 21 | EXPOSE $APPLICATION_PORT 22 | CMD ["npm","run", "start"] 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bank-loan-risk-ai-agent", 3 | "version": "0.0.1", 4 | "main": "main.ts", 5 | "scripts": { 6 | "dev": "nodemon", 7 | "start": "node build/main.js", 8 | "build": "tsc --project ./" 9 | }, 10 | "keywords": [], 11 | "type": "module", 12 | "author": "Anuj Jain jainanuj@us.ibm.com", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@ibm-cloud/watsonx-ai": "^1.4.0", 16 | "@langchain/anthropic": "^0.3.11", 17 | "@langchain/community": "^0.3.22", 18 | "@langchain/core": "^0.3.27", 19 | "@langchain/langgraph": "^0.2.39", 20 | "express": "^4.21.2", 21 | "json-server": "^0.16.3" 22 | }, 23 | "devDependencies": { 24 | "@types/express": "^5.0.0", 25 | "nodemon": "^3.1.9", 26 | "ts-node": "^10.9.2", 27 | "tsx": "^4.19.2", 28 | "typescript": "^5.7.3" 29 | }, 30 | "description": "" 31 | } 32 | -------------------------------------------------------------------------------- /public/wx-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Agentic Financial Assistant 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 |
13 | 30 |
31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /artifacts/usage-examples/usage-examples-README.md: -------------------------------------------------------------------------------- 1 | #### Usage flow: 2 | To use the application (and for demo), you assume the persona of a human risk analyst from a bank. You interact with the Loan Risk AI agent to assess the risk and interest rate for customers. When requested, the Loan Risk AI agent interprets the question context, uses LLMs and information about the available tools to decide the logic and sequence for completing the request, and then acts on the information to complete the request. LLMs receive the questions and natural language prompt instructions from the AI agent, but there is no hardcoded traditional programming logic for interpretation or tool sequence to find the response. 3 | 4 | 5 | #### Example questions to ask the Loan Risk AI Agent 6 | Below are some examples of question to ask the Loan Risk AI Agent. 7 | 8 | The code has predefined customers (Loren, Matt and Hilda) with specific risk profiles. The credit score and account status tool simulate API calls to return values for these customers. 9 | 10 | 11 | 12 | + what can you do for me? 13 | + what can you do for me? how? 14 | 15 | + what is the risk for loren? 16 | + what is the risk for loren? why? 17 | 18 | + What is the interest rate for matt? 19 | + What is the interest rate for matt? Explain how it was determined? 20 | + What is the interest rate for matt? why? how do you decide? 21 | 22 | + What is the credit score for hilda? 23 | 24 | + What is the credit score and account status for hilda and matt? 25 | + What is the interest rate for credit score 655 and account status closed? why? 26 | + What is the risk with credit score 825 but account status delinquent? why? 27 | 28 | + What is the interest rate for low to medium risk? 29 | + What is the interest rate for low to medium risk? why? 30 | 31 | + How do you determine the interest rate from overall risk? Explain. 32 | 33 | Example screenshots: 34 | 35 | Example 1:![Example 1](UsageExample1.png) 36 | 37 | Example 2:![Example 2](UsageExample2.png) 38 | 39 | Example 3:![Example 2](UsageExample3.png) 40 | 41 | Example 4:![Example 2](UsageExample4.png) 42 | -------------------------------------------------------------------------------- /public/wx-template2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Agentic Financial Assistant 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | This demo showcases agentic AI, an autonomous, adaptable system that can perform tasks independently and purposefully. 14 | 15 |
16 |
17 | 18 | To use the application, you assume the persona of a human risk analyst from a bank.
19 | You can interact with the Loan Risk AI agent to assess the risk and interest rate for customers.
20 | When requested, the Loan Risk AI agent interprets the question context, uses LLMs and information about the available tools to decide the logic and sequence for completing the request, and then acts on the information to complete the request.
21 | LLMs receive the questions and natural language prompt instructions from the AI agent, but there is no hardcoded traditional programming logic for interpretation or tool sequence to find the response.

22 | To begin, click on the icon on the bottom right.
23 | A list of sample queries, and the agent workflow, is located here. 24 | A suggested starter query is "What can you do for me? How?" or similar - remember that the agent uses natural language processing, so queries don't have to be exactly worded as in the examples. 25 |
26 |
27 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /artifacts/wxAssistantOrchestrate/agentic-ai-app-custom-ext-openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "description": "Minimal spec for calling Bank Loan Risk AI Agent API endpoint", 5 | "title": "Loan Risk AI Agent API", 6 | "version": "1.0.0" 7 | }, 8 | "servers": [ 9 | { 10 | "url": "https://agentic-ai-app.1r9wutl307jv.us-south.codeengine.appdomain.cloud", 11 | "description": "loan risk ai agent base url" 12 | } 13 | ], 14 | "components": { 15 | "schemas":{ 16 | "messages": { 17 | "type": "object", 18 | "description": "agent messages", 19 | "properties": { 20 | "type": { 21 | "type": "string", 22 | "description": "human or ai" 23 | }, 24 | "content": { 25 | "type": "string", 26 | "description": "message content" 27 | }, 28 | "toolCalls": { 29 | "type": "array", 30 | "description": "tool call details", 31 | "items": { 32 | "$ref": "#/components/schemas/toolcalls" 33 | } 34 | } 35 | } 36 | }, 37 | "toolcalls": { 38 | "type": "object", 39 | "description": "tool call details", 40 | "properties": { 41 | "name": { 42 | "type": "string", 43 | "description": "name" 44 | }, 45 | "args": { 46 | "type": "object", 47 | "description": "message content" 48 | }, 49 | "type": { 50 | "type": "string", 51 | "description": "tool type" 52 | }, 53 | "id": { 54 | "type": "string", 55 | "description": "tool id" 56 | } 57 | } 58 | } 59 | } 60 | }, 61 | "paths": { 62 | "/callagent": { 63 | "post": { 64 | "description": "Call the top level AI agent endpoint", 65 | "parameters": [], 66 | "requestBody": { 67 | "content": { 68 | "application/json": { 69 | "schema": { 70 | "type": "object", 71 | "required": ["type", "query"], 72 | "properties": { 73 | "type": { 74 | "type": "string", 75 | "description": "The type of call. Always call_ai_agent", 76 | "example": "call_ai_agent" 77 | }, 78 | "query": { 79 | "type": "string", 80 | "description": "The input query for the ai agent." 81 | } 82 | } 83 | } 84 | } 85 | } 86 | }, 87 | "responses": { 88 | "200": { 89 | "description": "Default Response", 90 | "content": { 91 | "application/json": { 92 | "schema": { 93 | "type": "array", 94 | "items": { 95 | "$ref": "#/components/schemas/messages" 96 | } 97 | } 98 | } 99 | } 100 | }, 101 | "default": { 102 | "description": "Unexpected error" 103 | } 104 | } 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Loan Risk - AI Agent 2 | 3 | > Note: - Due to the deprecation of "mistral-large" on watsonx.ai in ~October 2025, this application now uses "ibm/granite-4-h-small" and does not perform as documented in this repository. It needs to be tested and updated further. See contact information below. 4 | 5 | This repository provides an AI agent application for demonstration and proof-of-concept(PoC) to showcase agentic AI adoption in industry/enterprise workflows and use cases. 6 | 7 | With a focus on the financial industry, it uses a bank loan processing workflow as an example that leverages agentic AI. It demonstrates one of the main values of using agentic AI - _relying on LLMs to reason about what to do and take actions_, instead of relying on traditional approach of rules and conditions. 8 | 9 | The application is built to run on IBM Cloud – deploy on Code Engine to host the code for the AI agent, use watsonx.ai for inferencing and retrieval augmented generation (RAG) with LLMs, and watsonx Assistant/Orchestrate for a chat conversation experience. 10 | 11 | To learn more about the key features and architectural concepts of agentic AI and about using this Loan Risk AI Agent, you can: 12 | 13 | - Read the article [Agentic AI in enterprise workflow automation](https://developer.ibm.com/articles/agentic-ai-workflow-automation/). 14 | - Watch the 5-minute demo video [Agentic AI on IBM Cloud - Demo](https://mediacenter.ibm.com/media/Agentic+AI+on+IBM+Cloud+-+Demo+Video/1_kn6kvqmz). 15 | 16 | For questions or feedback contact Anuj Jain (jainanuj@us.ibm.com). 17 | 18 | ## Use Case 19 | + AI agent to support bank loan risk evaluation workflow. 20 | + AI agent determines overall risk and interest rate for a bank loan using LLMs and relevant tools. 21 | 22 | Similar use cases can be found in insurance, healthcare and other industry/enterprise workflows. 23 | 24 | 25 | ## Architecture 26 | + Architecture: Single AI Agent with Tools (using LangGraph, TypeScript/NodeJS) 27 | + LLM: ibm/granite-4-h-small (from IBM watsonx.ai) (mistral-large is deprecated) 28 | + Tools: API/functions (for credit score, account status, risk evaluation criteria, interest rate determination) 29 | 30 | #### Conceptual Architecture 31 | ![Conceptual architecture](artifacts/architecture/LoanRisk-Single-AI-Agent-Conceptual.png) 32 | 33 | #### High-level Deployment Architecture 34 | ![High-level deployment architecture](artifacts/architecture/LoanRisk-Single-AI-Agent-Deployment.png) 35 | 36 | 37 | ## Deployment 38 | #### Prerequisites 39 | Requires IBM Cloud account with: 40 | - watsonx.ai services 41 | - Account user with administrative privileges for watsonx.ai, Code Engine, Container Registry 42 | - Account user’s API key 43 | 44 | Optional enhancements: 45 | 46 | For using agentic RAG feature, you will also need watsonx.ai vector index and a deployed RAG inferencing endpoint. 47 | 48 | For using chat conversaiton feature, you will need watsonx Assistant/Orchestrate service. 49 | 50 | Steps for deploying the base and enhancements are provided below. 51 | 52 | #### Deployment options 53 | - IBM Cloud Code Engine - Refer instructions [here.](artifacts/deployment/deployment-README.md) 54 | - Python Notebook - A simplified version of this application can be set up as a Python Notebook (Code Engine deployment not required). Download from [here](artifacts/python-notebook/NB-ai-agent-loan-risk-demo-v1.ipynb) and import in watsonx.ai Project as an asset. It is meant for understanding the concepts and for experimentation. 55 | 56 | 57 | ## Usage 58 | For usage and additional examples refer [here.](artifacts/usage-examples/usage-examples-README.md) 59 | 60 | ![Example usage screenshot](artifacts/usage-examples/UsageExample2.png) 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /public/usage-single-agent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Using Loan Risk - AI Agent 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | Suggested questions for Loan Risk - AI Agent 16 |
17 |
18 |
19 | 20 |
21 | You’re a loan risk analyst who uses the Loan Risk AI Agent to assess risks. 22 | You have customers with varying credit scores and account statuses applying for loans. 23 | Here are some suggested questions. 24 | 25 |
> What can you do for me? 26 |
> What can you do for me? how? 27 | 28 |
> What is the risk for loren? 29 | 30 |
> What is the risk for loren? why? 31 | 32 |
> What is the interest rate for matt? 33 |
> What is the interest rate for matt? Explain how it was determined? 34 |
> What is the interest rate for matt? why? how do you decide? 35 |
> What is the credit score for hilda? 36 |
> What is the credit score and account status for hilda and matt? 37 |
> What is the interest rate for credit score 655 and account status closed? why? 38 |
> What is the risk with credit score 825 but account status delinquent? why? 39 |
> What is the interest rate for low to medium risk? 40 |
> What is the interest rate for low to medium risk? why? 41 |
> How do you determine the interest rate from overall risk? Explain. 42 |
Try other related questions and share your experience. 43 |
44 | Conceptual Architecture: 45 | 46 |
47 | Content used for Agentic RAG: 48 |

Bank Loan Overall Risk Policy PDF.

49 |

Bank Loan Interest Rate Policy PDF.

50 |
51 |
52 |
53 | 54 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /public/stylesheets/general.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | background-color: white; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body { 9 | height: 100%; 10 | background-color: white; 11 | margin: 0; 12 | padding: 0; 13 | } 14 | .top-container { 15 | background: #0354E9; 16 | box-shadow: 0 2px 2px 0 rgba(140, 155, 165, 0.25); 17 | text-align: center; 18 | color: #42535C; 19 | margin-bottom: 28px; 20 | } 21 | 22 | 23 | .maindiv { 24 | display: flex; 25 | flex-direction: column; 26 | height: 100%; 27 | } 28 | 29 | .middle-container { 30 | background: white; 31 | text-align: left; 32 | margin-left: 2%; 33 | margin-bottom: 20px; 34 | } 35 | 36 | .bottom-container { 37 | background: #F5F7FA; 38 | box-shadow: 0 2px 2px 0 rgba(140, 155, 165, 0.25); 39 | flex-grow: 1; 40 | } 41 | 42 | .bottom-container-data { 43 | #display: inline-block; 44 | color: darkblue; 45 | margin-top: 15px; 46 | margin-left: 2%; 47 | margin-right: 8%; 48 | text-align: right; 49 | margin-bottom: 15px; 50 | #min-width: 80%; 51 | } 52 | 53 | .token-title { 54 | font-family: IBMPlexSans; 55 | font-size: 14px; 56 | color: #000000; 57 | margin-bottom: 21px; 58 | } 59 | 60 | .token-info { 61 | font-family: IBMPlexMono; 62 | background: #FFFFFF; 63 | box-shadow: 0 2px 2px 0 rgba(140, 155, 165, 0.25); 64 | font-size: 14px; 65 | color: #152934; 66 | line-height: 24px; 67 | font-weight: normal; 68 | padding: 15px 20px 20px 32px; 69 | margin-right: 90px; 70 | margin-bottom: 20px; 71 | } 72 | 73 | .login { 74 | margin-right: 24px; 75 | } 76 | 77 | .login-button { 78 | background: #BFBFBF; 79 | border: none; 80 | text-decoration: none; 81 | cursor: pointer; 82 | margin-top:10px; 83 | margin-right:20px; 84 | height: 30px; 85 | width: 100px; 86 | float: right; 87 | } 88 | 89 | .logout { 90 | margin-right: 24px; 91 | } 92 | 93 | .logout-icon { 94 | margin-top: 10px; 95 | float: right; 96 | } 97 | 98 | .hello-text { 99 | font-family: IBMPlexSans-Light; 100 | font-size: 20px; 101 | color: #000000; 102 | letter-spacing: 0; 103 | line-height: 26px; 104 | margin-bottom: 30px; 105 | } 106 | 107 | .label-text { 108 | font-family: IBMPlexSans-Light; 109 | font-size: 16px; 110 | color: #000000; 111 | letter-spacing: 0; 112 | line-height: 26px; 113 | margin-bottom: 30px; 114 | } 115 | 116 | .above-button-text { 117 | font-family: IBMPlexSans-Light; 118 | font-size: 14px; 119 | color: #000000; 120 | letter-spacing: 0; 121 | margin-bottom: 13px; 122 | } 123 | 124 | .button { 125 | background: #4178BE; 126 | border: none; 127 | text-decoration: none; 128 | cursor: pointer; 129 | height: 36px; 130 | width: 231px; 131 | } 132 | 133 | .button-text { 134 | font-family: IBMPlexSans-Medium; 135 | font-size: 14px; 136 | color: #FFFFFF; 137 | letter-spacing: 0; 138 | text-align: center; 139 | margin: 8px 20px 10px 12px; 140 | } 141 | 142 | .hide { 143 | display: none; 144 | } 145 | 146 | .top { 147 | background: #0354E9; 148 | color: #BFBFBF; 149 | #font-weight: lighter; 150 | #box-shadow: 0 4px 2px 0 rgba(140, 155, 165, 0.25); 151 | padding-bottom: 10px; 152 | 153 | font-family: IBMPlexSans; 154 | font-size: 30px; 155 | text-align: left; 156 | #color: white; 157 | #letter-spacing: 0; 158 | #line-height: 26px; 159 | margin-left:45px; 160 | #margin-bottom: 30px; 161 | } 162 | 163 | .small-textarea { 164 | text-align: left; 165 | width: 80%; 166 | height: 150px; 167 | padding: 5px 10px; 168 | box-sizing: border-box; 169 | border: 2px solid #ccc; 170 | border-radius: 4px; 171 | background-color: #f8f8f8; 172 | resize: none; 173 | } 174 | .large-textarea { 175 | text-align: left; 176 | width: 80%; 177 | height: 550px; 178 | padding: 5px 10px; 179 | box-sizing: border-box; 180 | border: 2px solid #ccc; 181 | border-radius: 4px; 182 | background-color: #f8f8f8; 183 | resize: none; 184 | } 185 | 186 | .flex-top { 187 | flex: 1; 188 | text-align: center; 189 | margin-top: 2.5%; 190 | } 191 | 192 | .flex-bottom { 193 | font-family: IBMPlexSans-Medium; 194 | font-size: 14px; 195 | color: #42535C; 196 | letter-spacing: 0; 197 | line-height: 20px; 198 | font-weight: normal; 199 | flex: none; 200 | margin-right: 5px; 201 | margin-left: 5px; 202 | } 203 | 204 | .text-div { 205 | font-family: IBMPlexSans-Light; 206 | font-size: 20px; 207 | color: #152935; 208 | letter-spacing: 0; 209 | text-align: center; 210 | line-height: 24px; 211 | flex: 1; 212 | margin-bottom: 59px; 213 | } 214 | 215 | .button { 216 | font-family: IBMPlexSans-Medium; 217 | font-size: 14px; 218 | color: #FFFFFF; 219 | letter-spacing: 0; 220 | text-align: center; 221 | background-color: #4178BE; 222 | border: none; 223 | padding: 10px 40px; 224 | text-decoration: none; 225 | cursor: pointer; 226 | width: 158px; 227 | height: 40px; 228 | margin-bottom: 4px; 229 | } 230 | 231 | .logo-icon { 232 | display: block; 233 | margin-left: auto; 234 | margin-right: auto; 235 | width: 70px; 236 | height: 70px; 237 | margin-bottom: 41px; 238 | margin-top: 81px; 239 | } 240 | 241 | #menu li { 242 | padding: 5px 15px; 243 | border-top: 1px solid #eaeaea; 244 | } 245 | 246 | #menu ul { 247 | padding: 0; 248 | margin: 0; 249 | list-style-type: none; 250 | } 251 | 252 | #menu li:nth-child(1) { 253 | border-top: 0; 254 | } 255 | 256 | #menu li a { 257 | color: black; 258 | } 259 | -------------------------------------------------------------------------------- /artifacts/deployment/deployment-README.md: -------------------------------------------------------------------------------- 1 | # Steps for Deploying on IBM Cloud Code Engine 2 | 3 | ## Prerequisites 4 | 5 | Requires IBM Cloud account with: 6 | - watsonx.ai services [(refer)](https://dataplatform.cloud.ibm.com/docs/content/wsj/getting-started/signup-wx.html?context=wx&audience=wdp). This includes: 7 | - watsonx.ai Runtime 8 | - watsonx.ai Studio 9 | - Cloud Object Storage 10 | - Account user with administrative privileges for: 11 | - watsonx.ai - to create Projects, access services, store assets, etc. [(refer)](https://dataplatform.cloud.ibm.com/docs/content/wsj/getting-started/projects.html?context=wx&audience=wdp) 12 | - Code Engine – to create Project and deploy the application. [(refer-1)](https://cloud.ibm.com/docs/codeengine?topic=codeengine-getting-started) [(refer-2)](https://cloud.ibm.com/docs/codeengine?topic=codeengine-app-source-code) 13 | - Container Registry – to create namespace, store images for deploying the application 14 | - Account user’s API key [(refer)](https://cloud.ibm.com/docs/account?topic=account-userapikey&interface=ui#create_user_key) 15 | 16 | 17 | 18 | ## Deployment Summary 19 | **NOTE**: A document with detailed steps is available [here](Deploying-Loan-Risk-AI-Agent-on-Code-Engine.pdf). 20 | 21 | #### 1. Confirm prerequisites and gather information 22 | Note the user’s API key value. It will be used to set the environment variable WATSONX_AI_APIKEY. 23 | 24 | Depending on where the watsonx.ai service is deployed get the watsonx.ai API endpoint URL from [here](https://cloud.ibm.com/apidocs/watsonx-ai#endpoint-url). It will be used to set the environment variable WATSONX_SERVICE_URL. 25 | 26 | #### 2. Create a Project in watsonx.ai 27 | Launch watsonx.ai and create a Project, or use an existing one. 28 | 29 | Note the Project Id. This will be used to set the environment variable WATSONX_PROJECT_ID. 30 | 31 | #### 3. Deploy application to Code Engine 32 | Follow the steps to _deploy the application from repository source code_ as described [here](https://cloud.ibm.com/apidocs/watsonx-ai#endpoint-url). 33 | 34 | Use code repo URL: https://github.com/IBM/ai-agent-for-loan-risk.git 35 | 36 | Add the environment variables and the values captured above to the deployment. 37 | - WATSONX_AI_APIKEY 38 | - WATSONX_PROJECT_ID 39 | - WATSONX_SERVICE_URL 40 | 41 | Launch the application using the Code Engine URL for the deployed application. 42 | 43 | #### 4. Validate the application 44 | Ask “What is the interest rate for matt? Explain how it was determined?”. You should get a response explain high risk and 8% interest rate. 45 | 46 | Refer to the application [usage section](../usage-examples/usage-examples-README.md). 47 | 48 | #### 5. Optionally enhance application with additional features 49 | Once initial application is deployed and running successfully, you can add enhancements. 50 | - Using RAG LLM (Agentic RAG feature) 51 | - Using watsonx Assistant/Orchestrate (Chat widget) 52 | 53 | #### 6. Optional: Using RAG LLM (Agentic RAG feature) 54 | By default, the risk and interest rate tools of the AI agent simulate risk and interest rate determination. By adding this feature, the AI agent tool will make RAG query to retrieve relevant content from the bank documents and use that to determine the risk and interest rate. In the AI agent response you will be able to see the content from the document table and its interpretation by the AI agent. 55 | - Create a new watsonx.ai Deployment Space with Deployment stage set as Production. [(refer)](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-space-create.html?context=wx&locale=en&audience=wdp) 56 | - Create a vector index asset in the watsonx.ai Project using these content PDF documents. To create the index use vector store - "In memory", embedding model - "allminilm-l6-v2", chunksize - "2000", chunk overlap - "200". 57 | - Open the vector index you just created in Prompt Lab and set the generative AI model to "mistral-large". Test the index by asking some questions e.g., what is the risk for credit score 655 and account status closed?, what is the interest rate for medium risk?, and confirm answers are using RAG from the content in PDF documents provided [here](../data). 58 | - Deploy the vector index as watsonx.ai Deployment on AI service for inferencing using the "fast path" option (use the "Deploy" button). 59 | [(refer-1)](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ai-services-overview.html?context=wx&locale=en) [(refer-2)](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ai-services-prompt-lab.html?context=wx) and [(refer-3)](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ai-services-deploy-fast-path.html?context=wx). 60 | - Capture the watsonx.ai Deployment private endpoint for the vector index for RAG inferencing (use non-stream; ends with ai_service?version=...) 61 | - On Code Engine add the following environment variables with the values captured above and redeploy the application (ENABLE_RAG_LLM=true and WATSONX_RISK_RAG_LLM_ENDPOINT=endpoint captured above) 62 | - The application will now use the RAG content for risk and interest tools. 63 | 64 | #### 7. Optional: Using watsonx Assistant/Orchestrate (Chat widget) 65 | By adding this feature, you can get a more conversational/chat experience when asking questions in the watsonx Assistant chat widget. The conversation is single turn and the watsonx Assistant skills can be enhanced further if needed. 66 | - Note the Code Engine URL for the deployed application. 67 | - Open API file (agentic-ai-app-custom-ext-openapi.json) and update the URL with the deployed appliaciton URL. 68 | - Create an action skill in watsonx Assistant instance 69 | - Create and add a custom extension by importing the updated Open API file [agentic-ai-app-custom-ext-openapi.json](../wxAssistantOrchestrate/agentic-ai-app-custom-ext-openapi.json). 70 | - Import the zip file to set up the actions that use the custom extension [wx-asst-agentic-ai-app.zip](../wxAssistantOrchestrate/wx-asst-agentic-ai-app.zip). 71 | - Open the watsonx Assistant Web chat configuration and note the integrationID, region and serviceInstanceID from the Embed script tab. 72 | - On Code Engine open the deployed application configuration, add the following environment variables with the values captured above and redeploy the application (ENABLE_WXASST=true, WXASST_INTEGRATION_ID, WXASST_REGION, WXASST_SERVICE_INSTANCE_ID captured above) 73 | - The watsonx Assistant will become available on the page /wx.html 74 | 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /public/index-single-agent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Loan Risk - AI Agent 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | Loan Risk - AI Agent (on IBM Cloud) 16 |
17 |
18 |
19 | 24 |
25 | 26 |
Input:
27 | 28 |
29 | 30 |
31 |
32 | AI Agent response:
33 |
34 |
35 |
36 | 37 |
Input JSON: 38 |
39 | 41 |
42 |
43 | Response JSON:
44 | 45 |
46 | Status: 47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 | 72 |
73 | 74 | 75 | 76 | 77 | 274 | 275 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 IBM Corp. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | //***********************************************************// 14 | // Loan Risk - AI Agent 15 | // LangGraph - single AI agent with LLM and tools 16 | // Author: Anuj Jain (jainanuj@us.ibm.com) 17 | //***********************************************************// 18 | 19 | import express, { Application, Request, Response } from 'express'; 20 | import path from 'path'; 21 | import fs from 'fs'; 22 | const __dirname = path.dirname('.'); // get the name of the directory 23 | 24 | import { AIMessage, BaseMessage, HumanMessage } from "@langchain/core/messages"; 25 | import { tool } from "@langchain/core/tools"; 26 | import { z } from "zod"; 27 | import { StateGraph, END, START} from "@langchain/langgraph"; 28 | import { MemorySaver, Annotation, MessagesAnnotation, messagesStateReducer } from "@langchain/langgraph"; 29 | import { ToolNode } from "@langchain/langgraph/prebuilt"; 30 | 31 | import { ChatWatsonx } from "@langchain/community/chat_models/ibm"; 32 | 33 | 34 | ///////////////////////////////// 35 | 36 | //Pre configured required environment variables 37 | process.env.APPLICATION_NAME="LoanRisk-AIAgent"; 38 | process.env.WATSONX_AI_AUTH_TYPE="iam" 39 | process.env.IBM_IAM_TOKEN_ENDPOINT="https://iam.cloud.ibm.com/identity/token" 40 | 41 | //Set these REQUIRED environment variables as part of the deployment 42 | 43 | //REQUIRED environment variables 44 | //process.env.APPLICATION_PORT="8080"; //default is 8080. If using Docker, it must be same in Dockerfile. 45 | 46 | //REQUIRED environment variables when using IBM Cloud watsonx.ai platform LLMs 47 | //process.env.WATSONX_AI_APIKEY="xxxxxxxxxxx" 48 | //process.env.WATSONX_SERVICE_URL="https://us-south.ml.cloud.ibm.com" //default is us-south region 49 | //process.env.WATSONX_PROJECT_ID="xxx-xxx-xxx-xxx-xxx" 50 | 51 | //Optional - For using RAG LLM set rag llm watsonx environment variables to set in deployment 52 | //process.env.ENABLE_RAG_LLM = "true" //default is false. true requires WATSONX_RISK_RAG_LLM_ENDPOINT; 53 | //process.env.WATSONX_RISK_RAG_LLM_ENDPOINT="https://private.us-south.ml.cloud.ibm.com/ml/v4/deployments/xxx-xxx-xxx-xxx-xxx/ai_service?version=2021-05-01"; 54 | 55 | //Optional - For using watsonx Assistant. Creates a /wx.html webpage with the chat widget 56 | //process.env.ENABLE_WXASST="true" //default is false. true requires the other WXASST_ variables. These are available in the Embed script of the watsonx assistant 57 | //process.env.WXASST_INTEGRATION_ID="xxx-xxx-xxx-xx-xx" 58 | //process.env.WXASST_REGION="xx-xxx" 59 | //process.env.WXASST_SERVICE_INSTANCE_ID="xxx-xxxx-xxx-xx-x" 60 | 61 | //environment variables set by functions in the code 62 | //process.env.IBM_IAM_TOKEN="to be set by function" 63 | //process.env.IBM_IAM_TOKEN_EXPIRATION="to be set by function" 64 | 65 | //Validate the environment variables. Set defaults when not provided. 66 | if (process.env.WATSONX_AI_APIKEY) { 67 | console.log("Setting process.env.WATSONX_AI_APIKEY from envars:", process.env.WATSONX_AI_APIKEY); 68 | } else { 69 | console.error("WATSONX_AI_APIKEY envar is not set."); 70 | } 71 | 72 | if (process.env.WATSONX_PROJECT_ID) { 73 | console.log("Setting process.env.WATSONX_PROJECT_ID from envars:", process.env.WATSONX_PROJECT_ID); 74 | } else { 75 | console.error("WATSONX_PROJECT_ID envar is not set."); 76 | } 77 | 78 | if (process.env.APPLICATION_PORT) { 79 | console.log("Setting process.env.APPLICATION_PORT from envars:", process.env.APPLICATION_PORT); 80 | } else { 81 | process.env.APPLICATION_PORT="8080"; 82 | console.log("Using default process.env.APPLICATION_PORT:", process.env.APPLICATION_PORT); 83 | } 84 | 85 | if (process.env.WATSONX_SERVICE_URL) { 86 | console.log("Setting process.env.WATSONX_SERVICE_URL from envars:", process.env.WATSONX_SERVICE_URL); 87 | } else { 88 | process.env.WATSONX_SERVICE_URL="https://us-south.ml.cloud.ibm.com"; 89 | console.log("Using default process.env.WATSONX_SERVICE_URL:", process.env.WATSONX_SERVICE_URL); 90 | } 91 | 92 | if (process.env.ENABLE_RAG_LLM) { 93 | console.log("Using process.env.ENABLE_RAG_LLM from envars:", process.env.ENABLE_RAG_LLM); 94 | if (process.env.ENABLE_RAG_LLM.toLowerCase()==='true') { 95 | //console.log("Setting process.env.ENABLE_RAG_LLM from envars:", process.env.ENABLE_RAG_LLM); 96 | console.log("Requires WATSONX_RISK_RAG_LLM_ENDPOINT from envars. Uses bearer token using WATSONX_AI_APIKEY from envars."); 97 | if (process.env.WATSONX_RISK_RAG_LLM_ENDPOINT) { 98 | console.log("Setting process.env.WATSONX_RISK_RAG_LLM_ENDPOINT from envars:", process.env.WATSONX_RISK_RAG_LLM_ENDPOINT); 99 | } else { 100 | console.error("WATSONX_RISK_RAG_LLM_ENDPOINT envar is not set."); 101 | } 102 | } 103 | } else { 104 | process.env.ENABLE_RAG_LLM="false"; 105 | console.log("Using default process.env.ENABLE_RAG_LLM:", process.env.ENABLE_RAG_LLM); 106 | } 107 | 108 | if (process.env.ENABLE_WXASST) { 109 | if (process.env.ENABLE_WXASST.toLowerCase()==='true') { 110 | console.log("Setting up watsonx Assistant widget for page /wx.html. Envar ENABLE_WXASST:", process.env.ENABLE_WXASST); 111 | if (process.env.WXASST_INTEGRATION_ID && process.env.WXASST_REGION && process.env.WXASST_SERVICE_INSTANCE_ID) { 112 | fs.readFile('public/wx-template.html', 'utf8', function (err,data) { 113 | if (err) { 114 | return console.log(err); 115 | } 116 | var result1 = data.replace('[[[WXASST_INTEGRATION_ID]]]', process.env.WXASST_INTEGRATION_ID ); 117 | var result2 = result1.replace('[[[WXASST_REGION]]]', process.env.WXASST_REGION ); 118 | var result3 = result2.replace('[[[WXASST_SERVICE_INSTANCE_ID]]]', process.env.WXASST_SERVICE_INSTANCE_ID ); 119 | 120 | fs.writeFile('public/wx.html', result3, 'utf8', function (err) { 121 | if (err) return console.log(err); 122 | }); 123 | }); 124 | fs.readFile('public/wx-template2.html', 'utf8', function (err,data) { 125 | if (err) { 126 | return console.log(err); 127 | } 128 | var result1 = data.replace('[[[WXASST_INTEGRATION_ID]]]', process.env.WXASST_INTEGRATION_ID ); 129 | var result2 = result1.replace('[[[WXASST_REGION]]]', process.env.WXASST_REGION ); 130 | var result3 = result2.replace('[[[WXASST_SERVICE_INSTANCE_ID]]]', process.env.WXASST_SERVICE_INSTANCE_ID ); 131 | 132 | fs.writeFile('public/wx-detailed.html', result3, 'utf8', function (err) { 133 | if (err) return console.log(err); 134 | }); 135 | }); 136 | } else { 137 | console.log("Unable to set up watsonx Assistant widget. Missing one or more envars from WXASST_INTEGRATION_ID, WXASST_REGION, WXASST_SERVICE_INSTANCE_ID,"); 138 | } 139 | } 140 | } 141 | 142 | 143 | const agentic_instructions = { 144 | credit_score_tool:'Get the credit score for the customer using the customer id. Customer\'s name can be used instead of customer\'s id. If the credit score is already known do not retrieve again.', 145 | account_status_tool: 'Get the account status for the customer using customer id. Customer\'s name can be used instaed of customer\'s id. If the account status is already known do not retrieve again.', 146 | overall_risk_tool: 'Get overall risk based on combination of both credit score and account status. Explain how the overall risk was calculated. If the credit score and account status are not known then do not provide the risk status and first retrieve the missing credit score or account status.', 147 | overall_risk_from_rag_llm_tool: 'Get overall risk based on combination of both credit score and account status. Explain how the overall risk was calculated. If the credit score and account status are not known then do not provide the risk status and first retrieve the missing credit score or account status.', 148 | overall_risk_from_rag_llm_tool_prompt: "what is the risk for credit score {credit_score} and account status {account_status}, and how is it determined?", 149 | interest_rate_tool: 'Get interest rate percentage based on overall risk. Explain how the interest rate was determined. If the overall risk is not known then do not provide the interest rate status and first retrieve the overall risk.', 150 | interest_rate_from_rag_llm_tool_prompt: "what is the interest rate for overall risk {overall_risk} and how was it determined?", 151 | interest_rate_from_rag_llm_tool: 'Get interest rate percentage based on overall risk. If the overall risk is not known then do not provide the interest rate status and first retrieve the overall risk. Explain how the interest rate was determined.', 152 | 153 | //NOTE: Also - tbd -- There are additional descriptions for the tool input argumets that impact tool use. 154 | //eg schema: z.object({ customer_id: z.string().describe("Customer's id"), 155 | 156 | model: 'watsonx-ChatWatsonx', 157 | model_minTokens: 150, //not applicable to some models 158 | model_maxTokens: 250, 159 | model_temperature: 0.5, // 0 deterministic ie greedy mode 160 | model_randomSeed: 123, // if temperature is not 0 161 | model_topP: 1, //0-1 nucleus sampling. ideally not recommneded to use with temperature 162 | model_topK: 25 // 1-100 lower value keeps on topic 163 | //NOTE: check model details for which properties are supported by it. 164 | } 165 | 166 | ///////////////////////////////// 167 | 168 | const webapp: Application = express(); 169 | 170 | // Serve static files from the 'public' directory 171 | webapp.use(express.static(path.join(__dirname, 'public'))); 172 | 173 | //webapp.use(express.static("public")); 174 | webapp.use(express.json()); 175 | 176 | // Define a route for the home page 177 | webapp.get('/', async (req: Request, res: Response) => { 178 | const options = { 179 | root: path.join(__dirname) 180 | }; 181 | //res.sendFile(path.join(__dirname, 'public', 'index-single-agent.html')); 182 | res.sendFile('public/index-single-agent.html', options); 183 | 184 | }); 185 | 186 | //Define a route for the ai agent application graph processing 187 | webapp.post('/callagent', async (req: Request, res: Response) => { 188 | 189 | console.log('Received request on callagent endpoint.'); 190 | //console.log(req.body); 191 | const input_post_body=req.body; 192 | //app.use(express.json()) is set in the code and so by default parse all input as JSON 193 | //changes all input to json already 194 | //https://expressjs.com/en/api.html#express.json 195 | 196 | //console.log(`request body: ${input_post_body}`); 197 | console.log('input_post_body:'); 198 | console.log(input_post_body); 199 | 200 | const query = input_post_body.query; //"what is the overall risk for credit score 444?" ////req.query.q; // 'hello' 201 | 202 | console.log("Setting access_token."); 203 | await get_ibm_iam_token(); 204 | 205 | //Get input to the graph 206 | console.log("Input query for agent: ", query); 207 | 208 | const query_response = await runAppWithQuery(query); 209 | res.send(query_response); 210 | 211 | 212 | }); 213 | 214 | ///////////////////////////////// 215 | 216 | 217 | const makePostRequestCallByType = async (calltype: string, post_data: any): Promise => { 218 | //async function makePostRequest(url: string, data: any): Promise { 219 | try { 220 | 221 | const post_params = { 222 | url: '', 223 | headers: {}, 224 | data: post_data 225 | }; 226 | 227 | if ( calltype === 'get_token') { 228 | post_params.url = process.env.IBM_IAM_TOKEN_ENDPOINT; 229 | post_params.headers = { 230 | 'Accept': 'application/json', 231 | 'Content-Type': 'application/x-www-form-urlencoded' 232 | } 233 | } // end if ( calltype === 'get_token') { 234 | 235 | if ( calltype === 'get_risk_from_rag_llm' || calltype === 'get_interest_rate_from_rag_llm' ) { 236 | post_params.url = process.env.WATSONX_RISK_RAG_LLM_ENDPOINT; 237 | post_params.headers = { 238 | 'Accept': 'application/json', 239 | 'Content-Type': 'application/json;charset=UTF-8', 240 | 'Authorization': 'Bearer ' + process.env.IBM_IAM_TOKEN 241 | } 242 | 243 | } // end if ( calltype === 'get_risk_from_rag_llm') { 244 | 245 | 246 | const response = await fetch(post_params.url, { 247 | method: 'POST', 248 | headers: post_params.headers, 249 | body: post_params.data, //JSON.stringify(data), 250 | }); 251 | 252 | if (!response.ok) { 253 | console.log('makePostRequestCallByType ERROR:',response); 254 | throw new Error('Network response was not ok'); 255 | } 256 | 257 | return response.json(); 258 | 259 | 260 | 261 | } catch (error) { 262 | console.error('Error making POST request:', error); 263 | throw error; 264 | } 265 | } 266 | 267 | const get_ibm_iam_token = async () => { 268 | 269 | const data="grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=" + process.env.WATSONX_AI_APIKEY; 270 | 271 | var access_token=""; 272 | 273 | if ( process.env.IBM_IAM_TOKEN_EXPIRATION === undefined || process.env.IBM_IAM_TOKEN_EXPIRATION === null || parseInt(process.env.IBM_IAM_TOKEN_EXPIRATION) < ( (Date.now()/1000)-300) ) { 274 | // Token has expired 275 | console.log("Token has expired process.env.IBM_IAM_TOKEN_EXPIRATION, Date.now()/1000 :",process.env.IBM_IAM_TOKEN_EXPIRATION,Date.now()/1000); 276 | console.log("Token has expired. Getting new token."); 277 | await makePostRequestCallByType('get_token',data) 278 | .then((responseData) => { 279 | //console.log('Response:', responseData); 280 | access_token=responseData.access_token; 281 | //access_token_expiration=responseData.expiration; 282 | process.env.IBM_IAM_TOKEN=responseData.access_token; 283 | process.env.IBM_IAM_TOKEN_EXPIRATION=responseData.expiration; 284 | //return responseData.access_token; 285 | }) 286 | .catch((error) => { 287 | console.error('Error:', error); 288 | }); 289 | } else { 290 | // Token is still valid 291 | console.log("Token is valid. process.env.IBM_IAM_TOKEN_EXPIRATION, Date.now()/1000 :",process.env.IBM_IAM_TOKEN_EXPIRATION,Date.now()/1000); 292 | console.log("Existing token is valid."); 293 | access_token=process.env.IBM_IAM_TOKEN; 294 | } 295 | 296 | 297 | 298 | return access_token; 299 | 300 | }; 301 | 302 | 303 | const setupTools = async () => { 304 | 305 | function getRndInteger(min, max) { 306 | return Math.floor(Math.random() * (max - min + 1) ) + min; 307 | } 308 | 309 | // Customer information 310 | // customer_id : "loren@ibm.com" || "loren" || "1111" 311 | // credit_score = 455; 312 | // account_status = 'good-standing' ; 313 | 314 | // customer_id : "matt@ibm.com" || "matt" || "2222" 315 | // credit_score = 685; 316 | // account_status = 'closed'; 317 | 318 | // customer_id : "hilda@ibm.com" || "hilda" || "3333" 319 | // credit_score = 825; 320 | // account_status = 'delinquent'; 321 | 322 | const getCreditScore = tool(({ customer_id } ) => { 323 | var credit_score = 0; 324 | customer_id=customer_id.toLowerCase(); 325 | if (customer_id === "loren@ibm.com" || customer_id === "loren" || customer_id ==="1111" ) { 326 | credit_score = 455; 327 | } else if (customer_id === "matt@ibm.com" || customer_id === "matt" || customer_id === "2222" ) { 328 | credit_score = 685; 329 | } else if (customer_id === "hilda@ibm.com" || customer_id === "hilda" || customer_id === "3333" ) { 330 | credit_score = 825; 331 | } else { 332 | credit_score = getRndInteger(300, 850); 333 | } 334 | return credit_score; // 555; 335 | }, { 336 | name: 'get_credit_score', 337 | description: agentic_instructions.credit_score_tool, 338 | schema: z.object({ 339 | //customer_id: z.string().optional().describe("Customer's id"), 340 | customer_id: z.string().describe("Customer's id"), 341 | }) 342 | }) 343 | 344 | 345 | const getAccountStatus= tool(({ customer_id }) => { 346 | const status_list = ['delinquent', 'good-standing', 'closed' ]; 347 | var account_status = 'good-standing'; 348 | customer_id=customer_id.toLowerCase(); 349 | if (customer_id === "loren@ibm.com" || customer_id === "loren" || customer_id ==="1111" ) { 350 | account_status = 'good-standing' ; 351 | } else if (customer_id === "matt@ibm.com" || customer_id === "matt" || customer_id === "2222" ) { 352 | account_status = 'closed'; 353 | } else if (customer_id === "hilda@ibm.com" || customer_id === "hilda" || customer_id === "3333" ) { 354 | account_status = 'delinquent'; 355 | } else { 356 | account_status = status_list[getRndInteger(0, 2)]; 357 | } 358 | return account_status; // ; 359 | }, { 360 | name: 'get_account_status', 361 | description: agentic_instructions.account_status_tool, 362 | schema: z.object({ 363 | //customer_id: z.string().optional().describe("Customer's id"), 364 | customer_id: z.string().describe("Customer's id"), 365 | }) 366 | }) 367 | 368 | 369 | const getOverallRisk= tool(({credit_score, account_status}) => { 370 | let overall_risk = ''; 371 | //console.log('-----------------'); 372 | //console.log('--->',credit_score, account_status); 373 | if (credit_score >= 750 && account_status == 'good-standing') { 374 | overall_risk = 'low'; 375 | } else if (credit_score >= 750 && account_status == 'closed') { 376 | overall_risk = 'medium'; 377 | } else if (credit_score >= 750 && account_status == 'delinquent') { 378 | overall_risk = 'medium'; 379 | } else if (credit_score < 750 && credit_score >=550 && account_status == 'good-standing') { 380 | overall_risk = 'medium'; 381 | } else if (credit_score < 750 && credit_score >=550 && account_status == 'closed') { 382 | overall_risk = 'high'; 383 | } else if (credit_score < 750 && credit_score >=550 && account_status == 'delinquent') { 384 | overall_risk = 'high'; 385 | } else if (credit_score < 550) { 386 | overall_risk = 'high'; 387 | } else { 388 | overall_risk = 'unable to determine'; 389 | } 390 | //console.log('--->,overall_risk'); 391 | return overall_risk; 392 | }, { 393 | name: 'get_overall_risk', 394 | description: agentic_instructions.overall_risk_tool, 395 | schema: z.object({ 396 | credit_score: z.number().describe("Credit score"), 397 | account_status: z.string().describe("Account status") 398 | }) 399 | }) 400 | 401 | 402 | const getInterestRate = tool(({ overall_risk } ) => { 403 | 404 | var interest_rate = 0; 405 | overall_risk=overall_risk.toLowerCase(); 406 | if (overall_risk === "high") { 407 | interest_rate = 8; 408 | } else if (overall_risk === "medium") { 409 | interest_rate = 5; 410 | } else if (overall_risk === "low") { 411 | interest_rate = 3; 412 | } else { 413 | interest_rate = 12 414 | } 415 | return interest_rate; 416 | }, { 417 | name: 'get_interest_rate', 418 | description: agentic_instructions.interest_rate_tool, 419 | schema: z.object({ 420 | //customer_id: z.string().optional().describe("Customer's id"), 421 | overall_risk: z.string().describe("Customer's overall risk"), 422 | }) 423 | }) 424 | 425 | const getOverallRiskFromRAGLLM= tool( async ({credit_score, account_status}) => { 426 | 427 | //Call the deployed RAG LLM API endpoint with above query/message content and the the overall risk description. 428 | const risk_rag_llm_query=agentic_instructions.overall_risk_from_rag_llm_tool_prompt.replace("{credit_score}", credit_score.toString()).replace("{account_status}", account_status);; 429 | 430 | //console.log("risk_rag_llm_query:", risk_rag_llm_query); 431 | //const payload = '{"messages": [{ "role": "user", "content": "' + risk_rag_llm_query + '" }]}'; 432 | const risk_rag_llm_request_obj = {"messages": [{ "role": "user", "content": risk_rag_llm_query }]}; 433 | const risk_rag_llm_request = JSON.stringify(risk_rag_llm_request_obj); 434 | console.log('payload:',risk_rag_llm_request); 435 | 436 | 437 | const risk_rag_llm_response = await makePostRequestCallByType('get_risk_from_rag_llm',risk_rag_llm_request) 438 | .then((responseData) => { 439 | console.log('Response:', responseData); 440 | return responseData; 441 | }) 442 | .catch((error) => { 443 | console.error('Error:', error); 444 | }); 445 | 446 | console.log("risk_rag_llm_response:", risk_rag_llm_response); 447 | //console.log("choices[]:", risk_rag_llm_response.choices); 448 | console.log("risk_rag_llm_response.choices[0].message.content:", risk_rag_llm_response.choices[0].message.content); 449 | 450 | const overall_risk = risk_rag_llm_response.choices[0].message.content; 451 | 452 | //const overall_risk = "The risk is high"; 453 | return overall_risk; 454 | 455 | }, { 456 | name: 'get_overall_risk_from_rag_llm', 457 | description: agentic_instructions.overall_risk_from_rag_llm_tool, 458 | schema: z.object({ 459 | credit_score: z.number().describe("Credit score"), 460 | account_status: z.string().describe("Account status") 461 | }) 462 | }) 463 | 464 | const getInterestRateFromRAGLLM= tool( async ({overall_risk}) => { 465 | 466 | //Call the deployed RAG LLM API endpoint with above query/message content and the interest rate description. 467 | const interest_rate_rag_llm_query=agentic_instructions.interest_rate_from_rag_llm_tool_prompt.replace("{overall_risk}", overall_risk); 468 | 469 | //console.log("interest_rate_rag_llm_query:", interest_rate_rag_llm_query); 470 | //const payload = '{"messages": [{ "role": "user", "content": "' + interest_rate_rag_llm_query + '" }]}'; 471 | const interest_rate_rag_llm_query_request_obj = {"messages": [{ "role": "user", "content": interest_rate_rag_llm_query }]}; 472 | const interest_rate_rag_llm_query_request = JSON.stringify(interest_rate_rag_llm_query_request_obj); 473 | console.log('payload:',interest_rate_rag_llm_query_request); 474 | 475 | 476 | const interest_rate_rag_llm_response = await makePostRequestCallByType('get_interest_rate_from_rag_llm',interest_rate_rag_llm_query_request) 477 | .then((responseData) => { 478 | console.log('Response:', responseData); 479 | return responseData; 480 | }) 481 | .catch((error) => { 482 | console.error('Error:', error); 483 | }); 484 | 485 | console.log("interest_rate_rag_llm_response:", interest_rate_rag_llm_response); 486 | //console.log("choices[]:", interest_rate_rag_llm_response.choices); 487 | console.log("interest_rate_rag_llm_response.choices[0].message.content:", interest_rate_rag_llm_response.choices[0].message.content); 488 | 489 | const interest_rate = interest_rate_rag_llm_response.choices[0].message.content; 490 | 491 | //const interest_rate = 55; 492 | return interest_rate; 493 | 494 | }, { 495 | name: 'get_interest_rate_from_rag_llm', 496 | description: agentic_instructions.interest_rate_from_rag_llm_tool, 497 | schema: z.object({ 498 | overall_risk: z.string().describe("Overall risk") 499 | }) 500 | }) 501 | 502 | 503 | console.log("Checking process.env.ENABLE_RAG_LLM for RAG LLM use in tool.", process.env.ENABLE_RAG_LLM); 504 | if (process.env.ENABLE_RAG_LLM.toLowerCase()==='true') { 505 | console.log("Enabling use of RAG LLM in tool"); 506 | const tools = [getCreditScore, getAccountStatus, getOverallRiskFromRAGLLM, getInterestRateFromRAGLLM ] 507 | return tools; 508 | } else { 509 | const tools = [getCreditScore, getAccountStatus, getOverallRisk, getInterestRate ] 510 | return tools; 511 | } 512 | 513 | }; 514 | 515 | /// 516 | 517 | const setupModelWithTools = async (tools: Array) => { 518 | 519 | 520 | if ( agentic_instructions.model == 'watsonx-ChatWatsonx' ) { 521 | //props for meta-llama/llama-3-2-90b-vision-instruct or other models 522 | console.log('Using watsonx-ChatWatsonx'); 523 | const props = { 524 | minTokens: agentic_instructions.model_minTokens,// 150, 525 | maxTokens: agentic_instructions.model_maxTokens, //250, 526 | temperature: agentic_instructions.model_temperature, //0.5, 527 | randomSeed: agentic_instructions.model_randomSeed //12345 528 | }; 529 | 530 | 531 | const modelWithTools = new ChatWatsonx({ 532 | watsonxAIAuthType: "iam", 533 | model: "ibm/granite-4-h-small", 534 | //model: "meta-llama/llama-3-2-90b-vision-instruct", 535 | //model: "mistralai/mistral-large", #deprecated 536 | //model: "ibm/granite-3-8b-instruct", 537 | //model: "meta-llama/llama-3-1-70b-instruct", 538 | //apikey: process.env.WATSONX_AI_APIKEY, 539 | projectId: process.env.WATSONX_PROJECT_ID, 540 | serviceUrl: process.env.WATSONX_SERVICE_URL, 541 | version: '2024-05-31', 542 | ...props, 543 | }).bindTools(tools); 544 | 545 | return modelWithTools; 546 | }; // end if watsonx-ChatWatsonx', 547 | 548 | //return modelWithTools; //returned in the block. 549 | 550 | }; 551 | 552 | /// 553 | 554 | const setupApp = async (tools: Array, modelWithTools) => { 555 | 556 | 557 | const toolNodeForGraph = new ToolNode(tools) 558 | 559 | const shouldContinue = (state) => { 560 | const { messages } = state; 561 | const lastMessage = messages[messages.length - 1]; 562 | //console.log("lastMessage.tool_calls?.length::::",lastMessage.tool_calls?.length); 563 | if ("tool_calls" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) { 564 | return "tools"; 565 | } 566 | return END; 567 | } 568 | 569 | 570 | const callModel = async (state) => { 571 | const { messages } = state; 572 | const response = await modelWithTools.invoke(messages); 573 | return { messages: response }; 574 | } 575 | 576 | 577 | const workflow = new StateGraph(MessagesAnnotation) 578 | .addNode("agent", callModel) 579 | .addNode("tools", toolNodeForGraph) 580 | .addEdge(START, "agent") 581 | .addConditionalEdges("agent", shouldContinue, ["tools", END]) 582 | .addEdge("tools", "agent"); 583 | 584 | const app = workflow.compile() 585 | 586 | return app; 587 | 588 | 589 | }; 590 | 591 | 592 | //////////////////////////////// 593 | 594 | const tools = await setupTools(); 595 | 596 | const modelWithTools = await setupModelWithTools(tools); 597 | 598 | const app = await setupApp(tools,modelWithTools); 599 | 600 | ///////////////////////////////// 601 | 602 | const runAppWithQuery = async (query: string) => { 603 | 604 | console.log("\n=====START======"); 605 | 606 | const stream = await app.stream( 607 | { 608 | //messages: [{ role: "user", content: query }], 609 | messages: [new HumanMessage({content: query })] 610 | }, 611 | { 612 | streamMode: "values" 613 | } 614 | ) 615 | 616 | const chat_messages=[]; 617 | 618 | for await (const chunk of stream) { 619 | const lastMessage = chunk.messages[chunk.messages.length - 1]; 620 | const type = lastMessage._getType(); 621 | const content = lastMessage.content; 622 | const toolCalls = lastMessage.tool_calls; 623 | console.dir({ 624 | type, 625 | content, 626 | toolCalls 627 | }, { depth: null }); 628 | chat_messages.push({ 629 | type, 630 | content, 631 | toolCalls 632 | }); 633 | } 634 | 635 | //const result = "Result from the agent is the in final message."; 636 | //console.log("Agent result: ", result); 637 | console.log("\n=====END======"); 638 | 639 | return chat_messages; 640 | } 641 | 642 | //////////////////////////// 643 | 644 | webapp.listen(process.env.APPLICATION_PORT, () => { 645 | console.log(`Agentic AI application ${process.env.APPLICATION_NAME} is starting...`); 646 | console.log(`Server is running on http://:${process.env.APPLICATION_PORT}`); 647 | }); 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | -------------------------------------------------------------------------------- /artifacts/python-notebook/NB-ai-agent-loan-risk-demo-v1.ipynb: -------------------------------------------------------------------------------- 1 | {"metadata": {"kernelspec": {"name": "python3", "display_name": "Python 3.11", "language": "python"}, "language_info": {"name": "python", "version": "3.11.9", "mimetype": "text/x-python", "codemirror_mode": {"name": "ipython", "version": 3}, "pygments_lexer": "ipython3", "nbconvert_exporter": "python", "file_extension": ".py"}}, "nbformat_minor": 4, "nbformat": 4, "cells": [{"cell_type": "markdown", "source": "# Tech 2025 Lab\n### Lab A - Loan Risk AI Agent\nAn **AI Agent** refers to a system or program that is capable completing a request by first planning the sequence of workflow steps and then performing the tasks in the steps by utilizing available tools. AI agents utilizes large language models (LLM) to understand the context of the request, plan and perform the tasks. \n\nThis Python Notebook provides the code to create a simple AI agent for risk and interest rate evaluation in a bank loan processing scenario. It uses LangGraph graph for state and runtime processing. Once created, you assume the persona of a loan risk analyst at a bank and ask the Loan Risk AI Agent questions in natural language to assess risks for customers. When a question is asked, the AI agent will use a LLM to understand the context, determine the seqence of steps, and then complete the steps utilizing availble tools. The tools fetch customer information, credit score, account status and determine risk and interest rate. The sequence of steps, tool calling and final response can be reviewed in the output.\n\n#### To use this Notebook:\n\nRun each cell below one by one and make sure it completes successfully.\n\nWhen prompted for API key enter it in the box and hit Enter and then continue running the cells to initialize the code.\n\nFinally, when prompted enter your query and run the subsequent cells. Some example questions are provided.\n\nYou may run/repeat the query cells by entering and trying different queries.", "metadata": {"id": "4be62f6a-4776-4dc9-b6db-9673f06bc388"}}, {"cell_type": "markdown", "source": "#### Lab A - Exercise 1\nBelow is an outline of the code. The code is set up in Python Notebook cells.\n\n1. Set up required libraries\n2. Define functions to get and check credentials\n3. Define tools that the AI agent can use\n4. Configure the LLM\n5. Define the LangGraph graph and functions for state and runtime processing.\n6. Show a visual representation the graph - the AI agent with tools\n7. Use the AI agent - ask the AI agent risk related question\n8. Review the responses from AI agent risk\n\n#### Lab A - Exercise 2\n1. Add another tool that can provide interest rate.\n2. Use the AI agent - ask the AI agent risk and interest rate related question\n3. Review the responses from AI agent risk\n\n", "metadata": {"id": "609b3c27-f6dc-4daf-8101-7a3b38791717"}}, {"cell_type": "markdown", "source": "### Lab A - Exercise 1", "metadata": {"id": "956b5a54-4eb6-424f-83cc-1f6c15af4aa4"}}, {"cell_type": "markdown", "source": "#### 1. Set up required libraries", "metadata": {"id": "1f742f2c-386c-4094-874a-9be788685811"}}, {"cell_type": "code", "source": "# Set up libraries\n%pip install langgraph==0.2.73\n%pip install -U langchain-ibm==0.3.6\n \nfrom typing import Annotated, Literal, TypedDict\n\nfrom langchain_core.messages import HumanMessage\n\nfrom langchain_core.tools import tool\nfrom langgraph.checkpoint.memory import MemorySaver\nfrom langgraph.graph import END, START, StateGraph, MessagesState\nfrom langgraph.prebuilt import ToolNode\n\nimport requests\nimport json\nimport time\nimport random", "metadata": {"id": "ac1bb4df-737d-45fb-9e14-6bdc75bad441", "scrolled": true}, "outputs": [{"name": "stdout", "text": "Collecting langgraph==0.2.73\n Downloading langgraph-0.2.73-py3-none-any.whl.metadata (17 kB)\nCollecting langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43 (from langgraph==0.2.73)\n Downloading langchain_core-0.3.46-py3-none-any.whl.metadata (5.9 kB)\nCollecting langgraph-checkpoint<3.0.0,>=2.0.10 (from langgraph==0.2.73)\n Downloading langgraph_checkpoint-2.0.21-py3-none-any.whl.metadata (4.6 kB)\nRequirement already satisfied: langgraph-sdk<0.2.0,>=0.1.42 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langgraph==0.2.73) (0.1.51)\nRequirement already satisfied: langsmith<0.4,>=0.1.125 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (0.1.126)\nRequirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (8.2.2)\nRequirement already satisfied: jsonpatch<2.0,>=1.33 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (1.33)\nRequirement already satisfied: PyYAML>=5.3 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (6.0.1)\nRequirement already satisfied: packaging<25,>=23.2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (23.2)\nRequirement already satisfied: typing-extensions>=4.7 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (4.11.0)\nRequirement already satisfied: pydantic<3.0.0,>=2.5.2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (2.8.2)\nRequirement already satisfied: msgpack<2.0.0,>=1.1.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langgraph-checkpoint<3.0.0,>=2.0.10->langgraph==0.2.73) (1.1.0)\nRequirement already satisfied: httpx>=0.25.2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (0.27.0)\nRequirement already satisfied: orjson>=3.10.1 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (3.10.2)\nRequirement already satisfied: anyio in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx>=0.25.2->langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (3.5.0)\nRequirement already satisfied: certifi in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx>=0.25.2->langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (2025.1.31)\nRequirement already satisfied: httpcore==1.* in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx>=0.25.2->langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (1.0.2)\nRequirement already satisfied: idna in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx>=0.25.2->langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (3.7)\nRequirement already satisfied: sniffio in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx>=0.25.2->langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (1.3.0)\nRequirement already satisfied: h11<0.15,>=0.13 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpcore==1.*->httpx>=0.25.2->langgraph-sdk<0.2.0,>=0.1.42->langgraph==0.2.73) (0.14.0)\nRequirement already satisfied: jsonpointer>=1.9 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from jsonpatch<2.0,>=1.33->langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (3.0.0)\nRequirement already satisfied: requests<3,>=2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langsmith<0.4,>=0.1.125->langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (2.32.2)\nRequirement already satisfied: annotated-types>=0.4.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (0.6.0)\nRequirement already satisfied: pydantic-core==2.20.1 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (2.20.1)\nRequirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from requests<3,>=2->langsmith<0.4,>=0.1.125->langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (2.0.4)\nRequirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from requests<3,>=2->langsmith<0.4,>=0.1.125->langchain-core!=0.3.0,!=0.3.1,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14,!=0.3.15,!=0.3.16,!=0.3.17,!=0.3.18,!=0.3.19,!=0.3.2,!=0.3.20,!=0.3.21,!=0.3.22,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,<0.4.0,>=0.2.43->langgraph==0.2.73) (1.26.19)\nDownloading langgraph-0.2.73-py3-none-any.whl (151 kB)\n\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m151.5/151.5 kB\u001b[0m \u001b[31m7.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n\u001b[?25hDownloading langchain_core-0.3.46-py3-none-any.whl (417 kB)\n\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m417.1/417.1 kB\u001b[0m \u001b[31m28.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n\u001b[?25hDownloading langgraph_checkpoint-2.0.21-py3-none-any.whl (41 kB)\n\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m41.2/41.2 kB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n\u001b[?25hInstalling collected packages: langchain-core, langgraph-checkpoint, langgraph\n Attempting uninstall: langchain-core\n Found existing installation: langchain-core 0.3.12\n Uninstalling langchain-core-0.3.12:\n Successfully uninstalled langchain-core-0.3.12\n Attempting uninstall: langgraph-checkpoint\n Found existing installation: langgraph-checkpoint 2.0.9\n Uninstalling langgraph-checkpoint-2.0.9:\n Successfully uninstalled langgraph-checkpoint-2.0.9\n Attempting uninstall: langgraph\n Found existing installation: langgraph 0.2.40\n Uninstalling langgraph-0.2.40:\n Successfully uninstalled langgraph-0.2.40\nSuccessfully installed langchain-core-0.3.46 langgraph-0.2.73 langgraph-checkpoint-2.0.21\nNote: you may need to restart the kernel to use updated packages.\nCollecting langchain-ibm==0.3.6\n Downloading langchain_ibm-0.3.6-py3-none-any.whl.metadata (5.2 kB)\nRequirement already satisfied: ibm-watsonx-ai<2.0.0,>=1.1.16 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-ibm==0.3.6) (1.3.0)\nRequirement already satisfied: langchain-core<0.4,>=0.3.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-ibm==0.3.6) (0.3.46)\nRequirement already satisfied: requests in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2.32.2)\nRequirement already satisfied: httpx<=0.28,>=0.27 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (0.27.0)\nRequirement already satisfied: urllib3 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (1.26.19)\nRequirement already satisfied: pandas<2.2.0,>=0.24.2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2.1.4)\nRequirement already satisfied: certifi in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2025.1.31)\nRequirement already satisfied: lomond in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (0.3.3)\nRequirement already satisfied: tabulate in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (0.8.10)\nRequirement already satisfied: packaging in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (23.2)\nRequirement already satisfied: ibm-cos-sdk<2.14.0,>=2.12.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2.13.4)\nRequirement already satisfied: importlib-metadata in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (7.0.1)\nRequirement already satisfied: langsmith<0.4,>=0.1.125 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (0.1.126)\nRequirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (8.2.2)\nRequirement already satisfied: jsonpatch<2.0,>=1.33 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (1.33)\nRequirement already satisfied: PyYAML>=5.3 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (6.0.1)\nRequirement already satisfied: typing-extensions>=4.7 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (4.11.0)\nRequirement already satisfied: pydantic<3.0.0,>=2.5.2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (2.8.2)\nRequirement already satisfied: anyio in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx<=0.28,>=0.27->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (3.5.0)\nRequirement already satisfied: httpcore==1.* in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx<=0.28,>=0.27->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (1.0.2)\nRequirement already satisfied: idna in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx<=0.28,>=0.27->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (3.7)\nRequirement already satisfied: sniffio in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpx<=0.28,>=0.27->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (1.3.0)\nRequirement already satisfied: h11<0.15,>=0.13 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from httpcore==1.*->httpx<=0.28,>=0.27->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (0.14.0)\nRequirement already satisfied: ibm-cos-sdk-core==2.13.4 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-cos-sdk<2.14.0,>=2.12.0->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2.13.4)\nRequirement already satisfied: ibm-cos-sdk-s3transfer==2.13.4 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-cos-sdk<2.14.0,>=2.12.0->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2.13.4)\nRequirement already satisfied: jmespath<=1.0.1,>=0.10.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-cos-sdk<2.14.0,>=2.12.0->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (1.0.1)\nRequirement already satisfied: python-dateutil<3.0.0,>=2.8.2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from ibm-cos-sdk-core==2.13.4->ibm-cos-sdk<2.14.0,>=2.12.0->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2.8.2)\nRequirement already satisfied: jsonpointer>=1.9 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (3.0.0)\nRequirement already satisfied: orjson<4.0.0,>=3.9.14 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from langsmith<0.4,>=0.1.125->langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (3.10.2)\nRequirement already satisfied: numpy<2,>=1.23.2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from pandas<2.2.0,>=0.24.2->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (1.26.4)\nRequirement already satisfied: pytz>=2020.1 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from pandas<2.2.0,>=0.24.2->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2024.1)\nRequirement already satisfied: tzdata>=2022.1 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from pandas<2.2.0,>=0.24.2->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2023.3)\nRequirement already satisfied: annotated-types>=0.4.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (0.6.0)\nRequirement already satisfied: pydantic-core==2.20.1 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<0.4,>=0.3.0->langchain-ibm==0.3.6) (2.20.1)\nRequirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from requests->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (2.0.4)\nRequirement already satisfied: zipp>=0.5 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from importlib-metadata->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (3.20.2)\nRequirement already satisfied: six>=1.10.0 in /opt/conda/envs/Python-RT24.1/lib/python3.11/site-packages (from lomond->ibm-watsonx-ai<2.0.0,>=1.1.16->langchain-ibm==0.3.6) (1.16.0)\nDownloading langchain_ibm-0.3.6-py3-none-any.whl (25 kB)\nInstalling collected packages: langchain-ibm\n Attempting uninstall: langchain-ibm\n Found existing installation: langchain-ibm 0.3.1\n Uninstalling langchain-ibm-0.3.1:\n Successfully uninstalled langchain-ibm-0.3.1\nSuccessfully installed langchain-ibm-0.3.6\nNote: you may need to restart the kernel to use updated packages.\n", "output_type": "stream"}], "execution_count": 1}, {"cell_type": "markdown", "source": "#### 2. Define functions to get and check credentials", "metadata": {"id": "c376e0e3-76f0-4079-b382-995410034157"}}, {"cell_type": "code", "source": "# Function to get credentials\nimport getpass\nimport os\n\ndef _set_if_undefined(var: str):\n if not os.environ.get(var):\n os.environ[var] = getpass.getpass(f\"Please provide your {var}\")\n\n# Get credentials\n# Set the Project IDs\n# If using you own account, or lab account and project, the current project id will be configured and set automtically\n_set_if_undefined(\"PROJECT_ID\")\n\n# When prompted for API key enter it in the box and hit Enter, move to next cell and then continue running the cells\n_set_if_undefined(\"WATSONX_API_KEY\")\n", "metadata": {"id": "52a152ce-7efd-45f2-b5df-4ce5ea5efab7"}, "outputs": [{"output_type": "stream", "name": "stdin", "text": "Please provide your WATSONX_API_KEY \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\n"}], "execution_count": 2}, {"cell_type": "code", "source": "# Print credential configurations for validation\nprint('Api key:',os.environ.get(\"WATSONX_API_KEY\"))\nprint('Project id:',os.environ.get(\"PROJECT_ID\"))", "metadata": {"id": "069d3ba7-2788-4f3d-9083-df43c0495ff8"}, "outputs": [], "execution_count": null}, {"cell_type": "markdown", "source": "#### 3. Define tools that the AI agent can use", "metadata": {"id": "eb45e773-d3c9-42ca-b349-d062d808e596"}}, {"cell_type": "code", "source": "# Define tools that the AI agent can use\n\n# The tool get_customer_info() simulates/mocks API call that fetches credit score and account status information about a customer\n# The tools get_credit_score() and get_account_status() use the customer info tool to get the score and account status\n# The customer's loan risk is determined based on credit score and account status risk rules using get_overall_risk() tool\n# When a natural language question is asked, the AI agent first determines the context and then the sequence to utilize one or more tools to provide a response \n\n# this tool simulates an API call that will fetch information about customers\n@tool\ndef get_customer_info(customer_id: str): \n \"\"\"Retrieve customer information based on the customer_id.\"\"\"\n customer_id = customer_id.lower()\n \n if customer_id in ['loren@ibm.com', 'loren', '1111']:\n return {'credit_score': 455, 'account_status': 'good-standing'}\n elif customer_id in ['matt@ibm.com', 'matt', '2222']:\n return {'credit_score': 685, 'account_status': 'closed'}\n elif customer_id in ['hilda@ibm.com', 'hilda', '3333']:\n return {'credit_score': 825, 'account_status': 'delinquent'}\n else:\n #return {'credit_score': None, 'account_status': None}\n #if any other customer id is used, provide a random credit score and account status\n return {'credit_score': random.randint(300, 850), 'account_status': random.choice(['delinquent', 'good-standing', 'closed' ])}\n\n\n\n# this tool fetches credit score of a customer\n@tool\ndef get_credit_score(customer_id: str) -> int: \n \"\"\"Get the credit score for the customer using customer_id. Customer's name can be used instead of customer_id.\"\"\"\n customer_info = get_customer_info(customer_id)\n return customer_info['credit_score']\n customer_id = customer_id.lower()\n\n\n# this tool fetches account status of a customer\n@tool\ndef get_account_status(customer_id: str) -> str: \n \"\"\"Get the account status for the customer using customer_id. Customer's name can be used instead of customer_id.\"\"\"\n customer_info = get_customer_info(customer_id)\n return customer_info['account_status']\n customer_id = customer_id.lower()\n\n# this tool determines the overall risk for a customer\n@tool\ndef get_overall_risk(credit_score: int, account_status: str):\n \"\"\"Get overall risk based on combination of both credit score and account status. Only use high, medium or low as risk categories. Explain how the overall risk was calculated. If the credit score and account status are unknown then do not provide the risk status and first retrieve the missing credit score or account status.\"\"\"\n\n #print(\"get_overall_risk():credit score:account status::\", credit_score, account_status)\n \n if (credit_score > 299 & credit_score < 674): \n if (account_status == 'delinquent'): overall_risk = 'high'\n elif (account_status == 'closed'): overall_risk = 'high'\n elif (account_status == 'good-standing'): overall_risk = 'medium'\n else: overall_risk = 'unkown'\n \n elif (credit_score >= 675 & credit_score < 749): \n if (account_status == 'delinquent'): overall_risk = 'high'\n elif (account_status == 'closed'): overall_risk = 'medium'\n elif (account_status == 'good-standing'): overall_risk = 'medium'\n else: overall_risk = 'unkown'\n\n elif (credit_score >= 750 & credit_score < 851): \n if (account_status == 'delinquent'): overall_risk = 'high'\n elif (account_status == 'closed'): overall_risk = 'low'\n elif (account_status == 'good-standing'): overall_risk = 'low'\n else: overall_risk = 'unkown'\n \n\n else: overall_risk = 'unable to determine.' \n return overall_risk\n\n# Set the list of tools to be used by the AI agent\ntools = [get_credit_score, get_account_status, get_overall_risk, get_customer_info]\n\ntool_node = ToolNode(tools)\n\n", "metadata": {"id": "4aaf0378-a97b-4774-bc8d-855f75c9e3f9"}, "outputs": [], "execution_count": 4}, {"cell_type": "markdown", "source": "#### 4. Configure the LLM", "metadata": {"id": "479bcf27-bfab-4602-9cbc-842311bb64c6"}}, {"cell_type": "code", "source": "#Configure the LLM\n\nfrom ibm_watsonx_ai.metanames import GenTextParamsMetaNames\nparameters = {\n GenTextParamsMetaNames.DECODING_METHOD: \"sample\", #\"greedy\", #\"sample\"\n GenTextParamsMetaNames.MIN_NEW_TOKENS: 150,\n GenTextParamsMetaNames.MAX_NEW_TOKENS: 250,\n GenTextParamsMetaNames.TEMPERATURE: 0,\n #GenTextParamsMetaNames.TOP_K: 50,\n #GenTextParamsMetaNames.TOP_P: 1,\n}\n\nfrom langchain_ibm import ChatWatsonx\nmodel = ChatWatsonx(\n model_id=\"mistralai/mistral-large\", \n #url=\"https://us-south.ml.cloud.ibm.com\", \n url=\"https://eu-de.ml.cloud.ibm.com\", \n apikey=os.environ.get(\"WATSONX_API_KEY\"),\n project_id=os.environ.get(\"PROJECT_ID\"),\n params=parameters,\n).bind_tools(tools)", "metadata": {"id": "bbe66af5-01dd-44c2-9d61-c7cbc7758cf5"}, "outputs": [], "execution_count": 5}, {"cell_type": "markdown", "source": "#### 5. Define the LangGraph graph and functions for state and runtime processing.", "metadata": {"id": "0a89083d-9436-45ff-851d-6c5698c8b0f7"}}, {"cell_type": "code", "source": "# Define the function that determines whether to continue or not\ndef should_continue(state: MessagesState) -> Literal[\"tools\", END]:\n messages = state['messages']\n last_message = messages[-1]\n # If the LLM makes a tool call, then we route to the \"tools\" node\n if last_message.tool_calls:\n return \"tools\"\n # Otherwise, we stop (reply to the user)\n return END\n\n\n# Define the function that calls the model\ndef call_model(state: MessagesState):\n messages = state['messages']\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}\n\n", "metadata": {"id": "b2959dec-c28c-4ad5-82a0-d5ab8e33c8f8"}, "outputs": [], "execution_count": 6}, {"cell_type": "code", "source": "# Define a new graph\nworkflow = StateGraph(MessagesState)\n\n# Define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"tools\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n)\n\n# add a normal edge from `tools` to `agent`.\n# after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"tools\", 'agent')\n\n\n# Compile graph\napp = workflow.compile()\n", "metadata": {"id": "fd9f88ac-fc71-4939-8f09-8e3e719450e4"}, "outputs": [], "execution_count": 7}, {"cell_type": "markdown", "source": "#### 6. Show a visual representation the graph - the AI agent with tools", "metadata": {"id": "d05e046a-8d73-4a29-aa78-6b1c89b563c5"}}, {"cell_type": "code", "source": "# Show graph\nfrom IPython.display import display, Image\ndisplay(Image(app.get_graph().draw_mermaid_png()))", "metadata": {"id": "225b9451-fed0-4e61-9157-66ac42a0468d"}, "outputs": [{"output_type": "display_data", "data": {"image/png": "iVBORw0KGgoAAAANSUhEUgAAANgAAAD5CAIAAADKsmwpAAAAAXNSR0IArs4c6QAAIABJREFUeJztnWdcFNf+xs9sZTu9dxAEUVQsEYxdY4uIBQsmdm8sNyFGk5jcxMSLxhtzjbEk1mgMKpYgxnLFht3EgoUmIEgvy1K2L9vm/2L9o9ksiLizZ5Y9348vdndmzu9Z9vHMmVN+B8NxHCAQsKHAFoBAAGREBFlARkSQAmREBClARkSQAmREBCmgwRbQEZqVuvoqtUKqU0i1Wi2uVVtBDxSTRaExMDaPxuZT3XzsYMshHdZkRLlEU5gpL86WSeo1PEc6m0dl82h8Rzqwhq5QvQ7UljQrpHI6k1L2WBEQwQnszgnszoWtiyxgVtGhrdfhN0/Wi6qanTwZgRFcr2AWbEWvhUqhe5otryhUVBWrosc7denFg60IPlZgxJw/xJeP1kW/7dRriANsLWZGUq+5eaq+WaEb9Y47i0uFLQcmZDfi5aNCOzbljXHOsIUQiKi6OW1b5ejZ7t5d2LC1QIPURjyfXOseYNc9RgBbiCU4vq3yzThnZ08mbCFwIK8R036sDO7JjYi2CRcaOL6tonuMfXBPW3yCIWk/4rW0Ov9wjk25EAAQt9T7j//VN9aqYQuBABmNmJ8ppdEpPYfYwxYCgYRPfTOOCkl7myIOMhrxytG63sNs0YUAAAzD/MM5N0/WwxZiaUhnxHsXGiNi+EyW7fZl9B7mkPunRCXXwRZiUchlRBzHy/IV0eM7c2dNexg0yeXBlSbYKiwKuYxYnCVnssglCQq+oezsm2LYKiwKuX71p9nygAiOhYN+8sknJ0+e7MCFI0aMqKqqIkARYHGp9s6M6hIlEYWTE3IZsalOE9jd0kbMy8vrwFU1NTVNTQTePUP6cMsLFMSVTzZIZESVXNcoVBP3mJKWlhYfHx8TEzN8+PCVK1fW1tYCAPr06VNVVfX1118PGTIEAKDT6bZv3z5x4sTo6OgxY8asX79eqXxWLY0YMeLgwYPvv//+gAEDrl27Nn78eADAhAkTPvroIyLUcvg0UYUtdSjipEFUpTqwvpSgwjMzM6OiolJTU8vLy7OyshYsWDBnzhwcx2tra6OiolJSUpqamnAc379/f//+/dPT00tLS2/dujV69OgNGzYYSnjrrbcmT578ww8/PHz4UKlUnjt3LioqKi8vTyaTESG4+qnyyPdlRJRMTkg0H1Eu0XH4RFWHRUVFTCbz7bffptFo3t7e69evr66uBgAIBAIAAJvNNrwYM2bMgAEDgoODAQC+vr6jRo26ceOGoQQMw+zs7N5//33DWw6HAwDg8/mGF2aHI6DKxTbUg0MiI+J6nEHYI3OfPn0wDFuwYEFsbGz//v09PT2dnJz+fpq9vf3p06eTkpKEQqFWq1UoFGz28xkxPXr0IEje36HSMIYdiRpOREOir8rm08R1GoIK9/f337t3r7e395YtWyZMmDBnzpzs7Oy/n7Zhw4bdu3fHx8fv2rXr4MGDcXFxLx7lci03HUHWpKXSMIuFgw6JjMjhU+USAm9GXbp0SUpKOn/+/I4dO6hUamJiolr9l6cBnU534sSJ2bNnjx071svLy9nZWSaTEaenbQhtqJAQEhmRzaM5utP1ekLG+7Ozsx89egQAoFKpUVFRixcvbmpqqq9/NqRrmGSg1+t1Op2hsQgAkMvlV69ebXv+AXGzE5oVOhcfG5qbSCIjAgDs2NTiLDkRJd+8eXP58uUXL16sqKjIz89PSUnx8PBwd3dnMplMJjMzMzM/Px/DsNDQ0FOnTlVUVBQWFiYmJsbExEgkkpKSEq1Wa1Qgn88HAFy/fr24uJgIwfn3pB7+1r0055UglxH9u3FKcggx4rx58+Li4jZt2jRlypSlS5fiOL5582YMwwAAc+bMuXDhwpIlS5RK5ZdffqnT6eLj41etWjV9+vSlS5e6u7u/++67QqHQqMCwsLDo6Ojvv//+22+/NbtanRavfKL07WpDKwfINUNbKdOeS66Nfc8LthDIPM2RlRcoB8W5wBZiOchVI7K4NAc3xkMbm3jyd27+Xm9rs9NJ1I9oIOZt5x2fFkUONj0xVqfTDR8+3OQhtVrNYDBMHgoICNi7d69ZZT5n3759+/btM3mIy+W29twdFhb2008/mTz0+K7E1cfO0c30d+mskOvWbODBlSYMwyMHmV7FLJVKTX7e3NzMYDAMzT4jKBQKQeMfhrhG3UAtaDQaOp1u8hCVSn2xq/xFTu2uGjzFhWdv+sLOChmNaPgxur0hsPyUMOjY7BcnVxuxhfELPK+m1tXXNMMWYlEuHRa6+9vZoAvJWyMahp4P/7d80CQXzyCb6E7LOCL07sKy2Tw4JK0RAQAYBZu+0vfWmfq82xLYWohFr8OPb6t0dGfYrAtJXSO2cPOUqCxPEf22c6fs4L1zriH/rnTIVBdbTnxjHUYEANRVNt88KeLwaZ5BrIAIDotj9bMBhOWqsnzF3XONPYfY9xvtSKHY0EQbk1iHEQ1UFCry70qfZstdfJgCZzqHT+PwaWw+Va+HrawdUDEgbtDIxToc4I/vSDl8WnAkp8cgezqDvK0jS2JNRmyh+qlSVKmWS7RyiZaCYQqZOSePKRSK0tLSsLAwM5YJAOA50HEc5wioPEe6dxCLIyDdUAJcrNKIhJKXl7d27drk5GTYQmwLdF9AkAJkRAQpQEY0BsMwX19f2CpsDmREY3AcLysrg63C5kBGNIElV+shDCAjmgDi4j2bBRnRGAzDnJ1tPUGj5UFGNAbHcZFIBFuFzYGMaAyFQgkICICtwuZARjRGr9c/ffoUtgqbAxkRQQqQEY3BMKwl6wjCYiAjGoPjuFhsW4nUyQAyogns7W10uyGIICOagNAs7QiTICMiSAEyojEYhnl52XoWKMuDjGgMjuOVlZWwVdgcyIgIUoCMaAyGYX5+frBV2BzIiMbgOF5aWgpbhc2BjIggBciIxqDZN1BARjQGzb6BAjIighQgIxqDlpNCARnRGLScFArIiAhSgIxoArSu2fIgI5oArWu2PMiIxlAoFG9vb9gqbA5kRGP0en1FRQVsFTYHMiKCFCAjGoNhmKOjI2wVNgcyojE4jjc0NMBWYXMgIxpDoVD8/f1hq7A5kBGN0ev1JSUlsFXYHMiIxqAaEQrIiMagGhEKyIjGUCgUV1dX2CpsDrThzzNmzJghk8kwDFOr1TKZzMHBAcOw5ubm9PR02NJsAlQjPmPMmDFCobCqqkokEqlUqurq6qqqKh7PdvettTDIiM+YPn26j4/Pi59gGDZ48GB4imwLZMRnMBiMiRMnUqnPN+D19fWdMmUKVFE2BDLic+Lj41uy3mAYNnToUA8PD9iibAVkxOcwGIzJkycbKkVfX9+pU6fCVmRDICP+hfj4eE9PT0N16ObmBluODWGV21frdXhTnUZcryGi6yl25KLLly8P7D25OFtu9sLpDMzJg8HmWeWfnVCsrx8x77Yk5w+JSqZzD2ApJObcu94CsHjU0jy5u5/dsGkuyI4vYmVGzPlDUpwlHzTFnULBYGvpOI01zVdTa+KWenH4yIvPsKY2YkGmtOiRfEi8h1W7EADg4M4cM8/7wDdo9fRzrMaIOI5n3RBHT+gko8AMO2rkEMd7FxthCyELVmNEpUzXKNQwWdR2nGsd8Bzo1cVK2CrIgtUYUdKgdfWxg63CnAic6FqNNTXQCcVqjIgBoJRqYaswJ3o9sLqnfuKwGiMiOjfIiAhSgIyIIAXIiAhSgIyIIAXIiAhSgIyIIAXIiAhSgIyIIAXIiAhSgIyIIAXIiAhSgIxoHo6nHVn/7VewVVgxyIjmoaAgD7YE66Yzr5nQ6XT7f9118eLZOpGQzxfERA/+x6IPWCwWAECr1f7408YLF8/qdNpBbw6PiR78xeoVqcfOOTg4arXa5AN7LmWcq62tdnFxmzolIXbCs3wPcZNHvpMwv1ZYcykjXalUdO/ea8Xyfzk5OScuX/TwYSYAID391MkTl9F+QR2gM9eIx347ePDQvnnzluzZlfLxytU3bl7Z/fO2lkMnT6UuWvjPn7btd3Z22b7zB0NCOgDA9h0/HD7ya8KMuXt2H546JWHrtu9On0kzXEWj0Q4d/sXfP/DQgZM/7z5SWPj41+TdAICkNRtDunQdNnRUWuoFDocD9UtbK525RhwxfEzfPgMCA4MBAN7evkOHjPrz9g3DofRzpwbGDBk/Lg4AMH/ektzcrMrKcsOeUyd+P5owc+5bb40HAHh7+RQWPj54aN+4sRMNF/r5BowZPQEA4Orq1q9vdH5+rmHLNCqNRmcwBAJ7qN/YiunMRhQI7M+dP/3dxiSRSKjVapVKBYvFNqzDqqgoGz82ruXMgQOHZt6/AwAoKirQarV9ot5oORQZGXX6TJpCoWCz2QCAwMAuLYd4PL5EKrH41+qcdGYjbtm64fyFMx9+sKpbRCSTwTyU8suljHQAgFwu12q1LDa75Uw+X2B4oVDIAQAffvQPDHu2YtWw7ruhsd5gRCaT+WII617WSiY6rRH1ev2Z/514Z9aCkSPHGj6Ry59t9Uin0wEAKpWq5WTp/1dsHA4XAPD5Z0mBAcEvlubqgvLgEEtnNqJOp2up6uRy+c1bVw2PI0wm09XV7XF+TsvJ169nGF4EBnah0+mNjQ2+g59tLNDU1IhhGIPBeGlE68qZQTY67VMzjUbrEhyafu5UZVVFUVHhZ/9K7N8/RiqVlJWVaLXawYNGXLly4VLGucqqin2/7KgTCQ1Xcbnc8eMn7ftlx6WMc1XVlfcf3F3x8ZL29FTzuLwnT/ILn+RrtZ1qqaHF6LRGBACsXPGlXqebNz9+TdKqSXHTF8xb6ubqvnjpu3Ui4dw57w16c9iG79YsXTZHKpPOmjkPAECj0QEAS977cGLs1J27Ns+eM3n9f1Z3j+j5+aqkl8aKi5suEtW9/8H8lgYA4pWwmiRMtaWqy8fqxi7wace5L0er1cpkUnt7B8Pb/b/uTj2ekpZ6wSyFt5MmofrabzUzP/W1ZFDS0plrxDY4cHDvzFkTLl+5UFlVcf3G5dTjKW+NGg9blE3TaR9W2iZh5ly1unn7jk0NDfWuLm7jxk58952FsEXZNDZqRBqNtnDBsoULlsEWgniGjd6aEWQDGRFBCpAREaQAGRFBCpAREaQAGRFBCpAREaQAGRFBCpAREaQAGRFBCqzGiFQa4DrSYaswJ3ocd3B/+XxbG8FqjOjkyXz6qFNN9RNVqhh2VvP3Jxqr+UNgGBYSxaspVcAWYjYaq9UB3djtONEmsBojAgCGxbtcO1arUnSGTXLuXRDRGCCwO8oJ8QyrmaFtoFmp259U2muYE9ee7uDKsCrtwLDleV2lSlShpDOwQZNcjh07NmXKFNiiSIGVGdHA7u8y2Jg3y44tFmnMXrhep1NrNHZ2hOz75+zJpDOxoB7c4J5cAMDdu3c///zz9PR0ImJZGbi1UVpaumnTJuLK/+qrr4YNG3br1i3iQryIRCLBcTwrK8sy4UiLNbURxWJxfn6+QCD44IMPCAqRm5v78OFDsVh88OBBgkIYwePxDMtYx40bJ5fLLROUhFiNEUUiUVxcXEBAgEAgIC7KoUOHysrKAAAFBQU3btwgLpAR/v7+e/bsKSoqEovFFgtKKqzDiEKhsKys7NKlS+3JuNBh8vLyMjMzDa9FIpHFKkUD7u7uPXr0wDBs2rRpCkXn6aVqJ1ZgxOXLl+M43rt3b6IDHThwoLa2tuVtbm6uJStFA3w+f+3atXfu3LFwXOiQ2og4jt+7dy82NtbNjfAcSLm5uS3VoQGxWJycnEx03L8THBw8ePBgAMDixYvVarXlBUCBvEa8f/++XC7v3r274Vchmv3799fW1ur1+pbnOADA48ePLRC6NRYsWLB48WKIAiwK1Gf2VsnKypo/fz6U0Lm5uQkJCVBCt8aZM2dgSyAcktaIjY2Nu3fvhhXdz88PVmiTuLq6vvPOO7BVEAvpjPjhhx8CAN58801YApRKpVAohBXdJFFRUf/+978BAOXl5bC1EAW5jHj06NG4uLh2nEggSqXSxcUFroa/4+/vDwAoKyv7/vvvYWshBHIZcejQoYMGDYKrQSQSETTQ/PrExMS4uLiUlJTAFmJ+SGFEtVo9ZMgQAICzszNsLUAsFnt5ecFW0SqzZs1yc3PLycl5scuzE0AKI+7bt+/y5cuwVTyjqKjIAt2WrwOLxQoLC5s7d25TUxNsLWYDshF1Ol1tbe2iRYvgyjDC0CAjMxQK5cyZM6WlpZ1mbBqmESUSyYgRI8hW/Zw5cyY8PBy2inYRGRmp0Wj27NkDW4gZgGZEw/BdRkYGLAEmefz48YABAwy7YFgFzs7Ozc3NxcXFsIW8LtD+4rm5uYYHFFJx8+bN0NBQ2CpejSVLlhjth2WNwDHijBkz6HR6yzZj5OHatWsQ+9I7jJeX19mzZ3fs2AFbSMeBYMR79+5t3LgxJCTE8qHbRiwW8/n8Hj16wBbSEUaPHt2zZ8+zZ8/CFtJBLL14SqvVYhhGpVItGbSd/Pzzz0qlcunSpbCF2CIWrRHz8vLmzJlDThcCAFJTUydNmgRbxeuyadOmixcvwlbxyljUiBkZGdu3b7dkxPZz48aNvn37enh4wBbyuiQmJubn51dUVMAW8mpY5bpmIpg2bdratWuDg4PbcS7C/FioRpRKpR9//LFlYnWA8+fPBwQEdCYX5uXlbd26FbaKV8BCRtyyZUv//v0tE6sD/PDDDytWrICtwpyEhYXR6fTTp0/DFtJeLHFr1ul0IpGIbEN5LWzevFkgEMyePRu2EJvGEjUijuOOjo4WCNQBSkpK7ty501ldWF1dnZWVBVtFu7CEEefPn5+fn2+BQB0gMTFx3bp1sFUQhYeHx+rVq0tLS2ELeTmEG1EsFjOZzIiICKIDdYCkpKTZs2f7+JhnM3Jysnnz5qqqKtgqXo7tdt9cvHjxzz///Oyzz2ALQQBL7Nfc1NREo9G4XHKlRi0rK9u6devx48dhC7EEJ06cUKlU06ZNgy2kLQi/Na9fv/7WrVtER3lV4uPjjxw5AluFhYiOjt67dy9sFS+BcCPyeDyyzbxftWrVvn376PROtVlGG7i4uKSkpJA8jY7NtRFXrlw5ZsyYYcOGwRaC+AuE14gVFRVarZboKO1kw4YNUVFRNujCsrKyhIQE2CragnAjfvLJJ0+ePCE6Sns4duyYm5vb9OnTYQuBgK+vr0wma2xshC2kVQg3Ynh4uE4Hf2eUw4cPFxcXv/vuu7CFQOPEiRMODg6wVbSKTbQRf//99/v3769evRq2EJgolUocx9lsku51RXiN2NTUBDchwdmzZ+/cuWPjLgQAXL9+fc2aNbBVtArhRrx79+4333xDdJTWOHbs2NWrVw053WwcPz+/mpoa2CpahfBbs1AonDx5skAgkEqlUqnUKE81oSQnJ/N4vNjYWItFRHQYoob4Fi1a9OjRo5aOG6VSach8mpmZaYH9AQxt88LCwq+//toCsayFhoYG0s7HI+rWvHPnzr/PamEymZZZNfzrr78WFRUhFxoxY8YMkUgEW4VpCGwjLlu2zNPTs+UtjuPh4eE0GuHTLJKTk+vr65cvX050IKvDyclJpVLBVmEaAo04ePDg8ePHczgcw1s7OzsLLFvZuHEjhUJJTEwkOpA1cvDgQW9vb9gqTEPsU/OiRYv69etnSK7l4ODQvXt3QsOtWbPGzc1t5syZhEaxXsgwstAahHffrFu3LigoSK/XCwSCoKAg4gJ9+umnkZGRJB9RhcvcuXNzcnJgqzBNu1psWo1eKdN3NAT28fLV69at69srRtpI1OyH1V+uHjNh+MiRIwkqv3MQERFB2gR2L+lHzLsteXRN3FCjZnFJmrDG8BjE4Ogbq/CACE7vYfYeASzYishF7969MQzDcbwlDyCO4yEhISkpKbClPaetGvH2uQZRlebNSe48RyuYQ4rjuLhOc/m32uhxTn5hJB1RhUJoaGh+fv6LaXC5XO7ChQuhijKm1Tbin2cbxHXaN+PcrMKFAAAMw+xdGeMX+vx5tqE0z+b2O26D6dOns1h/uUv4+fkNHz4cniITmDZio1Atqmx+Y7yrxfWYgeEJHvczyDvxzvLExsa+uHMMm82eO3cuVEUmMG1EUWUzjpMur3A7YTCpTXUaSYMGthASkZCQwGAwDK8DAwOHDh0KW5Expo0oE+tcfEi6DVh78AnlNAqREZ8TGxtr6MrmcDhz5syBLccEpo2oadZrVB3ur4GPrEmD6zr/hN9XIiEhgU6nBwYGknAzB0sssEd0gNLHcmmjViHRqZV6ldI8wyEc8MaQbv/s1q3bhUPm2cSPw6fpdTiHT+Pwqe4BdjyH13qoRUYkEfl3JQX35aW5cs8QvkaDU2lUKp0GKGbrteg3YBwAQGqmHgW5CtOqNfoyNa7HJakiFoca3JPTLZrPFXREMDIiKSi8L72WVu/gyaEyOd1GupBwB5q2ce0ClNLm8qeK3NtVAeHsgROdaPRXGz1GRoSMToef3lMjlwLvSA8Gy4p/DhaPyeIxnQMcGsrFO1c9HTLVJbw/v/2XW/E37wQIy1VHN1UE9ffk+5B0CLgDOPoIHH0EWbfq6iqbB09yaedVVrP7YedDXK8+s1fYbUSAHa/zuLAFt1CXehHlWlp9O89HRoRDTakq7cca/75e7TjXWnH0sRfWgP/90q6lg8iIENBq9KlbKv36dGYXGnDys1fIKXcvvHzEFRkRAqd/rg16o/O70IBTgFNpfnN5obzt05ARLU3OLbFcjjE51jGnySywnflXfntJYxEZ0dLcONngGkjSxcUEweIzKTRa4X1pG+eQyIirv/r4oxWLYasgluybYic/Ho1J0unuD7Mvrviiv1xu/lxFTgGOOX/I2jjBbEY8nnZk/bdfmau0zsrjuzImx4qnNXUYJpveUKNurG01fbLZjFhQkGeuojormmZ9XbmK62SjS2o4zuzirFYrRfOMrCQuX/TwYSYAID391M4dB7oEh2ZlPdi1Z2tBQR6GYWFdIxYu/GdY126Gk0+fSTtyNLmqqoLFYvfvF734vQ8dHZ2MCjx9Ju3YbwerqyuZTLvIHr2XLV3h6krSrfzaT0me3DmAR1z59x+du3LjYG3dUyaT3av7qDEjFjMYdgCA/SmfYRgI7TIg4+p+sbTO1dkvbvwKP5/uAACdTnvizPeZj87ien146MDgwD7EyeO5sGvKWm0mmqdGTFqzMaRL12FDR6WlXggMCC4vL13x8RIXZ9dtW/Zt3byXxWavWLlYKKwFAJw7d/q7/yaNGjnu592H13y1oaDw8arPPjBaSfjo0f3v/ps0edKMPbsPf7PuB7Gk6et/f2oWnXAR12l1GqJmM2TnXjlw9IuQ4H4fLU2eFvfFo5xLx35/lg2QSqU9LX1YVp6TuGT/V5+cZbMFh1OTDIcuXf3lz7tpE8Ykfrhkf4B/zwtXfiZIHgCAzqRVFytbO2oeI3K5XCqNRmcwBAJ7KpV64vdjLBZ71adrgoK6BAV1+XxVklarTT93CgBw9NiBmJjBCTPn+vj49ewZ9c9lKwsKH2dnP3yxtKclRUwmc/Rbb3t5eoeHRaz+Yv3SJR+ZRSdcZE1a4h5TLl3bH+jfe+zIJc5OPmEh0eNGLc18eLZJ/GzqoVqtnDAmkclgMRh2vXuMFopK1GoVAODew/9FhA/u1/ttZyef6H6TQ4IIzAlDt6Op5K3OrSTkqbmgMC+kS9eWfEtsNtvHx6+oqECr1RYVF4aHPU88EhoaDgB4UlTw4uW9evbBMOz9xAWnTh+vrqlydHQKDyPjVn6vikKmI8iIer2+oiovJLhfyyeB/r0BANU1z9LoOzv5GG7TAAA2iw8AUCglWq1GVF/u4xXecpWvdzci5LXA5FDlEtNLOAiZfaNQyJ0cnV/8hM3mKBRypcqQxpnz/HMWGwCgVP5lrqavr//WzXsPHf5l564t0o1rw8Iili1d0Qm8SFxKVI1Gpdfrzl3adT5jz4ufS6TPktDRaH+fV4Gr1UoAAP2FQ0wmsevBcR3e2lRLQozI4XDl8r88H8nlMidHZ5Ydi0KhKBTPR3vkCrnhfKMSgoK6/OuzJJ1Ol5X1YM/eHz/7PPFIypmWdWhWCldArasjJA0SnW5HpdIGvjGtf9SEv0TktNVzTmfYAQCUzc9/KaWyrT7n1wTHcbVKz+aZtpw5b80tzxyhIeH5BXkazbNKWCqTlpWVdO3ajUajBQeFZGU/aLkkN+dRyw26hby87JycRwAAKpXas2fUvLmLxeKmhob2TigiLVx7mlZNiBEpFIqXR9fGpmpXF3/DP0cHLwqFxma3NTWVTmM42HtU1xS2fFJQdJsIeQa0zTo7TqstE7MZkcflPXmSX/gkXyxuio2d2tys+va7NeXlpcXFT5LWfs7hcN8aNR4AMHXqrD/+uH7kaHJNTfX9B3e3bPsuMrJ3178a8c/bNz//YvmVqxcrqyoKn+Snpqa4u3m4ubmbSyos7F3oNCpRayOHDJyVlZtx6eovwrrSyqr8g8dWb9u9SKV6yVSDXt1HZede+eNuWnXNkys3DlRVF7R9/uugVmo9AlvtQzXbrTkubvo36798/4P5X3+1oV/fARv+s23n7i0LFs2gUqndI3p+/98d9vYOAIARw0c3N6uOHE3etXsrh8MdGDPkH//4wKioWQnztFrN9u2bRPV1HA43IiJy/TebrW4Zx9/x78Y5+0uNc6BzO859ZXp0Gzpj8tcZ1/anX9xpZ8f19+2xeN6Pdnactq8aOWyBXNF06uxmPa4PC4kZN2rZ/sOr9Dgh/1vkInmXHq1OATadDex2eoNaBSKHWOvY/KVDVZFvCvy7veRnsDzHt1XR+Dyesy3miCq6WT4l0UvgZHraEYkmPdgCXftxm2XNsFVAQCVTO3szW3MhWjxlacL68m+dKuG7cRks0z9Jdt7VlFTTmyFwWAK5Umzy0BtRE8eP/qe5RD4tfbAn2fQIgl6vo2AlNPAwAAAClklEQVQUYKqZNKDvpHGjlrZWpqi4YeDb9m0ERUa0NG9OdLpzsdGzm+lMayFB/ZYv+dXkIbVa1dIpbQSTac5GiLdnWGsaNJpmKpX+YqrF9miQN6rodNw/vC2RyIiWpksvXuEDuUrabHLxHoNh58jwNHWd5aDTmY4O5tSgapQOnfqSRzTURoTA2Lnuxber9HqbSBNVW1AX2ovl+rLkcsiIcJjxsW/xHxWwVRBObWG9iwclIlrw0jOREeHg4MqY+YlX4fUyndaK0/+1TV1RfVA4fVh8u/IOIyNCg82lT/vIu/B6mbyx1Vl6Vopeq6/MrvEPofUZ4dDOS5ARYcJ3pL/3nyC6Xl7xsFop6ST9i3VPG/Ovlg0cZ9931CsMiKCnZviMmuVWXqC4elzE5DIpDAbfhUPaZX5tIKtXykQKiVAWOch+6pJX3mIMGZEU+ISwEz7xLc2VFzyQF9+udPBgqVV6GoNGZdAwCkkH2SlUikap1ml0ANc3VitdfezCozjhb/i/amZEA8iIJMIvnOMXzgEA1JappI1ahUSrUuibFSTdyZHFxTEKjcNnsvk0jwB3OuO1mnnIiGTEzdfOzRe2CMti2ogMO0wPSHpHaA8cezqFasX6bRDT1SnPgV5XasV9CmV5Mkd3615XYGuYNqKrD9N656EqZVpnLybXHrU6rIlWa0SvYLurv7Ur1yfZuJBc1Xdke/tRESShrf2ac26JCx/IIgc7ObgxqDSyd32rFDqJSH3jhHD0u26uvraY6MiqecnG4U9z5A+uNNU8VVFppL5VC5zpkgaNfzinz0gHB1fUOrQ+XmLEFpqVpB6bx/XAjkP2OhvRBu01IgJBKKgWQZACZEQEKUBGRJACZEQEKUBGRJACZEQEKfg/zsZU4/1PoqEAAAAASUVORK5CYII=", "text/plain": ""}, "metadata": {}}], "execution_count": 8}, {"cell_type": "markdown", "source": "#### 7. Use the AI agent - ask the AI agent risk related question\n", "metadata": {"id": "6f8e1264-ad9d-43b1-a0f6-fc41292de435"}}, {"cell_type": "markdown", "source": "\nAsk the AI agent these questions. Review the response.\n\n- what can you do for me?\n- what can you do for me? how?\n\n- what is the risk for matt?\n \n- what is the risk for matt? explain why?\n\n- tell me about hilda?\n \n- what is the risk for hilda? explain why?\n\n- what is the risk for credit score 774 and account status delinquent?\n\n- what is the risk for credit score 774 and account status delinquent? how was it determined?\n\nAlso try asking questions about interest rate....\n\n- what is the interest rate for matt?\n\n- what is the interest rate for matt? explain how it was determined?\n\nFor the last two questions, note that there is no tool yet that provides interest rate. So the response does not answer the question appropriately. \nWe will add interest rate tool in the next exercise.\n\n#### Write the query in the box and hit Enter and then continue running the next cells", "metadata": {"id": "eb5074ac-de5c-4cc9-ac2c-76b9fd92a900"}}, {"cell_type": "code", "source": "human_query = input(\"Type a question and hit enter: \")", "metadata": {"id": "ca269a6f-1ab0-4950-b27b-5101e52f052b"}, "outputs": [{"output_type": "stream", "name": "stdin", "text": "Type a question and hit enter: what is the interest rate for matt? explain how it was determined?\n"}], "execution_count": 9}, {"cell_type": "code", "source": "# Use the runtime\nfinal_state = app.invoke(\n #{\"messages\": [HumanMessage(content=\"what is the overall risk?\")]},\n {\"messages\": [HumanMessage(content=human_query)]},\n config={\"configurable\": {\"thread_id\": random.randint(1, 100)}}\n)\n\n#final_state[\"messages\"][-1].content\n\nfor m in final_state[\"messages\"]:\n m.pretty_print()\n\n#Print final message\n#final_state[\"messages\"][-1].content\n", "metadata": {"id": "053a334b-5f12-4a92-ab05-7e22e84219c5"}, "outputs": [{"name": "stdout", "text": "================================\u001b[1m Human Message \u001b[0m=================================\n\nwhat is the interest rate for matt? explain how it was determined?\n==================================\u001b[1m Ai Message \u001b[0m==================================\nTool Calls:\n get_customer_info (Wg1kYHe9D)\n Call ID: Wg1kYHe9D\n Args:\n customer_id: matt\n=================================\u001b[1m Tool Message \u001b[0m=================================\nName: get_customer_info\n\n{\"credit_score\": 685, \"account_status\": \"closed\"}\n==================================\u001b[1m Ai Message \u001b[0m==================================\nTool Calls:\n get_overall_risk (tthclTZ3N)\n Call ID: tthclTZ3N\n Args:\n credit_score: 685\n account_status: closed\n=================================\u001b[1m Tool Message \u001b[0m=================================\nName: get_overall_risk\n\nhigh\n==================================\u001b[1m Ai Message \u001b[0m==================================\n\n The overall risk for Matt is high. This is determined by considering both the credit score and account status. A credit score of 685 is considered fair, and a closed account status indicates potential issues with the account. Combining these factors, the overall risk is assessed as high.\n", "output_type": "stream"}], "execution_count": 10}, {"cell_type": "markdown", "source": "#### 8. Review the response above from AI agent risk\nRun/repeat the above cells with different queries, i.e., rerun the human_query and final_state = app.invoke() cells with different queries", "metadata": {"id": "a14b6c66-53f5-4730-8428-24b398177176"}}, {"cell_type": "code", "source": "", "metadata": {"id": "6984b533-9b55-4e89-9bd2-75517e26e4cc"}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "", "metadata": {"id": "3900ccac-113d-4877-82c3-68690f7f3bc7"}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "", "metadata": {"id": "7207c88a-163e-4759-9c1f-c23a87ef6f32"}, "outputs": [], "execution_count": null}, {"cell_type": "markdown", "source": "\n### Lab A - Exercise 2\n", "metadata": {"id": "cdc2ebe1-36d1-4b66-83cd-6d8801a8dfb7"}}, {"cell_type": "markdown", "source": "So far we had tool that provided the risk level for a customer - as low, medium or high. There was no tool to provide a specific interest rate.\nIn this exercise we will add another tool that can provide the interest rate based on the risk level.\n", "metadata": {"id": "d00f3964-30f8-4451-89b6-46fb5a9ba8fe"}}, {"cell_type": "code", "source": "# this tool determines the interest rate for a customer\n@tool\ndef get_interest_rate(overall_risk: str):\n \"\"\"Get interest rate percentage based on the overall risk. If the overall risk is not known then do not provide the interest rate and first retrieve the missing overall risk.\"\"\"\n if (overall_risk.lower() == \"high\"):\n interest_rate=10.75;\n \n elif (overall_risk.lower() == \"medium\"):\n interest_rate=5.25;\n\n elif (overall_risk.lower() == \"low\"):\n interest_rate=3.0;\n \n else: interest_rate = 'unable to determine.' \n return interest_rate\n\n\n# Lets add/include this tool in the list of tools used by the AI agent in the following code line \"tools =...\"\ntools = [get_credit_score, get_account_status, get_overall_risk, get_interest_rate, get_customer_info]\ntool_node = ToolNode(tools)", "metadata": {"id": "907303b3-dbf0-42ec-9ba1-65c4d57a7004"}, "outputs": [], "execution_count": 11}, {"cell_type": "code", "source": "# Re-bind the model configuration with new tools\n\nfrom ibm_watsonx_ai.metanames import GenTextParamsMetaNames\nparameters = {\n GenTextParamsMetaNames.DECODING_METHOD: \"sample\", #\"greedy\", #\"sample\"\n GenTextParamsMetaNames.MIN_NEW_TOKENS: 150,\n GenTextParamsMetaNames.MAX_NEW_TOKENS: 250,\n GenTextParamsMetaNames.TEMPERATURE: 0,\n #GenTextParamsMetaNames.TOP_K: 50,\n #GenTextParamsMetaNames.TOP_P: 1,\n}\n\nfrom langchain_ibm import ChatWatsonx\nmodel = ChatWatsonx(\n model_id=\"mistralai/mistral-large\", \n #url=\"https://us-south.ml.cloud.ibm.com\", \n url=\"https://eu-de.ml.cloud.ibm.com\", \n apikey=os.environ.get(\"WATSONX_API_KEY\"),\n project_id=os.environ.get(\"PROJECT_ID\"),\n params=parameters,\n).bind_tools(tools)", "metadata": {"id": "83b40917-602b-4725-8b64-799788371f55"}, "outputs": [], "execution_count": 12}, {"cell_type": "code", "source": "# Re-define the function that determines whether to continue or not\ndef should_continue(state: MessagesState) -> Literal[\"tools\", END]:\n messages = state['messages']\n last_message = messages[-1]\n # If the LLM makes a tool call, then we route to the \"tools\" node\n if last_message.tool_calls:\n return \"tools\"\n # Otherwise, we stop (reply to the user)\n return END\n\n\n# Re-define the function that calls the model\ndef call_model(state: MessagesState):\n messages = state['messages']\n response = model.invoke(messages)\n # We return a list, because this will get added to the existing list\n return {\"messages\": [response]}", "metadata": {"id": "fa1564b2-b055-4c9c-8ed1-f7cdb037a74b"}, "outputs": [], "execution_count": 13}, {"cell_type": "code", "source": "# Re-define a new graph with new tools\nworkflow = StateGraph(MessagesState)\n\n# Re-define the two nodes we will cycle between\nworkflow.add_node(\"agent\", call_model)\nworkflow.add_node(\"tools\", tool_node)\n\n# Set the entrypoint as `agent`\n# This means that this node is the first one called\nworkflow.add_edge(START, \"agent\")\n\n# add a conditional edge\nworkflow.add_conditional_edges(\n # First, we define the start node. We use `agent`.\n # This means these are the edges taken after the `agent` node is called.\n \"agent\",\n # Next, we pass in the function that will determine which node is called next.\n should_continue,\n)\n\n# add a normal edge from `tools` to `agent`.\n# after `tools` is called, `agent` node is called next.\nworkflow.add_edge(\"tools\", 'agent')\n\n\n# Re-compile graph\napp = workflow.compile()\n", "metadata": {"id": "9803104c-883a-419a-b727-d5b809c22ce8"}, "outputs": [], "execution_count": 14}, {"cell_type": "markdown", "source": "\nAsk the AI agent questions about interest rate. Review the response.\n\n \n- what is the interest rate for matt?\n\n- what is the interest rate for matt? explain how it was determined?\n\nNote that how the AI agent can now use the get_interest_rate to provide interest rate also.\n\n#### Write the query in the box and hit Enter and then continue running the next cells", "metadata": {"id": "7bbd99ea-9163-4e69-a238-cd9bd70b1dd9"}}, {"cell_type": "code", "source": "human_query = input(\"Type a question and hit enter: \")\n", "metadata": {"id": "5799519a-abd5-47cd-b394-35e0554cc744"}, "outputs": [{"output_type": "stream", "name": "stdin", "text": "Type a question and hit enter: what is the interest rate for matt? explain how it was determined?\n"}], "execution_count": 15}, {"cell_type": "code", "source": "# Use the runtime\nfinal_state = app.invoke(\n #{\"messages\": [HumanMessage(content=\"what is the overall risk?\")]},\n {\"messages\": [HumanMessage(content=human_query)]},\n config={\"configurable\": {\"thread_id\": random.randint(1, 100)}}\n)\n\n#final_state[\"messages\"][-1].content\n\nfor m in final_state[\"messages\"]:\n m.pretty_print()\n\n#Print final message\n#final_state[\"messages\"][-1].content", "metadata": {"id": "f9551a68-cd7e-44ab-b820-64e266ad3611"}, "outputs": [{"name": "stdout", "text": "================================\u001b[1m Human Message \u001b[0m=================================\n\nwhat is the interest rate for matt? explain how it was determined?\n==================================\u001b[1m Ai Message \u001b[0m==================================\nTool Calls:\n get_customer_info (c2tBSNKDV)\n Call ID: c2tBSNKDV\n Args:\n customer_id: matt\n=================================\u001b[1m Tool Message \u001b[0m=================================\nName: get_customer_info\n\n{\"credit_score\": 685, \"account_status\": \"closed\"}\n==================================\u001b[1m Ai Message \u001b[0m==================================\nTool Calls:\n get_overall_risk (HywHRDPLv)\n Call ID: HywHRDPLv\n Args:\n credit_score: 685\n account_status: closed\n=================================\u001b[1m Tool Message \u001b[0m=================================\nName: get_overall_risk\n\nhigh\n==================================\u001b[1m Ai Message \u001b[0m==================================\nTool Calls:\n get_interest_rate (WzfpihWZW)\n Call ID: WzfpihWZW\n Args:\n overall_risk: high\n=================================\u001b[1m Tool Message \u001b[0m=================================\nName: get_interest_rate\n\n10.75\n==================================\u001b[1m Ai Message \u001b[0m==================================\n\n The interest rate for Matt is 10.75%. This was determined based on his overall risk, which was assessed as high. The high risk was calculated due to his credit score of 685 and his account status being closed.\n", "output_type": "stream"}], "execution_count": 16}, {"cell_type": "code", "source": "", "metadata": {"id": "d606aec0-2b2f-4cef-b56a-73dddf56a464"}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "", "metadata": {"id": "10e0c369-ead5-45ac-9b3c-c0ee10f3243a"}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "", "metadata": {"id": "802e3518-0156-4350-b553-f819df068056"}, "outputs": [], "execution_count": null}, {"cell_type": "code", "source": "", "metadata": {"id": "9cec3987-9978-4750-8dbd-7c8ea258feb9"}, "outputs": [], "execution_count": null}]} --------------------------------------------------------------------------------