Quellcode durchsuchen

Restrict job search results to Indeed listings

Tighten OpenAI job-search prompts and JSON schema copy so discovery
targets Indeed domains only. Filter parsed listings to drop URLs
that are not Indeed (or empty), and add host checks for regional
Indeed sites.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 vor 3 Wochen
Ursprung
Commit
4f5160a9f3
1 geänderte Dateien mit 28 neuen und 9 gelöschten Zeilen
  1. 28 9
      App for Indeed/Views/DashboardView.swift

+ 28 - 9
App for Indeed/Views/DashboardView.swift

@@ -2264,15 +2264,17 @@ private final class OpenAIJobSearchService {
2264 2264
         }
2265 2265
 
2266 2266
         let developerInstructions = """
2267
-        You are the job-search backend for a desktop app. Always use web search to find currently posted jobs that match the user's request.
2267
+        You are the job-search backend for an Indeed-focused desktop app. Always use web search, but only to discover roles that are listed on Indeed (indeed.com and regional Indeed sites such as indeed.co.uk, ca.indeed.com, etc.).
2268
+
2269
+        Do not include jobs sourced only from LinkedIn, Glassdoor, company career pages (unless the same opening is clearly on Indeed with an Indeed URL), Google Jobs aggregates, or other job boards.
2268 2270
 
2269 2271
         Your final assistant message must be JSON that strictly matches the configured response schema (one object with a "jobs" array). Do not add markdown, code fences, or conversational prose outside that JSON.
2270 2272
 
2271
-        Each job entry needs a title, a single-sentence description, and a "url" string. Use a real listing or apply URL when available; use an empty string for "url" when none is known.
2273
+        Each job entry needs a title, a single-sentence description, and a "url" string. The "url" must be a direct Indeed job link (viewjob, pagead, or equivalent on an Indeed domain) when web search finds one; use an empty string for "url" only when you cannot find any Indeed URL for that role (omit the listing if it is not on Indeed).
2272 2274
 
2273 2275
         Return at most \(jobLimit) distinct listings. If the conversation context already lists jobs, do not repeat the same titles or URLs when the user asks for more—it is fine to return fewer than \(jobLimit) new results.
2274 2276
 
2275
-        Full sentences such as "looking for an AI developer job" are still job queries: always populate "jobs" from web search rather than answering with chatty text alone.
2277
+        Full sentences such as "looking for an AI developer job" are still job queries: always populate "jobs" from Indeed-oriented web search rather than answering with chatty text alone.
2276 2278
         """
2277 2279
 
2278 2280
         let userInput = """
@@ -2283,7 +2285,7 @@ private final class OpenAIJobSearchService {
2283 2285
 
2284 2286
         Latest user query: "\(query)"
2285 2287
 
2286
-        If the user refines pay, seniority, work location, or similar, run a new search that applies those constraints to the same career topic as in the context.
2288
+        If the user refines pay, seniority, work location, or similar, run a new Indeed-focused search that applies those constraints to the same career topic as in the context.
2287 2289
         """
2288 2290
 
2289 2291
         let payload = OpenAIJobSearchAPIRequest.jobSearchPayload(
@@ -2340,7 +2342,9 @@ private final class OpenAIJobSearchService {
2340 2342
                         userInfo: [NSLocalizedDescriptionKey: "The API returned an empty text payload."]
2341 2343
                     )
2342 2344
                 }
2343
-                let jobs = try Self.parseJobListings(fromModelText: trimmed).map(Self.normalizedJobListing)
2345
+                let jobs = try Self.parseJobListings(fromModelText: trimmed)
2346
+                    .filter(Self.jobListingUsesIndeedOrEmptyURL)
2347
+                    .map(Self.normalizedJobListing)
2344 2348
                 completion(.success(JobSearchOutput(jobs: jobs)))
2345 2349
             } catch {
2346 2350
                 completion(.failure(error))
@@ -2433,6 +2437,21 @@ private final class OpenAIJobSearchService {
2433 2437
         return JobListing(title: job.title, description: job.description, url: trimmedURL)
2434 2438
     }
2435 2439
 
2440
+    /// Drops listings whose `url` points at non-Indeed sites (e.g. LinkedIn) when the model ignores instructions.
2441
+    private static func jobListingUsesIndeedOrEmptyURL(_ job: JobListing) -> Bool {
2442
+        let trimmed = job.url?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
2443
+        if trimmed.isEmpty { return true }
2444
+        return isIndeedJobURL(trimmed)
2445
+    }
2446
+
2447
+    /// Host looks like an official Indeed property (`indeed.com`, `www.indeed.co.uk`, `ca.indeed.com`, …), not `notindeed.com`.
2448
+    private static func isIndeedJobURL(_ string: String) -> Bool {
2449
+        guard let host = URL(string: string)?.host?.lowercased() else { return false }
2450
+        if host == "indeed.com" { return true }
2451
+        if host.hasPrefix("indeed.") { return true }
2452
+        return host.contains(".indeed.")
2453
+    }
2454
+
2436 2455
     private static func parseJobListings(fromModelText text: String) throws -> [JobListing] {
2437 2456
         let stripped = text.trimmingCharacters(in: .whitespacesAndNewlines)
2438 2457
         if stripped.hasPrefix("{"), let directData = stripped.data(using: .utf8),
@@ -2588,9 +2607,9 @@ private struct OpenAIJobSearchAPIRequest: Encodable {
2588 2607
         jobLimit: Int
2589 2608
     ) -> OpenAIJobSearchAPIRequest {
2590 2609
         let itemProperties = OpenAIJobSearchJobItemProperties(
2591
-            title: OpenAIJSONSchemaStringField(type: "string", description: "Job title as shown on the listing."),
2592
-            description: OpenAIJSONSchemaStringField(type: "string", description: "One concise sentence summarizing the role."),
2593
-            url: OpenAIJSONSchemaStringField(type: "string", description: "Direct listing or apply URL; use an empty string when unknown.")
2610
+            title: OpenAIJSONSchemaStringField(type: "string", description: "Job title as shown on the Indeed listing."),
2611
+            description: OpenAIJSONSchemaStringField(type: "string", description: "One concise sentence summarizing the role from the Indeed posting."),
2612
+            url: OpenAIJSONSchemaStringField(type: "string", description: "Direct Indeed job URL (https on indeed.com or a regional Indeed domain such as indeed.co.uk); empty string only if no Indeed URL exists—never use LinkedIn, Glassdoor, or other boards.")
2594 2613
         )
2595 2614
         let itemSchema = OpenAIJobSearchJobItemSchema(
2596 2615
             type: "object",
@@ -2600,7 +2619,7 @@ private struct OpenAIJobSearchAPIRequest: Encodable {
2600 2619
         )
2601 2620
         let jobsProperty = OpenAIJobSearchJobsArrayProperty(
2602 2621
             type: "array",
2603
-            description: "Up to \(jobLimit) jobs from live web search; use an empty array if none are found.",
2622
+            description: "Up to \(jobLimit) jobs found on Indeed via web search; use an empty array if none are found. Do not include listings that only exist off Indeed.",
2604 2623
             items: itemSchema
2605 2624
         )
2606 2625
         let rootProperties = OpenAIJobSearchRootProperties(jobs: jobsProperty)