Skip to content

Run the Assistant service

The Assistant service runs as one container on a Linux server close to PeopleSoft, behind nginx. Like the AI Assist server, it runs rootless under a dedicated user, such as aips, as a systemd service that Podman’s Quadlet generates.

Your administrator provides the image, either in your organization’s container registry or as a file. Load it, and tag it aipeoplesoft-assistant:1.0:

Terminal window
podman pull <your-registry>/aipeoplesoft-assistant:1.0
podman tag <your-registry>/aipeoplesoft-assistant:1.0 \
aipeoplesoft-assistant:1.0

The service needs two secrets: the API key for your LLM, and the client secret it uses to exchange tokens with your identity provider. As the aips user, store each one as a Podman secret:

Terminal window
printf 'LLM API key: '; read -rs AIPS_LLM_API_KEY; echo
export AIPS_LLM_API_KEY
podman secret create --env=true \
aips_llm_api_key AIPS_LLM_API_KEY
unset AIPS_LLM_API_KEY
printf 'Client secret: '; read -rs AIPS_IDP_CLIENT_SECRET; echo
export AIPS_IDP_CLIENT_SECRET
podman secret create --env=true \
aips_idp_secret AIPS_IDP_CLIENT_SECRET
unset AIPS_IDP_CLIENT_SECRET

Create the directory ~/assistant for the configuration, with specs inside it for the OpenAPI descriptions, and ~/assistant-audit for the audit log. Then save the configuration as ~/assistant/assistant.yaml:

assistant.yaml
server:
port: 8080 # inside the container
allowed_origins:
- https://hr.example.com # where the chat page runs
auth:
issuer: https://login.example.com/
audience: api://aipeoplesoft-assistant
client_id: aipeoplesoft-assistant
client_secret: ${AIPS_IDP_CLIENT_SECRET}
peoplesoft_audience: api://peoplesoft-hr
peoplesoft:
base_url: https://hr.example.com
profile: GET /profile # optional; see below
llm:
endpoint: https://llm.example.com/v1
model: your-model-id
api_key: ${AIPS_LLM_API_KEY}
tools:
catalog: tools.yaml
max_calls_per_turn: 8
timeout_seconds: 20
conversations:
retention_hours: 8
audit:
destination: /var/log/aipeoplesoft/assistant-audit.jsonl
  • server.allowed_origins: the PeopleSoft web servers that the chat page is served from. The service answers browser requests from these origins only.
  • peoplesoft.base_url: the server of your application services, as in their OpenAPI descriptions.
  • peoplesoft.profile: an operation that returns the user’s name, role, language, and time zone, for the turn context. Until you build it, the service uses the name in the token, and English.
  • llm: your LLM provider’s API endpoint and model, or your own model server’s. The model must support tool calling and streaming.

Save the first tool catalog as ~/assistant/tools.yaml, and the OpenAPI description you downloaded in Build the first application service as ~/assistant/specs/zz_assistant_hr.json. Paths in the catalog are relative to the catalog file:

tools.yaml
services:
- openapi: specs/zz_assistant_hr.json
tools:
get_absence_balances:
operation: GET /absence/balances
progress: Checking your absence balances

As you build more application services, add their OpenAPI descriptions and tools here, as PeopleSoft APIs as tools describes, and restart the service.

As the aips user, save this unit file as ~/.config/containers/systemd/aipeoplesoft-assistant.container:

aipeoplesoft-assistant.container
[Unit]
Description=aiPeopleSoft Assistant service
[Container]
Image=localhost/aipeoplesoft-assistant:1.0
Exec=serve
PublishPort=127.0.0.1:8096:8080
Volume=%h/assistant:/etc/aipeoplesoft:ro,Z
Volume=%h/assistant-audit:/var/log/aipeoplesoft:Z
Secret=aips_llm_api_key,type=env,target=AIPS_LLM_API_KEY
Secret=aips_idp_secret,type=env,target=AIPS_IDP_CLIENT_SECRET
[Service]
Restart=always
[Install]
WantedBy=default.target

Then start it, and keep the user’s services running after you log out:

Terminal window
systemctl --user daemon-reload
systemctl --user start aipeoplesoft-assistant
sudo loginctl enable-linger aips

nginx terminates TLS and forwards requests to the container. Answers stream as server-sent events, so turn off response buffering:

nginx server block
server {
listen 443 ssl;
server_name assistant.example.com;
ssl_certificate /etc/ssl/assistant.example.com.pem;
ssl_certificate_key /etc/ssl/assistant.example.com.key;
location / {
proxy_pass http://127.0.0.1:8096;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_read_timeout 300s;
}
}

The service answers the browser’s cross-origin checks itself, from allowed_origins, so nginx doesn’t need CORS settings of its own.

The image’s check command tests the configuration without starting the service:

Terminal window
podman run --rm \
-v ~/assistant:/etc/aipeoplesoft:ro,Z \
--secret aips_llm_api_key,type=env,target=AIPS_LLM_API_KEY \
--secret aips_idp_secret,type=env,target=AIPS_IDP_CLIENT_SECRET \
aipeoplesoft-assistant:1.0 check
Output of check
aipeoplesoft-assistant 1.0.0
Identity https://login.example.com/, signing keys found
Client aipeoplesoft-assistant, signed in
Tools 1 tool, every operation found in its OpenAPI description
PeopleSoft https://hr.example.com, reachable
LLM your-model-id, answering with tool calls
Result ready

With a test user’s token for the Assistant, whose audience is api://aipeoplesoft-assistant, ask a question from the command line. -N shows the answer as it streams:

Terminal window
curl -N https://assistant.example.com/chat \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"question": "How many vacation hours do I have left?"}'
The streamed response
event: progress
data: {"text": "Checking your absence balances"}
event: answer
data: {"text": "You have 46.5 hours of vacation"}
event: answer
data: {"text": " as of today."}
event: done
data: {"conversation": "c-7f3a9b"}

To ask a follow-up in the same conversation, send its ID with the next question, as "conversation": "c-7f3a9b".