Skip to content

Commit bd28f21

Browse files
authored
Merge pull request #602 from alphagov/2831-add-link-token-mapping-to-llm-response
Add link token mapping to LLM response
2 parents c6db77b + 87d5112 commit bd28f21

6 files changed

Lines changed: 62 additions & 17 deletions

File tree

lib/answer_composition/link_token_mapper.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module AnswerComposition
22
class LinkTokenMapper
33
TOKEN_PREFIX = "link_".freeze
4+
attr_reader :mapping
45

56
def initialize
67
@mapping = {}
@@ -50,8 +51,6 @@ def replace_tokens_with_links(markdown)
5051

5152
private
5253

53-
attr_reader :mapping
54-
5554
def rewrite_links(element)
5655
if element.type == :a
5756
rewrite_link(element)

lib/answer_composition/pipeline/claude/structured_answer_composer.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ def call
2121
tool_choice: { type: "tool", name: "output_schema" },
2222
**inference_config,
2323
)
24+
llm_response_with_link_token_mapping = {
25+
"response" => response.to_h.stringify_keys,
26+
"link_token_mapping" => link_token_mapper.mapping.invert,
27+
}
2428

25-
context.answer.assign_llm_response("structured_answer", response.to_h)
29+
context.answer.assign_llm_response("structured_answer", llm_response_with_link_token_mapping)
2630
tool_output = response[:content][0][:input]
2731

2832
unless tool_output[:answered]

lib/answer_composition/pipeline/openai/structured_answer_composer.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ def initialize(context)
1111

1212
def call
1313
start_time = Clock.monotonic_time
14-
context.answer.assign_llm_response("structured_answer", openai_response_choice)
14+
llm_response_with_link_token_mapping = {
15+
"response" => openai_response_choice,
16+
"link_token_mapping" => link_token_mapper.mapping.invert,
17+
}
18+
context.answer.assign_llm_response("structured_answer", llm_response_with_link_token_mapping)
1519

1620
unless parsed_structured_response["answered"]
1721
return context.abort_pipeline!(

spec/lib/answer_composition/link_token_mapper_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
HTML
1818
end
1919

20+
describe "#initialize" do
21+
it "initializes mapping as an empty hash" do
22+
mapper = described_class.new
23+
expect(mapper.mapping).to eq({})
24+
end
25+
end
26+
2027
describe "#map_links_to_tokens" do
2128
it "replaces href attributes with tokens" do
2229
amended_html = described_class.new.map_links_to_tokens(html, "/exact-path")
@@ -48,6 +55,20 @@
4855

4956
expect(result).to eq(html)
5057
end
58+
59+
it "updates the mapping attribute with the link mappings" do
60+
mapper = described_class.new
61+
mapper.map_links_to_tokens(html, "/exact-path")
62+
63+
expect(mapper.mapping).to eq(
64+
{
65+
"https://www.test.gov.uk/tax-returns" => "link_1",
66+
"https://www.test.gov.uk/tax-news" => "link_2",
67+
"https://www.gov.uk/national-insurance/what-national-insurance-is" => "link_3",
68+
"https://www.test.gov.uk/exact-path#some-heading" => "link_4",
69+
},
70+
)
71+
end
5172
end
5273

5374
describe "#map_link_to_token" do

spec/lib/answer_composition/pipeline/claude/structured_answer_composer_spec.rb

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,17 @@
4343
input: { answer:, answered: true, sources_used: %w[link_1], answer_completeness: "complete" },
4444
name: "output_schema",
4545
)
46-
expected_llm_response = claude_messages_response(
47-
content: [expected_content],
48-
usage: { cache_read_input_tokens: 20 },
49-
stop_reason: :tool_use,
50-
).to_h
46+
expected_llm_response = {
47+
"response" => claude_messages_response(
48+
content: [expected_content],
49+
usage: { cache_read_input_tokens: 20 },
50+
stop_reason: :tool_use,
51+
).to_h.stringify_keys,
52+
"link_token_mapping" => {
53+
"link_1" => "https://www.test.gov.uk/vat-rates#vat-basics",
54+
"link_2" => "https://www.test.gov.uk/what-is-tax",
55+
},
56+
}
5157
expect(context.answer.llm_responses["structured_answer"]).to eq(expected_llm_response)
5258
end
5359

@@ -139,11 +145,17 @@
139145
},
140146
name: "output_schema",
141147
)
142-
expected_llm_response = claude_messages_response(
143-
content: [expected_content],
144-
usage: { cache_read_input_tokens: 20 },
145-
stop_reason: :tool_use,
146-
).to_h
148+
expected_llm_response = {
149+
"response" => claude_messages_response(
150+
content: [expected_content],
151+
usage: { cache_read_input_tokens: 20 },
152+
stop_reason: :tool_use,
153+
).to_h.stringify_keys,
154+
"link_token_mapping" => {
155+
"link_1" => "https://www.test.gov.uk/vat-rates#vat-basics",
156+
"link_2" => "https://www.test.gov.uk/what-is-tax",
157+
},
158+
}
147159
expect(context.answer.llm_responses["structured_answer"]).to eq(expected_llm_response)
148160
end
149161
end

spec/lib/answer_composition/pipeline/openai/structured_answer_composer_spec.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,18 @@
5959

6060
described_class.call(context)
6161

62+
expected_structured_answer = hash_including(
63+
"response" => hash_including_openai_response_with_tool_call("generate_answer_using_retrieved_contexts"),
64+
"link_token_mapping" => {
65+
"link_1" => "https://www.test.gov.uk/vat-rates#vat-basics",
66+
"link_2" => "https://www.test.gov.uk/what-is-tax",
67+
},
68+
)
69+
expect(context.answer.llm_responses["structured_answer"]).to match(expected_structured_answer)
6270
expect(context.answer.message.squish).to eq(
6371
"VAT (Value Added Tax) is a [tax][1] applied to most goods and services in the UK. [1]: https://www.test.gov.uk/what-is-tax",
6472
)
6573
expect(context.answer.status).to eq("answered")
66-
expect(context.answer.llm_responses["structured_answer"]).to match(
67-
hash_including_openai_response_with_tool_call("generate_answer_using_retrieved_contexts"),
68-
)
6974
end
7075

7176
it "sets the 'used' boolean to false for unused sources" do

0 commit comments

Comments
 (0)