From 55701d67434705096eede5ab1c45e3d79f7ebd5b Mon Sep 17 00:00:00 2001 From: narawat lamaiin Date: Wed, 16 Oct 2024 13:10:17 +0700 Subject: [PATCH] update --- src/interface.jl | 1086 +++++++------------------------------------- src/llmfunction.jl | 10 +- 2 files changed, 166 insertions(+), 930 deletions(-) diff --git a/src/interface.jl b/src/interface.jl index e9639d4..c7827bf 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -123,123 +123,6 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen # """ # end - # _prompt = - # """ - # You are a helpful sommelier working for a wine store. - # Your goal is to recommend the best wine from your inventory that match the user preferences. - # You are also keen to improve your recommendation with lesson(s). - - # You must follow the following criteria: - # 1) Get to know how much the user willing to spend - # 2) Get to know type of wine the user is looking for e.g. red, white, sparkling, rose, dessert, fortified - # 3) Get to know what occasion the user is buying wine for - # 4) Get to know what characteristics of wine the user is looking for - # e.g. tannin, sweetness, intensity, acidity - # 5) Get to know what food the user will have with wine - # 6) Check your inventory for the best wine that match the user preference - # 7) Recommend wine to the user - - # You should only respond with interleaving Thought, Action, Observation steps. - # Thought can reason about the current situation, and Action can be three types: - # 1) winestock[query], which you can use to find wine in your inventory. The more input data the better. - # 2) CHATBOX[text], which you can use to interact with the user. - # After each observation, provide the next Thought and next Action. - - # You should only respond in JSON format as describe below: - # { - # "thought": "your reasoning", - # "action": {"name": "action to take", "input": "action input"}, - # "observation": "result of the action" - # } - - # Here are some examples: - # { - # "question": "I would like to buy a sedan with 8 seats.", - # "thought_1": "Our showroom carries various vehicle model. But I'm not sure whether we have a models that fits the user demand, I need to check our inventory.", - # "action_1": {"name": "inventory", "input": "sedan with 8 seats."}, - # "observation_1": "Several model has 8 seats. Available color are black, red green" - # } - # { - # "thought": "I have a few color for the user to choose from. I will ask him what color he likes.", - # "action": {"name": "CHATBOX", "input": "Which color do you like?"} - # "observation": "I'll take black." - # } - - # $lesson - - # Let's begin! - - # $(JSON3.write(state[:thoughtHistory])) - # {"thought" - # """ - - - # systemmsg = - # """ - # Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for $(a.retailername)'s online store. - # Your goal includes: - # 1) Get to know the user preferences about wine - # 2) Help them select the best wines from your inventory that align with their preferences - - # Your responsibility includes: - # 1) Make an informed decision about what you need to do to achieve the goal - # 2) Thanks the user when they don't need any further assistance and invite them to comeback next time - - # Your responsibility do not include: - # 1) Asking or guiding the user to make a purchase - # 2) Processing sales orders or engaging in any other sales-related activities - - # At each round of conversation, you will be given the current situation: - # Recap: ... - # Context: ... - - # You must follow the following guidelines: - # - Generally speaking, your inventory has some wines from France, the United States, Australia, Spain, and Italy, but you won't know which wines your store carries until you check your inventory. - # - All wines in your inventory are always in stock. - # - If the user interrupts, prioritize the user - # - Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database. - # - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. - - # You should follow the following guidelines: - # - Identifying at least four preferences before checking inventory significantly improves search results - # - Sometimes, the item a user desires might not be available in your inventory. In such cases, inform the user that the item is unavailable and suggest an alternative instead. - - # For your information: - # - vintage 0 means non-vintage. - - # You should then respond to the user with interleaving Understanding, Reasoning, Plan, Action: - # 1) Understanding: - # - State your understanding about the current situation. - # 2) Reasoning: - # - State your step by step reasoning about the current situation. - # 3) Plan: Based on the current situation, state a complete plan to complete the task. Be specific. - # 4) Action_name (Must be aligned with your plan): The name of the action. Typically corresponds to the execution of the first step in your plan. - # Can be one of the following functions: - # - CHATBOX which you can use to talk with the user. The input is your intentions for the dialogue. Be specific. - # - CHECKINVENTORY which you can use to check info about wine in your inventory. The input is a search term in verbal English. - # Good query example: black car, a stereo, 200 mile range, electric motor. - # - PRESENTBOX which you can use to introduce / suggest / recommend a wine label from the inventory to the user when it hasn't been introduced before. Not for general conversation nor follow up conversation. - # The input is instructions on how you want the presentation to be conducted. - # Here are some input examples, - # "First, provide detailed introductions of Zena Crown, Schrader Cabernet Sauvignon. - # Second, if there are multiple wines, offer a thorough comparison of each option, highlighting their differences. - # Third, explain the potential impact each option could bring to the user." - # - ENDCONVERSATION which you can use when you want to finish the conversation with the user. The input is "NA". - # 5) Action_input: input of the action - # 6) Mentioning_winery: Are there any winery name in your response? Can be name of the winery or "None" - - # You should only respond in format as described below: - # Understanding: ... - # Reasoning: ... - # Plan: ... - # Action_name: ... - # Action_input: ... - # Mentioning_winery: ... - - # Let's begin! - # """ - - systemmsg = """ Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for $(a.retailername)'s online store. @@ -292,7 +175,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen Third, explain the potential impact each option could bring to the user." - ENDCONVERSATION which you can use when you believe the user has concluded their interaction, to properly end the conversation with them. Input is "NA". 5) Action_input: input of the action - 6) Mentioning_winery: Are there any winery mentioned in your response? Can be the names of the wineries or "None". + 6) Mentioned_brand: Are there any specific brand mentioned in your response? The answer can be the names of the brand or "None". You should only respond in format as described below: Understanding: ... @@ -300,7 +183,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen Plan: ... Action_name: ... Action_input: ... - Mentioning_winery: ... + Mentioned_brand: ... Let's begin! """ @@ -323,10 +206,12 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen end end - # # get only the latest 3 conversation - # x = length(a.chathistory) < 3 ? length(a.chathistory) : 3 - # x = x - 1 - # chathistory = vectorOfDictToText(a.chathistory[end-x:end]) + shortmem = + if length(a.memory[:shortmem]) > 0 + vectorOfDictToText(a.memory[:shortmem], withkey=false) + else + "" + end errornote = "" response = nothing # placeholder for show when error msg show up @@ -354,7 +239,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen try response = a.text2textInstructLLM(prompt) responsedict = GeneralUtils.textToDict(response, - ["Understanding", "Reasoning", "Plan", "Action_name", "Action_input", "Mentioning_winery"], + ["Understanding", "Reasoning", "Plan", "Action_name", "Action_input", "Mentioned_brand"], rightmarker=":", symbolkey=true, lowercasekey=true) if responsedict[:action_name] ∉ ["CHATBOX", "PRESENTBOX", "CHECKINVENTORY", "ENDCONVERSATION"] @@ -369,7 +254,7 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen end # check if there are more than 1 key per categories - for i ∈ [:understanding, :plan, :action_name, :action_input, :mentioning_winery] + for i ∈ [:understanding, :plan, :action_name, :action_input, :mentioned_brand] matchkeys = GeneralUtils.findMatchingDictKey(responsedict, i) if length(matchkeys) > 1 error("DecisionMaker has more than one key per categories") @@ -379,21 +264,23 @@ function decisionMaker(a::T; recent::Integer=5)::Dict{Symbol,Any} where {T<:agen println("\n~~~ Yiem decisionMaker() ", @__FILE__, " ", @__LINE__) pprintln(Dict(responsedict)) - #[WORKING] check whether winery is from agent's memory - mentioned_winery = strip.(split(responsedict[:mentioning_winery], ",")) - for i in mentioned_winery - if i != "None" && i != "" && !occursin(i, timeline) + # check whether an agent recommend wines before checking inventory or + # recommend wines outside its inventory + mentioned_brand = strip.(split(responsedict[:mentioned_brand], ",")) + for i in mentioned_brand + if i != "None" && i != "" && (!occursin(i, timeline) || !occursin(i, shortmem)) && + responsedict[:action_name] != "CHECKINVENTORY" # OK if the agent is checking inventory + errornote = "Note: Before recommending a wine, ensure it's in your inventory. Check your stock first." error("Before recommending a wine, ensure it's in your inventory. Check your stock first.") end end - - if responsedict[:action_name] == "PRESENTBOX" && mentioned_winery == "None" + if responsedict[:action_name] == "PRESENTBOX" && mentioned_brand == "None" errornote = "Note: Before recommending a wine, ensure it's in your inventory. Check your stock first." error("Before recommending a wine, ensure it's in your inventory. Check your stock first.") end - delete!(responsedict, :mentioning_winery) + delete!(responsedict, :mentioned_brand) return responsedict catch e @@ -432,58 +319,59 @@ julia> function evaluator(config::T1, state::T2 )::Tuple{String,Integer} where {T1<:AbstractDict,T2<:AbstractDict} - systemmsg = """ - Analyze the trajectories of a solution to a question answering task. The trajectories are - labeled by environmental observations about the situation, thoughts that can reason about - the current situation and actions that can be three types: - 1) CHECKINVENTORY[query], which you can use to find wine in your inventory. - 2) CHATBOX[text], which you can use to interact with the user. + systemmsg = + """ + Analyze the trajectories of a solution to a question answering task. The trajectories are + labeled by environmental observations about the situation, thoughts that can reason about + the current situation and actions that can be three types: + 1) CHECKINVENTORY[query], which you can use to find wine in your inventory. + 2) CHATBOX[text], which you can use to interact with the user. - Given a question and a trajectory, evaluate its correctness and provide your reasoning and - analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories - can be correct if the thoughts and actions so far are correct, even if the answer is not found - yet. Do not generate additional thoughts or actions. Then ending with the correctness score s - where s is an integer from 0 to 10. + Given a question and a trajectory, evaluate its correctness and provide your reasoning and + analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories + can be correct if the thoughts and actions so far are correct, even if the answer is not found + yet. Do not generate additional thoughts or actions. Then ending with the correctness score s + where s is an integer from 0 to 10. - You should only respond in JSON format as describe below: - {"evaluation": "your evaluation", "score": "your evaluation score"} + You should only respond in JSON format as describe below: + {"evaluation": "your evaluation", "score": "your evaluation score"} - Here are some examples: - user: - { - "question": "I'm looking for a sedan with an automatic driving feature.", - "thought_1": "I have many types of sedans in my inventory, each with diverse features.", - "thought_2": "But there is only 1 model that has the feature customer wanted.", - "thought_3": "I should check our inventory first to see if we have it.", - "action_1": {"name": "inventory", "input": "Yiem model A"}, - "observation_1": "Yiem model A is in stock." - } - assistant - { - "evaluation": "This trajectory is correct as it is reasonable to check an inventory for info provided in the question. - It is also better to have simple searches corresponding to a single entity, making this the best action.", - "score": 10 - } + Here are some examples: + user: + { + "question": "I'm looking for a sedan with an automatic driving feature.", + "thought_1": "I have many types of sedans in my inventory, each with diverse features.", + "thought_2": "But there is only 1 model that has the feature customer wanted.", + "thought_3": "I should check our inventory first to see if we have it.", + "action_1": {"name": "inventory", "input": "Yiem model A"}, + "observation_1": "Yiem model A is in stock." + } + assistant + { + "evaluation": "This trajectory is correct as it is reasonable to check an inventory for info provided in the question. + It is also better to have simple searches corresponding to a single entity, making this the best action.", + "score": 10 + } - user: - { - "question": "Do you have an all-in-one pen with 4 colors and a pencil for sale?", - "thought_1": "Let me check our inventory first to see if I have it.", - "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."}, - "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}", - "thought_2": "Ok, I have what the user is asking. Let's tell the user.", - "action_2": {"name": "CHATBOX", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"}, - "observation_1": "This is not what I wanted." - } - assistant: - { - "evaluation": "This trajectory is incorrect as my search term should be related to a 4-colors pen with a pencil in it, - not a pen and a pencil seperately. A better search term should have been a 4-colors pen with a pencil, all-in-one.", - "score": 0 - } + user: + { + "question": "Do you have an all-in-one pen with 4 colors and a pencil for sale?", + "thought_1": "Let me check our inventory first to see if I have it.", + "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."}, + "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}", + "thought_2": "Ok, I have what the user is asking. Let's tell the user.", + "action_2": {"name": "CHATBOX", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"}, + "observation_1": "This is not what I wanted." + } + assistant: + { + "evaluation": "This trajectory is incorrect as my search term should be related to a 4-colors pen with a pencil in it, + not a pen and a pencil seperately. A better search term should have been a 4-colors pen with a pencil, all-in-one.", + "score": 0 + } - Let's begin! - """ + Let's begin! + """ usermsg = """ $(JSON3.write(state[:thoughtHistory])) @@ -578,56 +466,57 @@ julia> function reflector(config::T1, state::T2)::String where {T1<:AbstractDict,T2<:AbstractDict} # https://github.com/andyz245/LanguageAgentTreeSearch/blob/main/hotpot/hotpot.py - _prompt = """ - You are a helpful sommelier working for a wine store. - Your goal is to recommend the best wine from your inventory that match the user preferences. - You will be given a question and a trajectory of the previous help you've done for a user. - You were unsuccessful in helping the user either because you guessed the wrong answer with Finish[answer], or you didn't know the user enough. - In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure. - Use complete sentences. + _prompt = + """ + You are a helpful sommelier working for a wine store. + Your goal is to recommend the best wine from your inventory that match the user preferences. + You will be given a question and a trajectory of the previous help you've done for a user. + You were unsuccessful in helping the user either because you guessed the wrong answer with Finish[answer], or you didn't know the user enough. + In a few sentences, Diagnose a possible reason for failure and devise a new, concise, high level plan that aims to mitigate the same failure. + Use complete sentences. - You should only respond in JSON format as describe below: - {"reflection": "your relection"} + You should only respond in JSON format as describe below: + {"reflection": "your relection"} - Here are some examples: - Previous Trial: - { - "question": "Hello, I would like a get a bottle of wine", - "thought_1": "A customer wants to buy a bottle of wine. Before making a recommendation, I need to know more about their preferences.", - "action_1": {"name": "CHATBOX", "input": "What is the occasion for which you're buying this wine?"}, - "observation_1": "We are holding a wedding party", + Here are some examples: + Previous Trial: + { + "question": "Hello, I would like a get a bottle of wine", + "thought_1": "A customer wants to buy a bottle of wine. Before making a recommendation, I need to know more about their preferences.", + "action_1": {"name": "CHATBOX", "input": "What is the occasion for which you're buying this wine?"}, + "observation_1": "We are holding a wedding party", - "thought_2": "A wedding party, that's a great occasion! The customer might be looking for a celebratory drink. Let me ask some more questions to narrow down the options.", - "action_2": {"name": "CHATBOX", "input": "What type of food will you be serving at the wedding?"}, - "observation_2": "It will be Thai dishes.", + "thought_2": "A wedding party, that's a great occasion! The customer might be looking for a celebratory drink. Let me ask some more questions to narrow down the options.", + "action_2": {"name": "CHATBOX", "input": "What type of food will you be serving at the wedding?"}, + "observation_2": "It will be Thai dishes.", - "thought_3": "With Thai food, I should recommend a wine that complements its spicy and savory flavors. And since it's a celebratory occasion, the customer might prefer a full-bodied wine.", - "action_3": {"name": "CHATBOX", "input": "What is your budget for this bottle of wine?"}, - "observation_3": "I would spend up to 50 bucks.", + "thought_3": "With Thai food, I should recommend a wine that complements its spicy and savory flavors. And since it's a celebratory occasion, the customer might prefer a full-bodied wine.", + "action_3": {"name": "CHATBOX", "input": "What is your budget for this bottle of wine?"}, + "observation_3": "I would spend up to 50 bucks.", - "thought_4": "Now that I have some more information, it's time to narrow down the options.", - "action_4": {"name": "winestock", "input": "red wine with full body, pairs well with spicy food, budget \$50"}, - "observation_4": "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n", + "thought_4": "Now that I have some more information, it's time to narrow down the options.", + "action_4": {"name": "winestock", "input": "red wine with full body, pairs well with spicy food, budget \$50"}, + "observation_4": "I found the following wines in our stock: \n{\n 1: El Enemigo Cabernet Franc 2019\n2: Tantara Chardonnay 2017\n\n}\n", - "thought_5": "Now that I have a list of potential wines, I need to know more about the customer's taste preferences.", - "action_5": {"name": "CHATBOX", "input": "What type of wine characteristics are you looking for? (e.g. t.e.g. tannin level, sweetness, intensity, acidity)"}, - "observation_5": "I like full-bodied red wine with low tannin.", + "thought_5": "Now that I have a list of potential wines, I need to know more about the customer's taste preferences.", + "action_5": {"name": "CHATBOX", "input": "What type of wine characteristics are you looking for? (e.g. t.e.g. tannin level, sweetness, intensity, acidity)"}, + "observation_5": "I like full-bodied red wine with low tannin.", - "thought_6": "Now that I have more information about the customer's preferences, it's time to make a recommendation.", - "action_6": {"name": "recommendbox", "input": "El Enemigo Cabernet Franc 2019"}, - "observation_6": "I don't like the one you recommend. I want dry wine." - } + "thought_6": "Now that I have more information about the customer's preferences, it's time to make a recommendation.", + "action_6": {"name": "recommendbox", "input": "El Enemigo Cabernet Franc 2019"}, + "observation_6": "I don't like the one you recommend. I want dry wine." + } - { - "reflection": "I asked the user about the occasion, food type, and budget, and then searched for wine in the inventory right away. However, I should have asked the user for the specific wine type and their preferences in order to gather more information before making a recommendation." - } + { + "reflection": "I asked the user about the occasion, food type, and budget, and then searched for wine in the inventory right away. However, I should have asked the user for the specific wine type and their preferences in order to gather more information before making a recommendation." + } - Let's begin! + Let's begin! - Previous trial: - $(JSON3.write(state[:thoughtHistory])) - {"reflection" - """ + Previous trial: + $(JSON3.write(state[:thoughtHistory])) + {"reflection" + """ # apply LLM specific instruct format externalService = config[:externalservice][:text2textinstruct] @@ -686,139 +575,6 @@ function reflector(config::T1, state::T2)::String where {T1<:AbstractDict,T2<:Ab end - - - -# """ Chat with llm. - -# # Arguments -# `a::agent` -# an agent - -# # Return -# None - -# # Example -# ```jldoctest -# julia> using JSON3, UUIDs, Dates, FileIO, MQTTClient, ChatAgent -# julia> const mqttBroker = "mqtt.yiem.cc" -# julia> mqttclient, connection = MakeConnection(mqttBroker, 1883) -# julia> tools=Dict( # update input format -# "askbox"=>Dict( -# :description => "Useful for when you need to ask the user for more context. Do not ask the user their own question.", -# :input => "Input is a text in JSON format.{\"Q1\": \"How are you doing?\", \"Q2\": \"How may I help you?\"}", -# :output => "" , -# :func => nothing, -# ), -# ) -# julia> msgMeta = Dict( -# :msgPurpose=> "updateStatus", -# :from=> "agent", -# :to=> "llmAI", -# :requestresponse=> "request", -# :sendto=> "", # destination topic -# :replyTo=> "agent/api/v0.1.0/txt/response", # requester ask responseer to send reply to this topic -# :repondToMsgId=> "", # responseer is responseing to this msg id -# :taskstatus=> "", # "complete", "fail", "waiting" or other status -# :timestamp=> Dates.now(), -# :msgId=> "$(uuid4())", -# ) -# julia> a = ChatAgent.agentReflex( -# "Jene", -# mqttclient, -# msgMeta, -# agentConfigTopic, # I need a function to send msg to config topic to get load balancer -# role=:sommelier, -# tools=tools -# ) -# julia> newAgent = ChatAgent.agentReact(agent) -# julia> response = ChatAgent.conversation(newAgent, "Hi! how are you?") -# ``` - -# # TODO -# - [] update docstring -# - [x] MCTS() for planning -# - [] add recap to initialState for earlier completed question -# - [WORKING] conversation loop - -# # Signature -# """ -# function conversation(a::T, userinput::Dict) where {T<:agent} -# config = deepcopy(a.config) -# pprint(config) -# if userinput[:text] == "newtopic" -# clearhistory(a) -# return "Okay. What shall we talk about?" -# else -# # add usermsg to a.chathistory -# addNewMessage(a, "user", userinput[:text]) - -# if isempty(a.plan[:currenttrajectory]) - -# # initial state -# a.plan[:currenttrajectory] = Dict{Symbol, Any}( -# # deepcopy the info to prevent modifying the info unintentionally during MCTS planning -# :customerinfo=> deepcopy(a.keywordinfo[:customerinfo]), -# :storeinfo=> deepcopy(a.keywordinfo[:storeinfo]), -# :userselect=> nothing, -# :reward=> 0, -# :isterminal=> false, -# :evaluation=> nothing, -# :lesson=> nothing, - -# :totalTrajectoryReward=> nothing, - -# # contain question, thought_1, action_1, observation_1, thought_2, ... -# :thoughtHistory=> OrderedDict{Symbol, Any}( -# #[] :recap=>, -# :question=> userinput[:text], -# ), - -# # store conversation for virtual customer because the virtual customer agent is just -# # a function and stateless. -# :virtualCustomerChatHistory=> Vector{Dict{Symbol, Any}}( -# [Dict(:name=> "user", :text=> userinput[:text])] -# ), -# ) -# else -# _, a.plan[:currenttrajectory] = makeNewState(a.plan[:currenttrajectory], -# a.plan[:activeplan][:thoughtHistory], userinput[:text], userinput[:select], -# userinput[:reward], userinput[:isterminal]) -# end -# end - -# while true -# bestNextState, besttrajectory = LLMMCTS.runMCTS(a.plan[:currenttrajectory], -# transition, config, decisionMaker, evaluator, reflector; -# totalsample=2, maxDepth=3, maxiterations=3, explorationweight=1.0) -# a.plan[:activeplan] = bestNextState - -# latestActionKey, latestActionIndice = -# GeneralUtils.findHighestIndexKey(bestNextState[:thoughtHistory], "action") -# actionname = bestNextState[:thoughtHistory][latestActionKey][:name] -# actioninput = bestNextState[:thoughtHistory][latestActionKey][:input] - -# # transition -# if actionname == "CHATBOX" -# # add usermsg to a.chathistory -# addNewMessage(a, "assistant", actioninput) -# return actioninput -# elseif actionname == "recommendbox" -# # add usermsg to a.chathistory -# addNewMessage(a, "assistant", actioninput) -# return actioninput -# else -# _, a.plan[:currenttrajectory] = transition(a, a.plan[:currenttrajectory], a.plan[:activeplan]) -# end -# end -# end - - -function generalconversation(a::T, userinput::Dict) where {T<:agent} - -end - - """ Chat with llm. # Arguments @@ -1048,7 +804,6 @@ julia> # TODO - [] update docs - - [] # Signature """ @@ -1080,11 +835,11 @@ function generatechat(a::sommelier) - If the user interrupts, prioritize the user You should then respond to the user with: - 1) Mentioning_winery: Are there any winery names mentioned in your response? Can be the names of the wineries or "None". - 2) Chat: Given the situation, how would you respond to the user in a way that expresses your thoughts while keeping the conversation going smoothly? + 1) Mentioned_brand: Are there any specific brand mentioned in your response? The answer can be the names of the brand or "None". + 2) Chat: Given the situation, How would you respond to the user to express your thoughts honestly and keep the conversation going smoothly? You should only respond in format as described below: - Mentioning_winery: ... + Mentioned_brand: ... Chat: ... Let's begin! @@ -1123,12 +878,12 @@ function generatechat(a::sommelier) try response = a.text2textInstructLLM(prompt) - responsedict = GeneralUtils.textToDict(response, ["Mentioning_winery", "Chat"], + responsedict = GeneralUtils.textToDict(response, ["Mentioned_brand", "Chat"], rightmarker=":", symbolkey=true, lowercasekey=true) for i ∈ [:chat] if length(JSON3.write(responsedict[i])) == 0 - error("$i is empty ", @__LINE__) + error("$i is empty ", @__FILE__, " ", @__LINE__) end end @@ -1158,16 +913,20 @@ function generatechat(a::sommelier) timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n" end end - mentioned_winery = strip.(split(responsedict[:mentioning_winery], ",")) - for i in mentioned_winery - if i != "None" && i != "" && !occursin(i, timeline) + # check whether an agent recommend wines before checking inventory or + # recommend wines outside its inventory + mentioned_brand = strip.(split(responsedict[:mentioned_brand], ",")) + for i in mentioned_brand + if i != "None" && i != "" && (!occursin(i, timeline) || !occursin(i, shortmem)) && + responsedict[:action_name] != "CHECKINVENTORY" # OK if the agent is checking inventory + errornote = "Note: Before recommending a wine, ensure it's in your inventory. Check your stock first." error("Before recommending a wine, ensure it's in your inventory. Check your stock first.") end end a.memory[:CHATBOX] = "" # delete content because it no longer used. - delete!(responsedict, :mentioning_winery) + delete!(responsedict, :mentioned_brand) result = responsedict[:chat] return result @@ -1182,28 +941,30 @@ function generatechat(a::sommelier) error("generatechat failed to generate an evaluation") end + function generatechat(a::companion) - systemmsg = """ - Your name is $(a.name). You are a helpful assistant. - You are currently talking with the user. - Your goal includes: - 1) Help the user as best as you can + systemmsg = + """ + Your name is $(a.name). You are a helpful assistant. + You are currently talking with the user. + Your goal includes: + 1) Help the user as best as you can - Your responsibility includes: - 1) Given the situation, help the user. + Your responsibility includes: + 1) Given the situation, help the user. - At each round of conversation, you will be given the current situation: - Your ongoing conversation with the user: ... - Context: ... + At each round of conversation, you will be given the current situation: + Your ongoing conversation with the user: ... + Context: ... - You should then respond to the user with: - 1) Chat: Given the situation, what would you say to the user? + You should then respond to the user with: + 1) Chat: Given the situation, what would you say to the user? - You should only respond in format as described below: - Chat: ... + You should only respond in format as described below: + Chat: ... - Let's begin! - """ + Let's begin! + """ chathistory = vectorOfDictToText(a.chathistory) response = nothing # placeholder for show when error msg show up @@ -1253,50 +1014,6 @@ end function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::String - # systemmsg = - # """ - # You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. - # Your goal is: Help the user select the best wines from your inventory that align with the user's preferences. - - # Your responsibility includes: - # 1) Self questioning about the current situation - # 2) Thanks the user when they are done choosing wines and invite them to comeback next time - - # Your responsibility does not include: - # 1) Processing sales orders or engaging in any other sales-related activities. - - # At each round of conversation, you will be given the current situation: - # Your conversation with the user: ... - # Context: ... - - # You must follow the following guidelines: - # - Your question should be specific, self-contained and not require any additional context. - # - Do not generate any question or comments at the end. - # - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. - - # You should follow the following guidelines: - # - Focus on the latest conversation. - # - Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database. - # - All wines in your inventory are always in stock. - - # You should then respond to the user with: - # 1) Reasoning: State your detailed reasoning of the current situation - # 2) Q: Based on the current situation, ask yourself at least two, but no more than five, questions about what you need to do to achieve the goal. - # 3) A: Based on the current situation, answer to yourself the best you can - - # You must only respond in format as described below: - # Reasoning: ... - # Q 1: ... - # A 1: ... - # Q 2: ... - # A 2: ... - # Q 3: ... - # A 3: ... - # ... - - # Let's begin! - # """ - systemmsg = """ Your name is $(a.name). You are a helpful assistant acting as a polite, website-based sommelier for $(a.retailername)'s online store. @@ -1316,7 +1033,6 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St You must follow the following guidelines: - Your question should be specific, self-contained and not require any additional context. - - Do not generate any question or comments at the end. - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. You should follow the following guidelines: @@ -1334,6 +1050,7 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St - State your understanding about the current situation 2) Q: Given the situation, "ask yourself" at least five, but no more than ten, questions 3) A: Given the situation, "answer to yourself" the best you can + - Do not generate any text after the last answer. You must only respond in format as described below: Understanding: ... @@ -1461,214 +1178,35 @@ function generatequestion(a, text2textInstructLLM::Function; recent=nothing)::St end error("generatequestion failed to generate a thought ", response) end -# function generatequestion(a, text2textInstructLLM::Function)::String -# # systemmsg = -# # """ -# # You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. -# # Your goal is: Help the user select the best wines from your inventory that align with the user's preferences. - -# # Your responsibility includes: -# # 1) Self questioning about the current situation -# # 2) Thanks the user when they are done choosing wines and invite them to comeback next time - -# # Your responsibility does not include: -# # 1) Processing sales orders or engaging in any other sales-related activities. - -# # At each round of conversation, you will be given the current situation: -# # Your conversation with the user: ... -# # Context: ... - -# # You must follow the following guidelines: -# # - Your question should be specific, self-contained and not require any additional context. -# # - Do not generate any question or comments at the end. -# # - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. - -# # You should follow the following guidelines: -# # - Focus on the latest conversation. -# # - Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database. -# # - All wines in your inventory are always in stock. - -# # You should then respond to the user with: -# # 1) Reasoning: State your detailed reasoning of the current situation -# # 2) Q: Based on the current situation, ask yourself at least two, but no more than five, questions about what you need to do to achieve the goal. -# # 3) A: Based on the current situation, answer to yourself the best you can - -# # You must only respond in format as described below: -# # Reasoning: ... -# # Q 1: ... -# # A 1: ... -# # Q 2: ... -# # A 2: ... -# # Q 3: ... -# # A 3: ... -# # ... - -# # Let's begin! -# # """ - -# systemmsg = -# """ -# You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. -# Your goal includes: -# 1) Help the user select the best wines from your inventory that align with the user's preferences. -# 2) Thanks the user when they are done choosing wines and invite them to comeback next time - -# Your responsibility includes: -# 1) Ask yourself what to do about the current situation - -# Your responsibility does not include: -# 1) Processing sales orders or engaging in any other sales-related activities. - -# At each round of conversation, you will be given the current situation: -# Your conversation with the user: ... -# Context: ... - -# You must follow the following guidelines: -# - Your question should be specific, self-contained and not require any additional context. -# - Do not generate any question or comments at the end. -# - Once the user has selected their wine, ask the user if they need any further assistance. Do not offer any additional services. If the user doesn't need any further assistance, say goodbye and invite them to come back next time. - -# You should follow the following guidelines: -# - Focus on the latest conversation. -# - Do not ask the user about wine's flavor e.g. floral, citrusy, nutty or some thing similar as these terms cannot be used to search the database. -# - All wines in your inventory are always in stock. - -# You should then respond to the user with: -# 1) thought: State your reasoning about the current situation -# 2) Q: Given the situation, "ask yourself" at least three, but no more than ten, questions. -# 3) A: Given the situation, "answer to yourself" the best you can -# 4) Note: Additional info you want to say. Otherwise, "NA" - -# You must only respond in format as described below: -# thought: ... -# Q1: ... -# A1: ... -# Q2: ... -# A2: ... -# Q3: ... -# A3: ... -# ... -# Note: ... - -# Here are some examples: -# Q: The user is asking for a cappuccino. Do I have it at my cafe? -# A: No I don't. - -# Q: Since I don't have a cappuccino but I have a Late, should I ask if they are okay with that? -# A: Yes, I should. - -# Q: Are they allergic to milk? -# A: According to the situation, since they mentioned a cappuccino before, it seems they are not allergic to milk. - -# Let's begin! -# """ - -# context = -# if length(a.memory[:shortmem]) > 0 -# vectorOfDictToText(a.memory[:shortmem], withkey=false) -# else -# "" -# end -# chathistory = vectorOfDictToText(a.chathistory) -# errornote = "" -# response = nothing # store for show when error msg show up - -# for attempt in 1:10 -# usermsg = -# """ -# Your conversation with the user: $chathistory -# Recap: $(a.memory[:recap]) -# $context -# $errornote -# """ - -# _prompt = -# [ -# Dict(:name=> "system", :text=> systemmsg), -# Dict(:name=> "user", :text=> usermsg) -# ] - -# # put in model format -# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct") -# prompt *= -# """ -# <|start_header_id|>assistant<|end_header_id|> -# """ - -# try -# response = text2textInstructLLM(prompt) -# q_number = count("Q", response) -# if q_number < 3 -# error("too few questions only $q_number questions are generated ", @__FILE__, " ", @__LINE__) -# end -# # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. -# responsedict = GeneralUtils.textToDict(response, -# ["thought", "Q1", "Note"], -# rightmarker=":", symbolkey=true) -# response = "Q1: " * responsedict[:Q1] -# println("--> generatequestion ", @__FILE__, " ", @__LINE__) -# pprintln(response) -# return response -# catch e -# io = IOBuffer() -# showerror(io, e) -# errorMsg = String(take!(io)) -# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) -# println("") -# println("Attempt $attempt. Error occurred: $errorMsg\n$st") -# println("") -# end -# end -# error("generatequestion failed to generate a thought ", response) -# end function generateSituationReport(a, text2textInstructLLM::Function; skiprecent::Integer=0 )::Dict - # systemmsg = - # """ - # You are a helpful assistant in the given timeline. - # You task is to writes a situational report on the current situation. + systemmsg = + """ + You are the assistant being in the given events. + Your task is to writes a summary for each event in an ongoing, interleaving series. + + At each round of conversation, you will be given the situation: + Total events: number of events you need to summarize. + Events timeline: ... + Context: ... - # At each round of conversation, you will be given the current situation: - # Timeline: ... - # Context: ... + You should then respond to the user with: + event: a detailed summary for each event without exaggerated details. + + You must only respond in format as described below: + Event_1: ... + Event_2: ... + ... - # You should then respond to the user with: - # 1) report: State your detailed situational report on the current situation. - # 2) wine_selected: Indicates whether the user selected wine. It can be the name of the selected wine or "None" + Here are some examples: + Event_1: The user ask me about where to buy a toy. + Event_2: I told the user to go to the store at 2nd floor. - # You must only respond in format as described below: - # report: ... - # wine_selected: ... - - # Let's begin! - # """ - - systemmsg = """ - You are the assistant being in the given events. - Your task is to writes a summary for each event in an ongoing, interleaving series. - - At each round of conversation, you will be given the situation: - Total events: number of events you need to summarize. - Events timeline: ... - Context: ... - - You should then respond to the user with: - event: a detailed summary for each event without exaggerated details. - - You must only respond in format as described below: - Event_1: ... - Event_2: ... - ... - - Here are some examples: - Event_1: The user ask me about where to buy a toy. - Event_2: I told the user to go to the store at 2nd floor. - - Let's begin! - """ + Let's begin! + """ if length(a.memory[:events]) <= skiprecent return Dict(:recap => "None") @@ -1714,15 +1252,6 @@ function generateSituationReport(a, text2textInstructLLM::Function; skiprecent:: println("\n~~~ generateSituationReport() ", @__FILE__, " ", @__LINE__) pprintln(response) - - eventcount = count("Event_", response) - - # if eventcount < (length(events)) - # errornote = "Note: You need to summarize event 1) to event $(length(events)))." - # println("the summary covers $eventcount/$(length(events)) ", @__FILE__, " ", @__LINE__) - # else - # return Dict(:recap=> response) - # end return Dict(:recap => response) end error("generateSituationReport failed to generate a thought ", response) @@ -1730,299 +1259,6 @@ end -# function situationtrack(a, text2textInstructLLM::Function)::String - -# systemmsg = -# """ -# You are a helpful assistant acting as a polite, website-based sommelier for an online wine store. - -# Sommelier's general workflow includes: -# 1) Greet the user and introduce yourself. -# 2) Understand the user's needs. -# 3) Looking for the wines that match user's needs in the database. -# 4) Introduce the wines to the user. -# 5) Thanks the user when they are done choosing wines and invite them to comeback next time. - -# Your goal includes: -# 1) Help the user select the best wines from your inventory that align with the user's preferences. - -# Your responsibility includes: -# 1) Ask yourself specifically about how you are progressing towards the goal. - -# At each round of conversation, you will be given the current situation: -# Your conversation with the user: ... -# Context: ... - -# You must follow the following guidelines: -# - Your question should be specific, self-contained and not require any additional context. -# - Do not generate any question or comments at the end. - -# You should follow the following guidelines: -# - Focus on the latest event. - -# You should then respond to the user with: -# 1) Understanding: State your understanding about the current situation -# 2) Q: Given the situation, "ask yourself" at least five, but no more than ten, questions. -# 3) A: Given the situation, "answer to yourself" the best you can -# 4) Note: Additional info you want to say. Otherwise, "NA" - -# You must only respond in format as described below: -# Understanding: ... -# Q1: ... -# A1: ... -# Q2: ... -# A2: ... -# Q3: ... -# A3: ... -# ... -# Note: ... - -# Here are some examples: -# Q: Where am I in the workflow? -# A: According to the situation, ... - -# Q: Am I greeting the user and introducing myself? -# A: According to the situation, no. - -# Q: Did the user tell me what they like? -# A: According to the situation, no. We are just starting the conversation. - -# Q: What do I know about the user? -# A: According to the situation, … - -# Q: Have I searched the database yet? -# A: According to the situation, no. I need more information. - -# Q: Did I introduce the coffee blend varieties to the user yet? -# A: According to the situation, no, I didn't because I have not searched the database yet. - -# Q: What did I find in the database? -# A: According to the situation, I found the following … - -# Let's begin! -# """ - -# timeline = "" -# for (i, event) in enumerate(a.memory[:events]) -# if event[:outcome] === nothing -# timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue])\n" -# else -# timeline *= "$i) $(event[:subject])> $(event[:action_or_dialogue]) $(event[:outcome])\n" -# end -# end - -# context = -# if length(a.memory[:shortmem]) > 0 -# vectorOfDictToText(a.memory[:shortmem], withkey=false) -# else -# "" -# end -# chathistory = vectorOfDictToText(a.chathistory) -# errornote = "" -# response = nothing # store for show when error msg show up - -# for attempt in 1:10 -# usermsg = -# """ -# Events timeline: $timeline -# $context -# $errornote -# """ - -# _prompt = -# [ -# Dict(:name=> "system", :text=> systemmsg), -# Dict(:name=> "user", :text=> usermsg) -# ] - -# # put in model format -# prompt = GeneralUtils.formatLLMtext(_prompt; formatname="llama3instruct") -# prompt *= -# """ -# <|start_header_id|>assistant<|end_header_id|> -# """ - -# try -# response = text2textInstructLLM(prompt) -# q_number = count("Q", response) -# if q_number < 3 -# error("too few questions only $q_number questions are generated ", @__FILE__, " ", @__LINE__) -# end -# # response = string(split(response, "Please")[1]) # LLM usually add comments which is no need. -# responsedict = GeneralUtils.textToDict(response, -# ["Understanding", "Q1", "Note"], -# rightmarker=":", symbolkey=true) -# response = "Q1: " * responsedict[:Q1] -# println("--> situationtrack ", @__FILE__, " ", @__LINE__) -# pprintln(Dict(responsedict)) -# return response -# catch e -# io = IOBuffer() -# showerror(io, e) -# errorMsg = String(take!(io)) -# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) -# println("") -# println("Attempt $attempt. Error occurred: $errorMsg\n$st") -# println("") -# end -# end -# error("situationtrack failed to generate a thought ", response) -# end - - - - - - - - - - - - - - -# """ - -# # Arguments -# - `a::T1` -# one of Yiem's agent -# - `state::T2` -# a game state - -# # Return -# - `evaluation::Tuple{String, Integer}` -# evaluation and score - -# # Example -# ```jldoctest -# julia> -# ``` - -# # TODO -# - [] update docs -# - [] implement the function - -# # Signature -# """ -# function comparer(a::T1, state::T2)::Tuple{String, Integer} where {T1<:agent, T2<:AbstractDict} - -# _prompt = -# """ -# Analyze the trajectories of a solution to a question answering task. The trajectories are -# labeled by environmental observations about the situation, thoughts that can reason about -# the current situation and actions that can be three types: -# 1) winestock[query], which you can use to find wine in your inventory. -# 2) CHATBOX[text], which you can use to interact with the user. -# 3) recommendbox[answer], which returns your wine recommendation to the user. - -# Given a question and a trajectory, evaluate its correctness and provide your reasoning and -# analysis in detail. Focus on the latest thought, action, and observation. Incomplete trajectories -# can be correct if the thoughts and actions so far are correct, even if the answer is not found -# yet. Do not generate additional thoughts or actions. Then ending with the correctness score s -# where s is an integer from 0 to 10. - -# You should only respond in JSON format as describe below: -# {"evaluation": "your evaluation", "score": "your evaluation score"} - -# Here are some examples: -# { -# "question": "I'm looking for a sedan with an automatic driving feature.", -# "thought_1": "I have many types of sedans in my inventory, each with diverse features.", -# "thought_2": "But there is only 1 model that has the feature customer wanted.", -# "thought_3": "I should check our inventory first to see if we have it.", -# "action_1": {"name": "inventory", "input": "Yiem model A"}, -# "observation_1": "Yiem model A is in stock." -# } -# {"evaluation": "This trajectory is correct as it is reasonable to check an inventory for info provided in the question. -# It is also better to have simple searches corresponding to a single entity, making this the best action.", -# "score": 10 -# } - -# { -# "question": "Do you have an all-in-one pen with 4 colors and a pencil for sale?", -# "thought_1": "Let me check our inventory first to see if I have it.", -# "action_1": {"name": "inventory", "input": "pen with 4 color and a pencil."}, -# "observation_1": "I found {1: "Pilot Dr. grip 4-in-1 pen", 2: "Rotting pencil"}", -# "thought_2": "Ok, I have what the user is asking. Let's tell the user.", -# "action_2": {"name": "CHATBOX", "input": "Yes, we do have a Pilot Dr. grip 4-in-1 pen and a Rotting pencil"}, -# "observation_1": "This is not what I wanted." -# } -# {"evaluation": "This trajectory is incorrect as my search term should be related to a 4-colors pen with a pencil in it, -# not a pen and a pencil seperately. A better search term should have been a 4-colors pen with a pencil, all-in-one.", -# "score": 0 -# } - -# Let's begin!: -# $(JSON3.write(state[:thoughtHistory])) -# {"evaluation" -# """ - -# # apply LLM specific instruct format -# externalService = a.config[:externalservice][:text2textinstruct] -# llminfo = externalService[:llminfo] -# prompt = -# if llminfo[:name] == "llama3instruct" -# formatLLMtext_llama3instruct("system", _prompt) -# else -# error("llm model name is not defied yet $(@__LINE__)") -# end - -# msgMeta = GeneralUtils.generate_msgMeta( -# a.config[:externalservice][:text2textinstruct][:mqtttopic], -# senderName= "evaluator", -# senderId= a.id, -# receiverName= "text2textinstruct", -# mqttBroker= a.config[:mqttServerInfo][:broker], -# mqttBrokerPort= a.config[:mqttServerInfo][:port], -# ) - -# outgoingMsg = Dict( -# :msgMeta=> msgMeta, -# :payload=> Dict( -# :text=> prompt, -# :kwargs=> Dict( -# :max_tokens=> 512, -# :stop=> ["<|eot_id|>"], -# ) -# ) -# ) - -# for attempt in 1:5 -# try -# response = GeneralUtils.sendReceiveMqttMsg(outgoingMsg) -# _responseJsonStr = response[:response][:text] -# expectedJsonExample = -# """ -# Here is an expected JSON format: -# {"evaluation": "...", "score": "..."} -# """ -# responseJsonStr = jsoncorrection(a, _responseJsonStr, expectedJsonExample) -# evaluationDict = copy(JSON3.read(responseJsonStr)) - -# # check if dict has all required value -# dummya::AbstractString = evaluationDict[:evaluation] -# dummyb::Integer = evaluationDict[:score] - -# return (evaluationDict[:evaluation], evaluationDict[:score]) -# catch e -# io = IOBuffer() -# showerror(io, e) -# errorMsg = String(take!(io)) -# st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) -# println("") -# @warn "Attempt $attempt. Error occurred: $errorMsg\n$st" -# println("") -# end -# end -# error("evaluator failed to generate an evaluation") -# end - - - - - - diff --git a/src/llmfunction.jl b/src/llmfunction.jl index e673716..b6ca4be 100644 --- a/src/llmfunction.jl +++ b/src/llmfunction.jl @@ -454,8 +454,8 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2< # check if grape_variety is mentioned in the input if !occursin("NA", responsedict[:grape_variety]) && !occursin(responsedict[:grape_variety], input) if attempt < maxattempt - errornote = "You are halucinating, $(responsedict[:grape_variety]) is not mentioned in the input" - error("$(responsedict[:grape_variety]) is not mentioned in the input") + errornote = "$(responsedict[:grape_variety]) is not mentioned in the user query, you must only use the info from the query.." + error("$(responsedict[:grape_variety]) is not mentioned in the user query, you must only use the info from the query.") else responsedict[:grape_variety] = "NA" end @@ -464,8 +464,8 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2< # check if region is mentioned in the input if !occursin("NA", responsedict[:region]) && !occursin(responsedict[:region], input) if attempt < maxattempt - errornote = "You are halucinating, $(responsedict[:region]) is not mentioned in the input" - error("$(responsedict[:region]) is not mentioned in the input") + errornote = "$(responsedict[:region]) is not mentioned in the user query, you must only use the info from the query.." + error("$(responsedict[:region]) is not mentioned in the user query, you must only use the info from the query..") else responsedict[:region] = "NA" end @@ -506,7 +506,7 @@ function extractWineAttributes_1(a::T1, input::T2)::String where {T1<:agent, T2< errorMsg = String(take!(io)) st = sprint((io, v) -> show(io, "text/plain", v), stacktrace(catch_backtrace())) println("") - println("Attempt $attempt. Error occurred: $errorMsg\n$st") + println("Attempt $attempt. Error occurred: $errorMsg\n$st ", @__FILE__, " ", @__LINE__) println("") end end