monadic-chat 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -3
- data/Gemfile.lock +8 -14
- data/README.md +72 -41
- data/apps/chat/chat.json +1 -1
- data/apps/chat/chat.md +13 -10
- data/apps/chat/chat.rb +30 -32
- data/apps/code/code.json +5 -1
- data/apps/code/code.md +11 -8
- data/apps/code/code.rb +30 -30
- data/{samples → apps}/linguistic/linguistic.json +7 -1
- data/apps/linguistic/linguistic.md +46 -0
- data/{samples → apps}/linguistic/linguistic.rb +33 -30
- data/apps/novel/novel.json +5 -1
- data/apps/novel/novel.md +13 -9
- data/apps/novel/novel.rb +30 -30
- data/apps/translate/translate.json +1 -1
- data/apps/translate/translate.md +13 -10
- data/apps/translate/translate.rb +30 -30
- data/bin/monadic-chat +7 -6
- data/doc/img/example-translation.png +0 -0
- data/doc/img/linguistic-html.png +0 -0
- data/lib/monadic_app.rb +18 -9
- data/lib/monadic_chat/formatting.rb +26 -37
- data/lib/monadic_chat/internals.rb +40 -16
- data/lib/monadic_chat/menu.rb +26 -24
- data/lib/monadic_chat/open_ai.rb +6 -6
- data/lib/monadic_chat/version.rb +1 -1
- data/lib/monadic_chat.rb +2 -1
- data/monadic_chat.gemspec +1 -2
- metadata +8 -22
- data/doc/img/monadic-chat-main-menu.png +0 -0
- data/samples/linguistic/linguistic.md +0 -39
@@ -32,12 +32,8 @@ class MonadicApp
|
|
32
32
|
false
|
33
33
|
else
|
34
34
|
replacements.each do |key, value|
|
35
|
-
|
36
|
-
|
37
|
-
@template.gsub!(key, value)
|
38
|
-
when "chat/completions"
|
39
|
-
@template["messages"][0]["content"].gsub!(key, value)
|
40
|
-
end
|
35
|
+
@messages[0]["content"].gsub!(key, value)
|
36
|
+
messages[0]["content"]
|
41
37
|
end
|
42
38
|
true
|
43
39
|
end
|
@@ -68,34 +64,58 @@ class MonadicApp
|
|
68
64
|
when "completions"
|
69
65
|
m = /\n\n```json\s*(\{.+\})\s*```\n\n/m.match(@template)
|
70
66
|
json = m[1].gsub(/(?!\\\\\\)\\\\"/) { '\\\"' }
|
71
|
-
JSON.parse(json)
|
67
|
+
res = JSON.parse(json)
|
68
|
+
res["messages"] = @messages
|
69
|
+
res
|
72
70
|
when "chat/completions"
|
73
|
-
@
|
71
|
+
@messages
|
74
72
|
end
|
75
73
|
end
|
76
74
|
|
77
75
|
def prepare_params(input)
|
78
76
|
params = @params.dup
|
77
|
+
|
78
|
+
@update_proc.call
|
79
79
|
case @method
|
80
80
|
when "completions"
|
81
|
-
|
81
|
+
messages = +""
|
82
|
+
system = +""
|
83
|
+
@messages.each do |mes|
|
84
|
+
role = mes["role"]
|
85
|
+
content = mes["content"]
|
86
|
+
case role
|
87
|
+
when "system"
|
88
|
+
system << "#{content}\n"
|
89
|
+
when "assistant", "gpt"
|
90
|
+
system << "- #{mes["role"].strip}: #{content.sub("\n\n###\n\n", "")}\n\n###\n\n"
|
91
|
+
else
|
92
|
+
messages << "- #{mes["role"].strip}: #{mes["content"]}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
template = @template.dup.sub("{{SYSTEM}}", system)
|
96
|
+
.sub("{{PROMPT}}", input)
|
97
|
+
.sub("{{MESSAGES}}", messages.strip)
|
98
|
+
|
82
99
|
params["prompt"] = template
|
100
|
+
@messages << { "role" => "user", "content" => input }
|
83
101
|
when "chat/completions"
|
84
|
-
@
|
85
|
-
|
102
|
+
@messages << { "role" => "user", "content" => input }
|
103
|
+
@update_proc.call
|
104
|
+
params["messages"] = @messages
|
86
105
|
end
|
106
|
+
|
87
107
|
params
|
88
108
|
end
|
89
109
|
|
90
110
|
def update_template(res)
|
91
111
|
case @method
|
92
112
|
when "completions"
|
93
|
-
|
94
|
-
|
113
|
+
@metadata = res
|
114
|
+
@messages << { "role" => "assistant", "content" => res["response"] }
|
115
|
+
json = res.to_json.strip
|
95
116
|
@template.sub!(/\n\n```json.+```\n\n/m, "\n\n```json\n#{json}\n```\n\n")
|
96
117
|
when "chat/completions"
|
97
|
-
@
|
98
|
-
@template["messages"] = @update_proc.call(@template["messages"])
|
118
|
+
@messages << { "role" => "assistant", "content" => res }
|
99
119
|
end
|
100
120
|
end
|
101
121
|
|
@@ -110,6 +130,7 @@ class MonadicApp
|
|
110
130
|
wait
|
111
131
|
|
112
132
|
params = prepare_params(input)
|
133
|
+
|
113
134
|
print TTY::Cursor.save
|
114
135
|
|
115
136
|
escaping = +""
|
@@ -158,6 +179,7 @@ class MonadicApp
|
|
158
179
|
print "\n"
|
159
180
|
|
160
181
|
update_template(res)
|
182
|
+
set_html if @html
|
161
183
|
end
|
162
184
|
|
163
185
|
def bind_research_mode(input, num_retry: 0)
|
@@ -166,6 +188,7 @@ class MonadicApp
|
|
166
188
|
wait
|
167
189
|
|
168
190
|
params = prepare_params(input)
|
191
|
+
|
169
192
|
print TTY::Cursor.save
|
170
193
|
|
171
194
|
@threads << true
|
@@ -179,7 +202,7 @@ class MonadicApp
|
|
179
202
|
finished = false
|
180
203
|
response = +""
|
181
204
|
spinning = false
|
182
|
-
res = @completion.run(params, num_retry: num_retry) do |chunk|
|
205
|
+
res = @completion.run(params, num_retry: num_retry, tmp_json_file: TEMP_JSON, tmp_md_file: TEMP_MD) do |chunk|
|
183
206
|
if finished && !response_all_shown
|
184
207
|
response_all_shown = true
|
185
208
|
@responses << response.sub(/\s+###\s*".*/m, "")
|
@@ -265,5 +288,6 @@ class MonadicApp
|
|
265
288
|
break
|
266
289
|
end
|
267
290
|
end
|
291
|
+
set_html if @html
|
268
292
|
end
|
269
293
|
end
|
data/lib/monadic_chat/menu.rb
CHANGED
@@ -9,16 +9,16 @@ class MonadicApp
|
|
9
9
|
clear_screen
|
10
10
|
print TTY::Cursor.save
|
11
11
|
parameter = PROMPT_SYSTEM.select("Select function:", per_page: 10, cycle: true, filter: true, default: 1, show_help: :never) do |menu|
|
12
|
-
menu.choice "#{BULLET} #{PASTEL.bold("cancel/return/escape")}
|
13
|
-
menu.choice "#{BULLET} #{PASTEL.bold("params/settings/config")}
|
14
|
-
menu.choice "#{BULLET} #{PASTEL.bold("data/context")}
|
15
|
-
menu.choice "#{BULLET} #{PASTEL.bold("html")}
|
16
|
-
menu.choice "#{BULLET} #{PASTEL.bold("reset")}
|
17
|
-
menu.choice "#{BULLET} #{PASTEL.bold("save")}
|
18
|
-
menu.choice "#{BULLET} #{PASTEL.bold("load")}
|
19
|
-
menu.choice "#{BULLET} #{PASTEL.bold("clear/clean")}
|
20
|
-
menu.choice "#{BULLET} #{PASTEL.bold("readme/documentation")}
|
21
|
-
menu.choice "#{BULLET} #{PASTEL.bold("exit/bye/quit")}
|
12
|
+
menu.choice "#{BULLET} #{PASTEL.bold("cancel/return/escape")} Cancel this menu", "cancel"
|
13
|
+
menu.choice "#{BULLET} #{PASTEL.bold("params/settings/config")} Show and change values of parameters", "params"
|
14
|
+
menu.choice "#{BULLET} #{PASTEL.bold("data/context")} Show currrent contextual info", "data"
|
15
|
+
menu.choice "#{BULLET} #{PASTEL.bold("html")} View contextual info on the web browser", "html"
|
16
|
+
menu.choice "#{BULLET} #{PASTEL.bold("reset")} Reset context to initial state", "reset"
|
17
|
+
menu.choice "#{BULLET} #{PASTEL.bold("save")} Save current contextual info to file", "save"
|
18
|
+
menu.choice "#{BULLET} #{PASTEL.bold("load")} Load current contextual info from file", "load"
|
19
|
+
menu.choice "#{BULLET} #{PASTEL.bold("clear/clean")} Clear screen", "clear"
|
20
|
+
menu.choice "#{BULLET} #{PASTEL.bold("readme/documentation")} Open readme/documentation", "readme"
|
21
|
+
menu.choice "#{BULLET} #{PASTEL.bold("exit/bye/quit")} Go back to main menu", "exit"
|
22
22
|
end
|
23
23
|
|
24
24
|
print TTY::Cursor.restore
|
@@ -33,7 +33,8 @@ class MonadicApp
|
|
33
33
|
when "data"
|
34
34
|
show_data
|
35
35
|
when "html"
|
36
|
-
|
36
|
+
@html = true
|
37
|
+
show_html
|
37
38
|
when "reset"
|
38
39
|
reset
|
39
40
|
when "save"
|
@@ -52,14 +53,10 @@ class MonadicApp
|
|
52
53
|
end
|
53
54
|
|
54
55
|
def reset
|
55
|
-
@
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
@template = @template_original.dup
|
60
|
-
when "chat/completions"
|
61
|
-
@template = JSON.parse @template_original
|
62
|
-
end
|
56
|
+
@html = false
|
57
|
+
@params = @params_initial.dup
|
58
|
+
@messages = @messages_initial.dup
|
59
|
+
@template = @template_initial.dup
|
63
60
|
|
64
61
|
if @placeholders.empty?
|
65
62
|
print PROMPT_SYSTEM.prefix
|
@@ -133,9 +130,11 @@ class MonadicApp
|
|
133
130
|
case @method
|
134
131
|
when "completions"
|
135
132
|
m = /\n\n```json\s*(\{.+\})\s*```\n\n/m.match(@template)
|
136
|
-
|
133
|
+
data = JSON.parse(m[1])
|
134
|
+
data["messages"] = @messages
|
135
|
+
f.write JSON.pretty_generate(data)
|
137
136
|
when "chat/completions"
|
138
|
-
f.write JSON.pretty_generate(@
|
137
|
+
f.write JSON.pretty_generate({ "messages" => @messages })
|
139
138
|
end
|
140
139
|
|
141
140
|
print "Data has been saved successfully\n"
|
@@ -170,14 +169,17 @@ class MonadicApp
|
|
170
169
|
data = JSON.parse(json)
|
171
170
|
case @method
|
172
171
|
when "completions"
|
172
|
+
self.class.name.downcase.split("::")[-1]
|
173
|
+
|
173
174
|
raise unless data["mode"] == self.class.name.downcase.split("::")[-1]
|
174
175
|
|
175
|
-
|
176
|
-
@template =
|
176
|
+
@messages = data.delete "messages"
|
177
|
+
@template = @template.sub(/\n\n```json\s*\{.+\}\s*```\n\n/m, "\n\n```json\n#{JSON.pretty_generate(data).strip}\n```\n\n")
|
177
178
|
when "chat/completions"
|
179
|
+
pp data
|
178
180
|
raise unless data["messages"] && data["messages"][0]["role"]
|
179
181
|
|
180
|
-
@
|
182
|
+
@messages = data["messages"]
|
181
183
|
end
|
182
184
|
print "Data has been loaded successfully\n"
|
183
185
|
true
|
data/lib/monadic_chat/open_ai.rb
CHANGED
@@ -81,16 +81,15 @@ module OpenAI
|
|
81
81
|
class Completion
|
82
82
|
attr_reader :access_token
|
83
83
|
|
84
|
-
def initialize(access_token
|
84
|
+
def initialize(access_token)
|
85
85
|
@access_token = access_token
|
86
|
-
@tmp_file = tmp_file
|
87
86
|
end
|
88
87
|
|
89
88
|
def models
|
90
89
|
OpenAI.models(@access_token)
|
91
90
|
end
|
92
91
|
|
93
|
-
def run(params, num_retry: 1, &block)
|
92
|
+
def run(params, num_retry: 1, tmp_json_file: nil, tmp_md_file: nil, &block)
|
94
93
|
method = OpenAI.model_to_method(params["model"])
|
95
94
|
|
96
95
|
response = OpenAI.query(@access_token, "post", method, 60, params, &block)
|
@@ -102,7 +101,8 @@ module OpenAI
|
|
102
101
|
|
103
102
|
case method
|
104
103
|
when "completions"
|
105
|
-
|
104
|
+
File.open(tmp_md_file, "w") { |f| f.write params["prompt"] } if tmp_md_file
|
105
|
+
get_json(response["choices"][0]["text"], tmp_json_file: tmp_json_file)
|
106
106
|
when "chat/completions"
|
107
107
|
response ["choices"][0]["text"]
|
108
108
|
end
|
@@ -115,7 +115,7 @@ module OpenAI
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
-
def get_json(data)
|
118
|
+
def get_json(data, tmp_json_file: nil)
|
119
119
|
case data
|
120
120
|
when %r{<JSON>\n*(\{.+\})\n*</JSON>}m
|
121
121
|
json = Regexp.last_match(1).gsub(/\r\n?/, "\n").gsub(/\r\n/) { "\n" }
|
@@ -126,7 +126,7 @@ module OpenAI
|
|
126
126
|
else
|
127
127
|
res = data
|
128
128
|
end
|
129
|
-
File.open(
|
129
|
+
File.open(tmp_json_file, "w") { |f| f.write json } if tmp_json_file
|
130
130
|
res
|
131
131
|
end
|
132
132
|
|
data/lib/monadic_chat/version.rb
CHANGED
data/lib/monadic_chat.rb
CHANGED
@@ -46,6 +46,7 @@ module MonadicChat
|
|
46
46
|
|
47
47
|
TEMP_HTML = File.join(Dir.home, "monadic_chat.html")
|
48
48
|
TEMP_JSON = File.join(Dir.home, "monadic_chat.json")
|
49
|
+
TEMP_MD = File.join(Dir.home, "monadic_chat.md")
|
49
50
|
|
50
51
|
style = +File.read(File.join(__dir__, "..", "assets", "github.css")).gsub(".markdown-") { "" }
|
51
52
|
style << File.read(File.join(__dir__, "..", "assets", "pigments-default.css"))
|
@@ -126,7 +127,7 @@ module MonadicChat
|
|
126
127
|
raise if OpenAI.models(token).empty?
|
127
128
|
|
128
129
|
print "success\n"
|
129
|
-
OpenAI::Completion.new(token
|
130
|
+
OpenAI::Completion.new(token)
|
130
131
|
rescue StandardError
|
131
132
|
print "failure.\n"
|
132
133
|
false
|
data/monadic_chat.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
DESC
|
15
15
|
spec.homepage = "https://github.com/yohasebe/monadic-chat"
|
16
16
|
spec.license = "MIT"
|
17
|
-
spec.required_ruby_version = ">= 2.6.
|
17
|
+
spec.required_ruby_version = ">= 2.6.10"
|
18
18
|
|
19
19
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
20
20
|
|
@@ -36,7 +36,6 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_development_dependency "bundler"
|
37
37
|
spec.add_development_dependency "rake"
|
38
38
|
spec.add_development_dependency "rspec"
|
39
|
-
spec.add_development_dependency "solargraph"
|
40
39
|
|
41
40
|
spec.add_dependency "http"
|
42
41
|
spec.add_dependency "kramdown"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: monadic-chat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yohasebe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03-
|
11
|
+
date: 2023-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: solargraph
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: http
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -261,6 +247,7 @@ extensions: []
|
|
261
247
|
extra_rdoc_files: []
|
262
248
|
files:
|
263
249
|
- ".rspec"
|
250
|
+
- ".ruby-version"
|
264
251
|
- CHANGELOG.md
|
265
252
|
- Gemfile
|
266
253
|
- Gemfile.lock
|
@@ -273,6 +260,9 @@ files:
|
|
273
260
|
- apps/code/code.json
|
274
261
|
- apps/code/code.md
|
275
262
|
- apps/code/code.rb
|
263
|
+
- apps/linguistic/linguistic.json
|
264
|
+
- apps/linguistic/linguistic.md
|
265
|
+
- apps/linguistic/linguistic.rb
|
276
266
|
- apps/novel/novel.json
|
277
267
|
- apps/novel/novel.md
|
278
268
|
- apps/novel/novel.rb
|
@@ -289,7 +279,6 @@ files:
|
|
289
279
|
- doc/img/input-acess-token.png
|
290
280
|
- doc/img/langacker-2001.svg
|
291
281
|
- doc/img/linguistic-html.png
|
292
|
-
- doc/img/monadic-chat-main-menu.png
|
293
282
|
- doc/img/monadic-chat.svg
|
294
283
|
- doc/img/readme-example-beatles-html.png
|
295
284
|
- doc/img/readme-example-beatles.png
|
@@ -310,9 +299,6 @@ files:
|
|
310
299
|
- lib/monadic_chat/parameters.rb
|
311
300
|
- lib/monadic_chat/version.rb
|
312
301
|
- monadic_chat.gemspec
|
313
|
-
- samples/linguistic/linguistic.json
|
314
|
-
- samples/linguistic/linguistic.md
|
315
|
-
- samples/linguistic/linguistic.rb
|
316
302
|
homepage: https://github.com/yohasebe/monadic-chat
|
317
303
|
licenses:
|
318
304
|
- MIT
|
@@ -329,14 +315,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
329
315
|
requirements:
|
330
316
|
- - ">="
|
331
317
|
- !ruby/object:Gem::Version
|
332
|
-
version: 2.6.
|
318
|
+
version: 2.6.10
|
333
319
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
334
320
|
requirements:
|
335
321
|
- - ">="
|
336
322
|
- !ruby/object:Gem::Version
|
337
323
|
version: '0'
|
338
324
|
requirements: []
|
339
|
-
rubygems_version: 3.4.
|
325
|
+
rubygems_version: 3.4.1
|
340
326
|
signing_key:
|
341
327
|
specification_version: 4
|
342
328
|
summary: Highly configurable CLI client app for OpenAI chat/text-completion API
|
Binary file
|
@@ -1,39 +0,0 @@
|
|
1
|
-
You are an English syntactic/semantic/pragmatic analyzer. Analyze the new prompt from the user below and execute a syntactic parsing. Give your response in a variation of the penn treebank format, but use brackets [ ] instead of parentheses ( ). Also, give your response in a markdown code span. The sentence must always be parsed if the user's input sentence is enclosed in double quotes. Create a response to the following new prompt from the user and set your response to the "response" property of the JSON object shown below. All prompts by "user" in the "messages" property are continuous in content. If pasing the input sentence is extremely difficult, or the input is not enclosed in double quotes, let the user know.
|
2
|
-
|
3
|
-
NEW PROMPT: {{PROMPT}}
|
4
|
-
|
5
|
-
```json
|
6
|
-
{
|
7
|
-
"prompt": "\"We didn't have a camera.\"",
|
8
|
-
"response": "`[S [NP We] [VP [V didn't] [VP [V have] [NP [Det a] [N camera] ] ] ] ] ]`\n\n###\n\n",
|
9
|
-
"mode": "linguistic",
|
10
|
-
"turns": 3,
|
11
|
-
"sentence_type": ["declarative"],
|
12
|
-
"sentiment": ["sad"],
|
13
|
-
"summary": "The user saw a beautiful sunset, but did not take a picture because the user did not have a camera.",
|
14
|
-
"tokens": 351,
|
15
|
-
"messages": [{"user": "\"We saw a beautiful sunset.\"", "assistant": "`[S [NP He] [VP [V saw] [NP [det a] [N' [Adj beautiful] [N sunset] ] ] ] ]`\n\n###\n\n"},{"user": "\"We didn't take a picture.\"", "assistant": "`[S [NP We] [IP [I didn't] [VP [V take] [NP [Det a] [N picture] ] ] ] ] ]`\n\n###\n\n"},{"user": "\"We didn't have a camera.\"", "assistant": "`[S [NP We] [IP [I didn't] [VP [V have] [NP [Det a] [N camera] ] ] ] ] ]`\n\n###\n\n"}]
|
16
|
-
}
|
17
|
-
```
|
18
|
-
|
19
|
-
Make sure the following content requirements are all fulfilled:
|
20
|
-
|
21
|
-
- keep the value of the "mode" property at "linguistic"
|
22
|
-
- set the new prompt to the "prompt" property
|
23
|
-
- create your response to the new prompt in accordance with the "messages" and set it to "response"
|
24
|
-
- insert both the new prompt and the response after all the existing items in the "messages"
|
25
|
-
- analyze the new prompt's sentence type and set a sentence type value such as "interrogative", "imperative", "exclamatory", or "declarative" to the "sentence_type" property
|
26
|
-
- analyze the new prompt's sentiment and set one or more sentiment types such as "happy", "excited", "troubled", "upset", or "sad" to the "sentiment" property
|
27
|
-
- summarize the user's messages so far and update the "summary" property with a text of fewer than 100 words using as many discourse markers such as "because", "therefore", "but", "so" to show the logical connection between the events.
|
28
|
-
- update the value of "tokens" with the number of tokens of the resulting JSON object"
|
29
|
-
|
30
|
-
Make sure the following formal requirements are all fulfilled:
|
31
|
-
|
32
|
-
- do not use invalid characters in the JSON object
|
33
|
-
- escape double quotes and other special characters in the text values in the resulting JSON object
|
34
|
-
- increment the value of "turns" by 1 and update the property so that the value of "turns" equals the number of the items in the "messages" of the resulting JSON object
|
35
|
-
- check the validity of the generated JSON object and correct any possible parsing problems before returning it
|
36
|
-
|
37
|
-
Add "\n\n###\n\n" at the end of the "response" value.
|
38
|
-
|
39
|
-
Wrap the JSON object with "<JSON>\n" and "\n</JSON>".
|