Skip to content

Commit

Permalink
Merge pull request #43 from turingschool/jm/connect_openai
Browse files Browse the repository at this point in the history
Connected Rails BE directly to OpenAI for summaries
  • Loading branch information
noahdurbin authored Sep 19, 2024
2 parents 1459258 + e9b8464 commit 852819f
Show file tree
Hide file tree
Showing 14 changed files with 974 additions and 28 deletions.
34 changes: 21 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,9 @@ This is the backend API repository for TurLink. TurLink is a link shortener app
"id": 1,
"name": "javascript"
}
]
],
"private": false,
"summary": "Summary not available"
}
},
{
Expand All @@ -449,32 +451,38 @@ This is the backend API repository for TurLink. TurLink is a link shortener app
"id": 2,
"name": "ruby"
}
]
],
"private": false,
"summary": "Summary not available"
}
},
// ... (3 more link objects)
]
}
```
### Get Summary for a Link
- **GET** `/api/v1/summary?link={original link}`
- **GET** `/api/v1/summary?link={short link}`
- Description: Retrieves a summary of the content at the given link.
- Example Request: GET `https://turlink-be-53ba7254a7c1.herokuapp.com/api/v1/summary?link=www.example.com`
- Example Request: GET `https://turlink-be-53ba7254a7c1.herokuapp.com/api/v1/summary?link=tur.link/96559226`
- Successful Response (200 OK):
```json
{
"data": {
"attributes": {
"link": "www.example.com",
"summary": "1. example 1\n2. example 2\n3. example 3"
}
"id": "6",
"type": "link",
"attributes": {
"original": "https://mod4.turing.edu/lessons/cs/arrays_stacks_queues.html",
"short": "tur.link/96559226",
"user_id": 1,
"tags": [],
"click_count": 0,
"last_click": null,
"private": false,
"summary": "1. The document outlines the curriculum for Module 4 at the Turing School of Software and Design, focusing on data structures such as Arrays, Stacks, and Queues. It includes learning goals and key topics of discussion.\n\n2. It provides detailed explanations of each data structure: Arrays, Stacks (Last-In-First-Out), and Queues (First-In-First-Out), including their background, memory usage, implementation, usage, and their pros and cons.\n\n3. Students are encouraged to practice their knowledge of these data structures through various examples and a workshop challenge, reinforcing their understanding of how these data structures operate within programming languages."
}
}
}
}
```
- Notes:
- This endpoint currently returns mock data.
- In the future, it will provide an actual summary of the content at the given link.
- The summary is expected to be a string with numbered points, separated by newline characters.

### Update Link Privacy
- **PATCH** `/api/v1/users/:user_id/links/:id/update_privacy`
Expand Down
15 changes: 14 additions & 1 deletion app/controllers/api/v1/links_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,22 @@ def top_links
query = query.joins(:tags).where(tags: { name: tags })
query = query.group('links.id').having('COUNT(DISTINCT tags.id) = ?', tags.size)
end

links = query.distinct

links.each do |link|
begin
if link.summary == nil || link.summary_timestamp < (Time.current - 259200) #3 days in seconds
new_summary = SummaryService.new.summarize(link.original)
link.update(summary: new_summary)
link.update(summary_timestamp: Time.current)
end
rescue Faraday::ConnectionFailed => e
link.update(summary: "Summary not available")
link.update(summary_timestamp: Time.current)
end
end

render json: LinkSerializer.new(links)
end

Expand Down
21 changes: 18 additions & 3 deletions app/controllers/api/v1/summaries_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
class Api::V1::SummariesController < ApplicationController

rescue_from NoMethodError, with: :not_found_error

def show
summary = SummaryService.new.summarize
summary_json = JSON.parse(summary.body, symbolize_names: true)
render json: SummarySerializer.new(summary_json).serialize_json
link = Link.find_by(short: params[:link])
if link.summary == nil || link.summary_timestamp < (Time.current - 259200) #3 days in seconds
new_summary = SummaryService.new.summarize(link.original)
link.update(summary: new_summary)
link.update(summary_timestamp: Time.current)
render json: LinkSerializer.new(link)
else
render json: LinkSerializer.new(link)
end
end

private

def not_found_error
render json: { errors: [{ message: 'Link not found in database' }] }, status: :not_found
end
end
2 changes: 1 addition & 1 deletion app/serializers/link_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class LinkSerializer
include JSONAPI::Serializer
attributes :original, :short, :user_id, :tags, :click_count, :last_click, :private
attributes :original, :short, :user_id, :tags, :click_count, :last_click, :private, :summary
end
37 changes: 34 additions & 3 deletions app/services/summary_service.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
class SummaryService

def get_html(link)
faraday = Faraday.new(link)
response = faraday.get
response.body
end

def conn
Faraday.new('https://nameless-garden-14218-de5663d17d61.herokuapp.com/')
Faraday.new("https://api.openai.com/v1/chat/completions") do |faraday|
faraday.headers[:Authorization] = "Bearer #{Rails.application.credentials.openai[:key]}"
end
end

def summarize
conn.get('/api/v1/ping/')
def summarize(link)
html_content = get_html(link)
response = conn.post do |req|
req.headers['Content-Type'] = 'application/json'
req.body = {
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: "You are a helpful assistant."
},
{
role: "user",
content: "Summarize the following content: #{html_content}."
},
{
role: "user",
content: "Use 3 numbered bullet points."
}
]
}.to_json
end
openai_response = response.body
JSON.parse(response.body, symbolize_names: true)[:choices][0][:message][:content]
end
end
2 changes: 1 addition & 1 deletion config/credentials.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9QGtxaTLtmlXMP9VSsGdHD+4jeGuHjcQx7kaz5ldHAXhLjA/WTpisoI5Be7FC9q7hDFhZ641LdJkV7rug0mkqMmvZ+9xw2ScX3qEo/PTfM8fQnUha+604Wg0XTXJwODheFA7AEjsWfbD8q+qQzNZPANRc+qeTz1TP+BwvBN+KVr/XbPiJ2joF01Q/18F1kyED/9sbdL3kI7rMA2e7Cqd1DYeTvC3pR6T4a3tVWEjtB4AqgHrek5yHKl2erG6sxmbmRMPwAH4ZWRYe3NPXUP3YbEdIWBbSKr90A9RUIRGhvlfC6upXTPTvKklZ6mPwB15J1NL/SJLx8mDl1UuYz6MMj8zSNiQK6JmbGTkS0dwI2kIGQ3jooBG9GVwg0BATwHhkVKF/Ij3pusSKS4r28mVkl/+A9/W--p2sxNS2GrO5A+YL0--FYNvp951oU7AiBtVupKviA==
lVbutrr7eF1l7i2+e9pTfhURUcNzRFuFJTKbI9aMG0cJgXpZbR10iB2vcAHPyJMj59TuXHwk1TMMcRAPAyMS62aGgDiqm5aXdVOKQyQnfJSvVw3YBFYNWtgvggrm2tU0liuKkHbztJ4fo8LYMDfdOQvfWeELRBdFUJrsYyh/WKQdqYb7yNA7Do1PtpdFr4LGNlj/kHqU/IiUoTT4MIhmH39CjDZ3dbYJp3//az5sbHLfdTAkPdb/4RzFwbV2LcCfVZTwlC0jKsnBukD99yjhtqbcJToBkbUGo/T8qLprDXDj7QIKGP536aAPQCOcEk97vS6InnU9c8IQcB4RK8aPPqTzYkGWlTPgPTUzzB+Tqqkzx6IATipUyVXL85M5v+ek3Cy0s9Qy97oIuBxqE6thnJjlixNHBYYuDqjWYQa8xj9sa6Ib//U8pm32Q9TYi7MNVoO/fjqJHG0b+wvcIQoltVUloCeOw5c9J5EGFXtTqITi/UZXYqMcUdmqEmU1u+/k5X4XmycjyRuKJkWdrupFDYTCAvP/LE6BkJNfgR64t/cfPhdNI74U38+GueMAzoxE/06pIK5oRHjJxWoL1PudCuOJlafdLTLNhV4=--PofSmzfmWPDiJyky--8KUUUH2hN7pWC0QfNQYrRg==
6 changes: 6 additions & 0 deletions db/migrate/20240919154951_add_summary_to_links.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddSummaryToLinks < ActiveRecord::Migration[7.1]
def change
add_column :links, :summary, :text, default: nil
add_column :links, :summary_timestamp, :datetime, default: nil
end
end
4 changes: 3 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 852819f

Please sign in to comment.