|
17 | 17 | load_dotenv() |
18 | 18 |
|
19 | 19 | from foundry_iq import KNOWLEDGE_BASE |
| 20 | +import requests |
20 | 21 |
|
21 | 22 |
|
22 | | -def create_index_if_not_exists(search_client): |
23 | | - """Create the search index with the expected schema.""" |
24 | | - from azure.search.documents.indexes import SearchIndexClient |
25 | | - from azure.search.documents.indexes.models import ( |
26 | | - SearchIndex, SimpleField, SearchableField, SearchFieldDataType, |
27 | | - ) |
| 23 | +def create_index(): |
| 24 | + """Create the search index via REST API.""" |
| 25 | + endpoint = os.getenv('FOUNDRY_IQ_ENDPOINT').rstrip('/') |
28 | 26 | index_name = os.getenv('FOUNDRY_IQ_INDEX_NAME', 'sleepiq-medical-knowledge') |
29 | | - index_client = SearchIndexClient( |
30 | | - endpoint=os.getenv('FOUNDRY_IQ_ENDPOINT'), |
31 | | - credential=AzureKeyCredential(os.getenv('FOUNDRY_IQ_API_KEY')), |
32 | | - ) |
33 | | - try: |
34 | | - index_client.get_index(index_name) |
35 | | - print(f'Index "{index_name}" already exists.') |
36 | | - except: |
37 | | - fields = [ |
38 | | - SimpleField(name='id', type=SearchFieldDataType.String, key=True), |
39 | | - SearchableField(name='source', type=SearchFieldDataType.String, filterable=True), |
40 | | - SearchableField(name='content', type=SearchFieldDataType.String), |
41 | | - SearchableField(name='keywords', type=SearchFieldDataType.Collection(SearchFieldDataType.String), |
42 | | - filterable=True, searchable=True), |
| 27 | + api_key = os.getenv('FOUNDRY_IQ_API_KEY') |
| 28 | + |
| 29 | + url = f"{endpoint}/indexes/{index_name}?api-version=2023-11-01" |
| 30 | + headers = { |
| 31 | + "Content-Type": "application/json", |
| 32 | + "api-key": api_key, |
| 33 | + } |
| 34 | + body = { |
| 35 | + "name": index_name, |
| 36 | + "fields": [ |
| 37 | + {"name": "id", "type": "Edm.String", "key": True}, |
| 38 | + {"name": "source", "type": "Edm.String", "filterable": True, "searchable": True}, |
| 39 | + {"name": "content", "type": "Edm.String", "searchable": True}, |
| 40 | + {"name": "keywords", "type": "Collection(Edm.String)", "filterable": True, "searchable": True}, |
43 | 41 | ] |
44 | | - index = SearchIndex(name=index_name, fields=fields) |
45 | | - index_client.create_index(index) |
| 42 | + } |
| 43 | + |
| 44 | + # Check if index exists |
| 45 | + resp = requests.get(url, headers=headers) |
| 46 | + if resp.status_code == 200: |
| 47 | + print(f'Index "{index_name}" already exists.') |
| 48 | + return |
| 49 | + |
| 50 | + # Create it |
| 51 | + resp = requests.put(url, headers=headers, json=body) |
| 52 | + if resp.status_code in (201, 204): |
46 | 53 | print(f'Created index "{index_name}".') |
| 54 | + else: |
| 55 | + print(f'Failed to create index: {resp.status_code} {resp.text}') |
| 56 | + sys.exit(1) |
47 | 57 |
|
48 | 58 |
|
49 | 59 | def seed(): |
50 | | - endpoint = os.getenv('FOUNDRY_IQ_ENDPOINT') |
| 60 | + endpoint = os.getenv('FOUNDRY_IQ_ENDPOINT', '').rstrip('/') |
51 | 61 | index_name = os.getenv('FOUNDRY_IQ_INDEX_NAME', 'sleepiq-medical-knowledge') |
52 | 62 | api_key = os.getenv('FOUNDRY_IQ_API_KEY') |
53 | 63 |
|
54 | 64 | if not endpoint or not api_key: |
55 | 65 | print('ERROR: Set FOUNDRY_IQ_ENDPOINT and FOUNDRY_IQ_API_KEY in .env') |
56 | 66 | sys.exit(1) |
57 | 67 |
|
58 | | - from azure.core.credentials import AzureKeyCredential |
59 | | - from azure.search.documents import SearchClient |
60 | | - |
61 | | - client = SearchClient(endpoint=endpoint, index_name=index_name, |
62 | | - credential=AzureKeyCredential(api_key)) |
| 68 | + create_index() |
63 | 69 |
|
64 | | - create_index_if_not_exists(client) |
| 70 | + # Upload documents via REST API |
| 71 | + url = f"{endpoint}/indexes/{index_name}/docs/index?api-version=2023-11-01" |
| 72 | + headers = { |
| 73 | + "Content-Type": "application/json", |
| 74 | + "api-key": api_key, |
| 75 | + } |
65 | 76 |
|
66 | 77 | documents = [] |
67 | 78 | for entry in KNOWLEDGE_BASE: |
68 | 79 | documents.append({ |
69 | | - 'id': entry['id'], |
70 | | - 'source': entry['source'], |
71 | | - 'content': entry['content'], |
72 | | - 'keywords': entry['keywords'], |
| 80 | + "@search.action": "upload", |
| 81 | + "id": entry["id"], |
| 82 | + "source": entry["source"], |
| 83 | + "content": entry["content"], |
| 84 | + "keywords": entry["keywords"], |
73 | 85 | }) |
74 | 86 |
|
75 | | - result = client.upload_documents(documents) |
76 | | - succeeded = sum(1 for r in result if r.succeeded) |
77 | | - failed = len(result) - succeeded |
78 | | - print(f'Uploaded {succeeded}/{len(result)} knowledge entries to "{index_name}".') |
79 | | - if failed: |
80 | | - print(f'Failed: {failed}') |
81 | | - for r in result: |
82 | | - if not r.succeeded: |
83 | | - print(f' - {r.key}: {r.error}') |
| 87 | + batch_size = 10 |
| 88 | + total_succeeded = 0 |
| 89 | + total_failed = 0 |
| 90 | + |
| 91 | + for i in range(0, len(documents), batch_size): |
| 92 | + batch = documents[i:i + batch_size] |
| 93 | + body = {"value": batch} |
| 94 | + resp = requests.post(url, headers=headers, json=body) |
| 95 | + result = resp.json() |
| 96 | + |
| 97 | + if resp.status_code == 200 or resp.status_code == 201: |
| 98 | + for item in result.get("value", []): |
| 99 | + if item.get("status", False): |
| 100 | + total_succeeded += 1 |
| 101 | + else: |
| 102 | + total_failed += 1 |
| 103 | + print(f' Failed: {item.get("key")}: {item.get("errorMessage")}') |
| 104 | + else: |
| 105 | + print(f'Upload batch failed: {resp.status_code} {resp.text}') |
| 106 | + total_failed += len(batch) |
| 107 | + |
| 108 | + print(f'Uploaded {total_succeeded}/{len(documents)} knowledge entries to "{index_name}".') |
| 109 | + if total_failed: |
| 110 | + print(f'Failed: {total_failed}') |
| 111 | + |
| 112 | + # Verify by search |
| 113 | + print("\nVerifying with test search...") |
| 114 | + search_url = f"{endpoint}/indexes/{index_name}/docs/search?api-version=2023-11-01" |
| 115 | + test_resp = requests.post(search_url, headers=headers, json={"search": "AHI classification", "top": 2}) |
| 116 | + if test_resp.status_code == 200: |
| 117 | + results = test_resp.json() |
| 118 | + count = results.get("@odata.count", len(results.get("value", []))) |
| 119 | + print(f'Search works! Found {count} results for "AHI classification".') |
| 120 | + for r in results.get("value", []): |
| 121 | + print(f' - [{r["id"]}] {r["source"]}') |
| 122 | + else: |
| 123 | + print(f'Search test failed: {test_resp.status_code} {test_resp.text}') |
84 | 124 |
|
85 | 125 |
|
86 | 126 | if __name__ == '__main__': |
|
0 commit comments