dhti-cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/compose.d.ts +14 -0
- package/dist/commands/compose.js +112 -0
- package/dist/commands/conch.d.ts +19 -0
- package/dist/commands/conch.js +119 -0
- package/dist/commands/docker.d.ts +16 -0
- package/dist/commands/docker.js +76 -0
- package/dist/commands/elixir.d.ts +20 -0
- package/dist/commands/elixir.js +129 -0
- package/dist/commands/mimic.d.ts +9 -0
- package/dist/commands/mimic.js +157 -0
- package/dist/commands/synthetic.d.ts +17 -0
- package/dist/commands/synthetic.js +88 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/resources/docker-compose-master.yml +238 -0
- package/dist/resources/genai/Dockerfile +27 -0
- package/dist/resources/genai/README.md +1 -0
- package/dist/resources/genai/app/__init__.py +0 -0
- package/dist/resources/genai/app/bootstrap.py +43 -0
- package/dist/resources/genai/app/server.py +57 -0
- package/dist/resources/genai/pyproject.toml +84 -0
- package/dist/resources/genai/whl/placeholder.md +0 -0
- package/dist/resources/spa/Dockerfile +16 -0
- package/dist/resources/spa/def/importmap.json +39 -0
- package/dist/resources/spa/def/routes.registry.json +1599 -0
- package/dist/resources/spa/def/spa-assemble-config.json +40 -0
- package/dist/utils/bootstrap.d.ts +3 -0
- package/dist/utils/bootstrap.js +57 -0
- package/dist/utils/card.d.ts +58 -0
- package/dist/utils/card.js +76 -0
- package/dist/utils/chain.d.ts +5 -0
- package/dist/utils/chain.js +17 -0
- package/dist/utils/getCard.d.ts +3 -0
- package/dist/utils/getCard.js +11 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/request.d.ts +30 -0
- package/dist/utils/request.js +34 -0
- package/dist/utils/useDhti.d.ts +5 -0
- package/dist/utils/useDhti.js +21 -0
- package/oclif.manifest.json +442 -2
- package/package.json +7 -5
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
export default class Mimic extends Command {
|
|
3
|
+
static args = {
|
|
4
|
+
server: Args.string({ default: 'http://localhost/fhir/$import', description: 'Server URL to submit' }), // object with input, instruction (rationale in distillation), output
|
|
5
|
+
};
|
|
6
|
+
static description = 'Submit a FHIR request to a server';
|
|
7
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
8
|
+
async run() {
|
|
9
|
+
const { args, flags } = await this.parse(Mimic);
|
|
10
|
+
const mimic_request = `{
|
|
11
|
+
|
|
12
|
+
"resourceType": "Parameters",
|
|
13
|
+
|
|
14
|
+
"parameter": [ {
|
|
15
|
+
|
|
16
|
+
"name": "inputFormat",
|
|
17
|
+
|
|
18
|
+
"valueCode": "application/fhir+ndjson"
|
|
19
|
+
|
|
20
|
+
}, {
|
|
21
|
+
|
|
22
|
+
"name": "inputSource",
|
|
23
|
+
|
|
24
|
+
"valueUri": "http://example.com/fhir/"
|
|
25
|
+
|
|
26
|
+
}, {
|
|
27
|
+
|
|
28
|
+
"name": "storageDetail",
|
|
29
|
+
|
|
30
|
+
"part": [ {
|
|
31
|
+
|
|
32
|
+
"name": "type",
|
|
33
|
+
|
|
34
|
+
"valueCode": "https"
|
|
35
|
+
|
|
36
|
+
}, {
|
|
37
|
+
|
|
38
|
+
"name": "credentialHttpBasic",
|
|
39
|
+
|
|
40
|
+
"valueString": "admin:password"
|
|
41
|
+
|
|
42
|
+
}, {
|
|
43
|
+
|
|
44
|
+
"name": "maxBatchResourceCount",
|
|
45
|
+
|
|
46
|
+
"valueString": "500"
|
|
47
|
+
|
|
48
|
+
} ]
|
|
49
|
+
|
|
50
|
+
}, {
|
|
51
|
+
|
|
52
|
+
"name": "input",
|
|
53
|
+
|
|
54
|
+
"part": [ {
|
|
55
|
+
|
|
56
|
+
"name": "type",
|
|
57
|
+
|
|
58
|
+
"valueCode": "Observation"
|
|
59
|
+
|
|
60
|
+
}, {
|
|
61
|
+
|
|
62
|
+
"name": "url",
|
|
63
|
+
|
|
64
|
+
"valueUri": "https://physionet.org/files/mimic-iv-fhir-demo/2.0/mimic-fhir/ObservationLabevents.ndjson"
|
|
65
|
+
|
|
66
|
+
} ]
|
|
67
|
+
|
|
68
|
+
}, {
|
|
69
|
+
|
|
70
|
+
"name": "input",
|
|
71
|
+
|
|
72
|
+
"part": [ {
|
|
73
|
+
|
|
74
|
+
"name": "type",
|
|
75
|
+
|
|
76
|
+
"valueCode": "Medication"
|
|
77
|
+
|
|
78
|
+
}, {
|
|
79
|
+
|
|
80
|
+
"name": "url",
|
|
81
|
+
|
|
82
|
+
"valueUri": "https://physionet.org/files/mimic-iv-fhir-demo/2.0/mimic-fhir/Medication.ndjson"
|
|
83
|
+
|
|
84
|
+
} ]
|
|
85
|
+
|
|
86
|
+
}, {
|
|
87
|
+
|
|
88
|
+
"name": "input",
|
|
89
|
+
|
|
90
|
+
"part": [ {
|
|
91
|
+
|
|
92
|
+
"name": "type",
|
|
93
|
+
|
|
94
|
+
"valueCode": "Procedure"
|
|
95
|
+
|
|
96
|
+
}, {
|
|
97
|
+
|
|
98
|
+
"name": "url",
|
|
99
|
+
|
|
100
|
+
"valueUri": "https://physionet.org/files/mimic-iv-fhir-demo/2.0/mimic-fhir/Procedure.ndjson"
|
|
101
|
+
|
|
102
|
+
} ]
|
|
103
|
+
|
|
104
|
+
}, {
|
|
105
|
+
|
|
106
|
+
"name": "input",
|
|
107
|
+
|
|
108
|
+
"part": [ {
|
|
109
|
+
|
|
110
|
+
"name": "type",
|
|
111
|
+
|
|
112
|
+
"valueCode": "Condition"
|
|
113
|
+
|
|
114
|
+
}, {
|
|
115
|
+
|
|
116
|
+
"name": "url",
|
|
117
|
+
|
|
118
|
+
"valueUri": "https://physionet.org/files/mimic-iv-fhir-demo/2.0/mimic-fhir/Condition.ndjson"
|
|
119
|
+
|
|
120
|
+
} ]
|
|
121
|
+
|
|
122
|
+
}, {
|
|
123
|
+
|
|
124
|
+
"name": "input",
|
|
125
|
+
|
|
126
|
+
"part": [ {
|
|
127
|
+
|
|
128
|
+
"name": "type",
|
|
129
|
+
|
|
130
|
+
"valueCode": "Patient"
|
|
131
|
+
|
|
132
|
+
}, {
|
|
133
|
+
|
|
134
|
+
"name": "url",
|
|
135
|
+
|
|
136
|
+
"valueUri": "https://physionet.org/files/mimic-iv-fhir-demo/2.0/mimic-fhir/Patient.ndjson"
|
|
137
|
+
|
|
138
|
+
} ]
|
|
139
|
+
|
|
140
|
+
} ]
|
|
141
|
+
|
|
142
|
+
}`;
|
|
143
|
+
// send a POST request to the server with the mimic_request body
|
|
144
|
+
const response = await fetch(args.server, {
|
|
145
|
+
body: mimic_request,
|
|
146
|
+
headers: {
|
|
147
|
+
'Content-Type': 'application/fhir+json',
|
|
148
|
+
Prefer: 'respond-async',
|
|
149
|
+
},
|
|
150
|
+
method: 'POST',
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
console.error(`Error: ${response.status} ${response.statusText}`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Synthetic extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
input: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
output: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
prompt: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
inputField: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
maxCycles: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
maxRecords: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
outputField: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import bootstrap from '../utils/bootstrap.js';
|
|
4
|
+
import { ChainService } from '../utils/chain.js';
|
|
5
|
+
export default class Synthetic extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
input: Args.string({ default: "", description: 'Input file to process' }), // object with input, instruction (rationale in distillation), output
|
|
8
|
+
output: Args.string({ description: 'Output file to write' }),
|
|
9
|
+
prompt: Args.string({ default: "", description: 'Prompt file to read' }),
|
|
10
|
+
};
|
|
11
|
+
static description = 'Generate synthetic data using LLM';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %>',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
inputField: Flags.string({ char: 'i', default: 'input', description: 'Input field to use', options: ['input', 'instruction', 'output'] }),
|
|
17
|
+
maxCycles: Flags.integer({ char: 'm', default: 0, description: 'Maximum number of cycles to run' }),
|
|
18
|
+
maxRecords: Flags.integer({ char: 'r', default: 10, description: 'Maximum number of records to generate' }),
|
|
19
|
+
outputField: Flags.string({ char: 'o', default: 'output', description: 'Output field to use', options: ['input', 'instruction', 'output'] }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { args, flags } = await this.parse(Synthetic);
|
|
23
|
+
let prompt = "";
|
|
24
|
+
// read prompt file if provided
|
|
25
|
+
if (args.prompt)
|
|
26
|
+
prompt = fs.readFileSync(args.prompt ?? '', 'utf8');
|
|
27
|
+
const container = await bootstrap();
|
|
28
|
+
const chain = new ChainService(container);
|
|
29
|
+
// if no output file, exit with error
|
|
30
|
+
if (!args.output) {
|
|
31
|
+
console.log("Please provide an output file");
|
|
32
|
+
this.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (flags.maxCycles) { // No input file, can process in batches
|
|
35
|
+
const input = {
|
|
36
|
+
input: prompt,
|
|
37
|
+
};
|
|
38
|
+
let responses = [];
|
|
39
|
+
for (let i = 0; i < flags.maxCycles; i++) {
|
|
40
|
+
const response = await chain.Chain(input);
|
|
41
|
+
let cycle = [];
|
|
42
|
+
const jsonArrayMatch = response.match(/\[[^\]]*]/);
|
|
43
|
+
if (jsonArrayMatch) {
|
|
44
|
+
try {
|
|
45
|
+
cycle = JSON.parse(jsonArrayMatch[0]);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error('Failed to parse JSON array from response:', error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
responses = responses.concat(cycle);
|
|
52
|
+
console.log(`Iteration ${i + 1}: Collected ${responses.length} records so far, ${flags.maxRecords - responses.length} to go`);
|
|
53
|
+
if (responses.length >= flags.maxRecords)
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
// convert to json
|
|
57
|
+
const jsonOutput = [];
|
|
58
|
+
for (const response of responses) {
|
|
59
|
+
jsonOutput.push({
|
|
60
|
+
[flags.outputField]: response,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(args.output ?? '', JSON.stringify(jsonOutput, null, 4));
|
|
64
|
+
console.log(`${args.output} has been created with ${flags.maxRecords} records`);
|
|
65
|
+
}
|
|
66
|
+
else { // Input file, process one by one
|
|
67
|
+
// read input file
|
|
68
|
+
const input = JSON.parse(fs.readFileSync(args.input ?? '', 'utf8'));
|
|
69
|
+
const responses = [];
|
|
70
|
+
// for each record in input file
|
|
71
|
+
for (const [i, record] of input.entries()) {
|
|
72
|
+
const chainInput = {
|
|
73
|
+
input: record[flags.inputField],
|
|
74
|
+
prompt,
|
|
75
|
+
};
|
|
76
|
+
const response = await chain.Chain(chainInput);
|
|
77
|
+
record[flags.outputField] = response;
|
|
78
|
+
responses.push(record);
|
|
79
|
+
if (responses.length >= flags.maxRecords)
|
|
80
|
+
break;
|
|
81
|
+
console.log(`Processed ${i + 1} records so far, ${flags.maxRecords - responses.length} to go`);
|
|
82
|
+
}
|
|
83
|
+
// write to output file
|
|
84
|
+
fs.writeFileSync(args.output ?? '', JSON.stringify(responses, null, 4));
|
|
85
|
+
console.log(`${args.output} has been created with ${responses.length} records`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
version: "3.7"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
gateway:
|
|
5
|
+
image: beapen/dhti-gateway:latest
|
|
6
|
+
restart: "unless-stopped"
|
|
7
|
+
pull_policy: always
|
|
8
|
+
depends_on:
|
|
9
|
+
- frontend
|
|
10
|
+
- backend
|
|
11
|
+
ports:
|
|
12
|
+
- "80:80"
|
|
13
|
+
- "9000:80"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
frontend:
|
|
17
|
+
image: openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17
|
|
18
|
+
# image: openmrs/openmrs-reference-application-3-frontend:${TAG:-3.0.0-beta.17} # dev3, qa, demo, 3.0.0-beta.18
|
|
19
|
+
ports:
|
|
20
|
+
- "8003:80"
|
|
21
|
+
restart: "unless-stopped"
|
|
22
|
+
pull_policy: always
|
|
23
|
+
environment:
|
|
24
|
+
SPA_PATH: /openmrs/spa
|
|
25
|
+
API_URL: /openmrs
|
|
26
|
+
SPA_CONFIG_URLS: /openmrs/spa/config-core_demo.json
|
|
27
|
+
SPA_DEFAULT_LOCALE:
|
|
28
|
+
healthcheck:
|
|
29
|
+
test: ["CMD", "curl", "-f", "http://localhost/"]
|
|
30
|
+
timeout: 5s
|
|
31
|
+
depends_on:
|
|
32
|
+
- backend
|
|
33
|
+
# volumes:
|
|
34
|
+
# - ./spa:/usr/share/nginx/html
|
|
35
|
+
|
|
36
|
+
backend:
|
|
37
|
+
image: openmrs/openmrs-reference-application-3-backend:${TAG:-3.0.0-beta.17} # dev3, qa, demo, 3.0.0-beta.18
|
|
38
|
+
ports:
|
|
39
|
+
- "8002:8080"
|
|
40
|
+
restart: "unless-stopped"
|
|
41
|
+
depends_on:
|
|
42
|
+
- openmrs-db
|
|
43
|
+
environment:
|
|
44
|
+
OMRS_CONFIG_MODULE_WEB_ADMIN: "true"
|
|
45
|
+
OMRS_CONFIG_AUTO_UPDATE_DATABASE: "true"
|
|
46
|
+
OMRS_CONFIG_CREATE_TABLES: "true"
|
|
47
|
+
OMRS_CONFIG_CONNECTION_SERVER: openmrs-db
|
|
48
|
+
OMRS_CONFIG_CONNECTION_DATABASE: openmrs
|
|
49
|
+
OMRS_CONFIG_CONNECTION_USERNAME: ${OPENMRS_DB_USER:-openmrs}
|
|
50
|
+
OMRS_CONFIG_CONNECTION_PASSWORD: ${OPENMRS_DB_PASSWORD:-openmrs}
|
|
51
|
+
healthcheck:
|
|
52
|
+
test: ["CMD", "curl", "-f", "http://localhost:8080/openmrs"]
|
|
53
|
+
timeout: 5s
|
|
54
|
+
volumes:
|
|
55
|
+
- openmrs-data:/openmrs/data
|
|
56
|
+
|
|
57
|
+
openmrs-db:
|
|
58
|
+
image: mariadb:10.11.7
|
|
59
|
+
restart: "unless-stopped"
|
|
60
|
+
command: "mysqld --character-set-server=utf8 --collation-server=utf8_general_ci"
|
|
61
|
+
healthcheck:
|
|
62
|
+
test: "mysql --user=${OMRS_DB_USER:-openmrs} --password=${OMRS_DB_PASSWORD:-openmrs} --execute \"SHOW DATABASES;\""
|
|
63
|
+
interval: 3s
|
|
64
|
+
timeout: 1s
|
|
65
|
+
retries: 5
|
|
66
|
+
environment:
|
|
67
|
+
MYSQL_DATABASE: openmrs
|
|
68
|
+
MYSQL_USER: ${OMRS_DB_USER:-openmrs}
|
|
69
|
+
MYSQL_PASSWORD: ${OMRS_DB_PASSWORD:-openmrs}
|
|
70
|
+
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-openmrs}
|
|
71
|
+
volumes:
|
|
72
|
+
- openmrs-db:/var/lib/mysql
|
|
73
|
+
|
|
74
|
+
langserve:
|
|
75
|
+
image: beapen/genai:latest
|
|
76
|
+
pull_policy: always
|
|
77
|
+
ports:
|
|
78
|
+
- "8001:8001"
|
|
79
|
+
restart: "unless-stopped"
|
|
80
|
+
environment:
|
|
81
|
+
- OLLAMA_SERVER_URL==http://ollama:11434
|
|
82
|
+
- OLLAMA_WEBUI=http://ollama-webui:8080
|
|
83
|
+
- LANGFUSE_HOST=http://langfuse:3000
|
|
84
|
+
- LANGFUSE_PUBLIC_KEY=pk-lf-abcd
|
|
85
|
+
- LANGFUSE_SECRET_KEY=sk-lf-abcd
|
|
86
|
+
|
|
87
|
+
ollama:
|
|
88
|
+
image: ollama/ollama:latest
|
|
89
|
+
ports:
|
|
90
|
+
- 11434:11434
|
|
91
|
+
volumes:
|
|
92
|
+
- ollama-code:/code
|
|
93
|
+
- ollama-root:/root/.ollama
|
|
94
|
+
tty: true
|
|
95
|
+
restart: "unless-stopped"
|
|
96
|
+
environment:
|
|
97
|
+
- OLLAMA_ORIGINS=*
|
|
98
|
+
|
|
99
|
+
ollama-webui:
|
|
100
|
+
image: ghcr.io/open-webui/open-webui:main
|
|
101
|
+
volumes:
|
|
102
|
+
- ollama-webui:/app/backend/data
|
|
103
|
+
depends_on:
|
|
104
|
+
- ollama
|
|
105
|
+
ports:
|
|
106
|
+
- 8080:8080
|
|
107
|
+
environment:
|
|
108
|
+
- '/ollama/api=http://ollama:11434/api'
|
|
109
|
+
extra_hosts:
|
|
110
|
+
- host.docker.internal:host-gateway
|
|
111
|
+
restart: unless-stopped
|
|
112
|
+
|
|
113
|
+
fhir:
|
|
114
|
+
image: alphora/cqf-ruler:0.14.0 # includes cql
|
|
115
|
+
ports:
|
|
116
|
+
- 8005:8080
|
|
117
|
+
restart: "unless-stopped"
|
|
118
|
+
depends_on:
|
|
119
|
+
- postgres-db
|
|
120
|
+
environment:
|
|
121
|
+
- "spring.datasource.url=jdbc:postgresql://postgres-db:5432/postgres"
|
|
122
|
+
- "spring.datasource.username=postgres"
|
|
123
|
+
- "spring.datasource.password=postgres"
|
|
124
|
+
- "spring.datasource.driverClassName=org.postgresql.Driver"
|
|
125
|
+
- "spring.jpa.properties.hibernate.dialect=ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect"
|
|
126
|
+
- "hapi.fhir.fhir_version=R4"
|
|
127
|
+
- "hapi.fhir.cors.allowed-origins=*"
|
|
128
|
+
- "hapi.fhir.cors.allowCredentials=true"
|
|
129
|
+
- "hapi.fhir.bulkdata.enabled=true"
|
|
130
|
+
- "hapi.fhir.bulk_export_enabled=true"
|
|
131
|
+
- "hapi.fhir.bulk_import_enabled=true"
|
|
132
|
+
- "hapi.fhir.enforce_referential_integrity_on_write=false"
|
|
133
|
+
- "hapi.fhir.enforce_referential_integrity_on_delete=false"
|
|
134
|
+
|
|
135
|
+
mcp-fhir:
|
|
136
|
+
image: beapen/fhir-mcp-server:1.0
|
|
137
|
+
ports:
|
|
138
|
+
- 8006:8000
|
|
139
|
+
restart: "unless-stopped"
|
|
140
|
+
depends_on:
|
|
141
|
+
- fhir
|
|
142
|
+
environment:
|
|
143
|
+
- FHIR_SERVER_BASE_URL="http://fhir:8005/baseR4"
|
|
144
|
+
- FHIR_SERVER_SCOPES="*"
|
|
145
|
+
- FHIR_SERVER_ACCESS_TOKEN="none"
|
|
146
|
+
|
|
147
|
+
cql-elm:
|
|
148
|
+
image: cqframework/cql-translation-service:latest
|
|
149
|
+
ports:
|
|
150
|
+
- 8091:8080
|
|
151
|
+
restart: "unless-stopped"
|
|
152
|
+
|
|
153
|
+
cql-web:
|
|
154
|
+
image: beapen/cql_runner:latest
|
|
155
|
+
ports:
|
|
156
|
+
- 8092:80
|
|
157
|
+
restart: "unless-stopped"
|
|
158
|
+
depends_on:
|
|
159
|
+
- fhir
|
|
160
|
+
|
|
161
|
+
langfuse:
|
|
162
|
+
image: ghcr.io/langfuse/langfuse:latest
|
|
163
|
+
depends_on:
|
|
164
|
+
- postgres-db
|
|
165
|
+
ports:
|
|
166
|
+
- "3000:3000"
|
|
167
|
+
environment:
|
|
168
|
+
- DATABASE_URL=postgresql://postgres:postgres@postgres-db:5432/postgres
|
|
169
|
+
- NEXTAUTH_SECRET=mysecret
|
|
170
|
+
- SALT=mysalt
|
|
171
|
+
- NEXTAUTH_URL=http://langfuse:3000
|
|
172
|
+
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false}
|
|
173
|
+
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
|
|
174
|
+
|
|
175
|
+
postgres-db:
|
|
176
|
+
image: postgres
|
|
177
|
+
restart: "unless-stopped"
|
|
178
|
+
environment:
|
|
179
|
+
- POSTGRES_USER=postgres
|
|
180
|
+
- POSTGRES_PASSWORD=postgres
|
|
181
|
+
- POSTGRES_DB=postgres
|
|
182
|
+
ports:
|
|
183
|
+
- 5432:5432
|
|
184
|
+
volumes:
|
|
185
|
+
- postgres-db:/var/lib/postgresql/data
|
|
186
|
+
|
|
187
|
+
redis:
|
|
188
|
+
image: redislabs/redisearch:2.8.8
|
|
189
|
+
ports:
|
|
190
|
+
- 6379:6379
|
|
191
|
+
restart: "unless-stopped"
|
|
192
|
+
volumes:
|
|
193
|
+
- redis-db:/data
|
|
194
|
+
|
|
195
|
+
redis-commander:
|
|
196
|
+
image: rediscommander/redis-commander:latest
|
|
197
|
+
restart: "unless-stopped"
|
|
198
|
+
environment:
|
|
199
|
+
- REDIS_HOSTS=local:redis:6379
|
|
200
|
+
ports:
|
|
201
|
+
- "8081:8081"
|
|
202
|
+
|
|
203
|
+
neo4j:
|
|
204
|
+
image: neo4j:5.1-enterprise
|
|
205
|
+
ports:
|
|
206
|
+
- 7474:7474
|
|
207
|
+
- 7687:7687
|
|
208
|
+
environment:
|
|
209
|
+
- NEO4J_AUTH=neo4j/password
|
|
210
|
+
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
|
|
211
|
+
- NEO4J_PLUGINS=["apoc", "graph-data-science", "n10s"]
|
|
212
|
+
- NEO4J_dbms_security_procedures_unrestricted=apoc.*,gds.*,n10s.*
|
|
213
|
+
- NEO4J_dbms_security_procedures_whitelist=apoc.*,gds.*,n10s.*
|
|
214
|
+
restart: "unless-stopped"
|
|
215
|
+
volumes:
|
|
216
|
+
- neo4j-db:/data
|
|
217
|
+
|
|
218
|
+
fhirg:
|
|
219
|
+
image: beapen/fhirg:latest
|
|
220
|
+
ports:
|
|
221
|
+
- 8004:8080
|
|
222
|
+
restart: "unless-stopped"
|
|
223
|
+
environment:
|
|
224
|
+
- spring.neo4j.uri=bolt://neo4j:7687
|
|
225
|
+
- spring.neo4j.authentication.username=neo4j
|
|
226
|
+
- spring.neo4j.authentication.password=password
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
volumes:
|
|
230
|
+
openmrs-data: ~
|
|
231
|
+
openmrs-db: ~
|
|
232
|
+
fhir-db: ~
|
|
233
|
+
postgres-db: ~
|
|
234
|
+
redis-db: ~
|
|
235
|
+
neo4j-db: ~
|
|
236
|
+
ollama-code: ~
|
|
237
|
+
ollama-root: ~
|
|
238
|
+
ollama-webui: ~
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Use Python 3.12 slim as base image
|
|
2
|
+
FROM python:3.12-slim AS base
|
|
3
|
+
|
|
4
|
+
# Update package lists and install Git
|
|
5
|
+
RUN apt-get update && \
|
|
6
|
+
apt-get install -y --no-install-recommends git && \
|
|
7
|
+
rm -rf /var/lib/apt/lists/*
|
|
8
|
+
|
|
9
|
+
# Install uv
|
|
10
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
|
11
|
+
|
|
12
|
+
# Set working directory
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
|
|
15
|
+
# Copy pyproject.toml into the image
|
|
16
|
+
COPY pyproject.toml /app/pyproject.toml
|
|
17
|
+
COPY README.md /app/README.md
|
|
18
|
+
|
|
19
|
+
# Install dependencies and generate uv.lock
|
|
20
|
+
RUN uv sync --no-dev
|
|
21
|
+
|
|
22
|
+
# Copy the project into the image
|
|
23
|
+
COPY . /app
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Run the server
|
|
27
|
+
CMD ["uv", "run", "python", "app/server.py"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# README
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from kink import di
|
|
2
|
+
from os import getenv
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
from langchain_core.prompts import PromptTemplate
|
|
5
|
+
from langchain_core.language_models.fake import FakeListLLM
|
|
6
|
+
|
|
7
|
+
## Override the default configuration of elixirs here if needed
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def bootstrap():
|
|
11
|
+
load_dotenv()
|
|
12
|
+
fake_llm = FakeListLLM(responses=["Paris", "I don't know"])
|
|
13
|
+
di["main_prompt"] = PromptTemplate.from_template(
|
|
14
|
+
"Summarize the following in 100 words: {input}"
|
|
15
|
+
)
|
|
16
|
+
di["main_llm"] = fake_llm
|
|
17
|
+
di["cds_hook_discovery"] = {
|
|
18
|
+
"services": [
|
|
19
|
+
{
|
|
20
|
+
"id": "dhti-service",
|
|
21
|
+
"hook": "order-select",
|
|
22
|
+
"title": "MyOrg Order Assistant",
|
|
23
|
+
"description": "Provides suggestions and actions for selected draft orders, including handling CommunicationRequest resources.",
|
|
24
|
+
"prefetch": {
|
|
25
|
+
"patient": "Patient/{{context.patientId}}",
|
|
26
|
+
"draftOrders": "Bundle?patient={{context.patientId}}&status=draft",
|
|
27
|
+
},
|
|
28
|
+
"scopes": [
|
|
29
|
+
"launch",
|
|
30
|
+
"patient/Patient.read",
|
|
31
|
+
"user/Practitioner.read",
|
|
32
|
+
"patient/CommunicationRequest.read",
|
|
33
|
+
],
|
|
34
|
+
"metadata": {
|
|
35
|
+
"author": "MyOrg CDS Team",
|
|
36
|
+
"version": "1.0.0",
|
|
37
|
+
"supportedResources": [
|
|
38
|
+
"CommunicationRequest",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from langserve import add_routes
|
|
3
|
+
from langchain_core.runnables.config import RunnableConfig
|
|
4
|
+
from dhti_elixir_base.cds_hook.routes import add_services, add_invokes
|
|
5
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
mcp_server = FastMCP(name="dhti-mcp-server")
|
|
8
|
+
|
|
9
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
10
|
+
# DHTI_CLI_IMPORT
|
|
11
|
+
import uvicorn
|
|
12
|
+
|
|
13
|
+
# Comes after elixir bootstraps, so can override elixir configurations
|
|
14
|
+
from bootstrap import bootstrap
|
|
15
|
+
bootstrap()
|
|
16
|
+
|
|
17
|
+
app = FastAPI(title="dhti-elixir-server")
|
|
18
|
+
# Mount the MCP server's ASGI application at a specific path (Exposes /messages and /sse endpoints)
|
|
19
|
+
app.mount("/langserve/mcp", mcp_server.sse_app())
|
|
20
|
+
|
|
21
|
+
origins = [
|
|
22
|
+
"*",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
app.add_middleware(
|
|
26
|
+
CORSMiddleware,
|
|
27
|
+
allow_origins=origins,
|
|
28
|
+
allow_credentials=True,
|
|
29
|
+
allow_methods=["*"],
|
|
30
|
+
allow_headers=["*"],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Define a root endpoint
|
|
34
|
+
@app.get("/langserve")
|
|
35
|
+
async def read_root():
|
|
36
|
+
return {"message": "Hello from DHTI!"}
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from langfuse import Langfuse
|
|
40
|
+
from langfuse.callback import CallbackHandler
|
|
41
|
+
langfuse_handler = CallbackHandler()
|
|
42
|
+
langfuse_handler.auth_check()
|
|
43
|
+
config = RunnableConfig(callbacks=[langfuse_handler])
|
|
44
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
45
|
+
# DHTI_LANGFUSE_ROUTE
|
|
46
|
+
|
|
47
|
+
except:
|
|
48
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
49
|
+
# DHTI_NORMAL_ROUTE
|
|
50
|
+
x = True
|
|
51
|
+
|
|
52
|
+
# ! DO NOT REMOVE THE COMMENT BELOW
|
|
53
|
+
# DHTI_COMMON_ROUTE
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
uvicorn.run(app, host="0.0.0.0", port=8001)
|